Skip to content

Commit 41801d2

Browse files
Add git context access for agent
- Create WorkspaceCommitDto and WorkspaceGitInfoDto - Extend GitAdapterInterface with getCurrentBranch, getRecentCommits, getBranches - Implement new methods in GitCliAdapter using NUL-separated format - Update SimulatedGitAdapter to delegate to real adapter - Add getGitInfo to WorkspaceGitService - Add getGitInfo to WorkspaceMgmtFacade and interface - Extend AgentExecutionContext to store git info - Add getGitContextInfo to WorkspaceToolingServiceInterface - Inject git context into ContentEditorAgent system prompt - Set git context in RunEditSessionHandler before agent runs This allows the agent to see recent commits, current branch, and available branches at session start. Co-authored-by: Manuel Kießling <manuel@kiessling.net>
1 parent 7d2dccd commit 41801d2

File tree

14 files changed

+298
-0
lines changed

14 files changed

+298
-0
lines changed

src/ChatBasedContentEditor/Infrastructure/Handler/RunEditSessionHandler.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,18 @@ public function __invoke(RunEditSessionMessage $message): void
101101
$project->remoteContentAssetsManifestUrls
102102
);
103103

104+
// Set git context info for the agent
105+
try {
106+
$gitInfo = $this->workspaceMgmtFacade->getGitInfo($conversation->getWorkspaceId());
107+
$this->executionContext->setGitInfo($gitInfo);
108+
} catch (Throwable $e) {
109+
// Log but don't fail the session - git info is nice-to-have
110+
$this->logger->debug('Failed to get git info for agent', [
111+
'workspaceId' => $conversation->getWorkspaceId(),
112+
'error' => $e->getMessage(),
113+
]);
114+
}
115+
104116
// Build agent configuration from project settings (#79).
105117
$agentConfig = new AgentConfigDto(
106118
$project->agentBackgroundInstructions,

src/LlmContentEditor/Domain/Agent/ContentEditorAgent.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ public function instructions(): string
7070
$base .= "\n\nWORKING FOLDER (use for all path-based tools): " . $this->agentConfig->workingFolderPath;
7171
}
7272

73+
$gitContextInfo = $this->sitebuilderFacade->getGitContextInfo();
74+
if ($gitContextInfo !== '') {
75+
$base .= "\n\n" . $gitContextInfo;
76+
}
77+
7378
$history = $this->resolveChatHistory();
7479
if ($history instanceof TurnActivityProviderInterface) {
7580
$summary = $history->getTurnActivitySummary();

src/WorkspaceMgmt/Domain/Service/WorkspaceGitService.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66

77
use App\ProjectMgmt\Facade\ProjectMgmtFacadeInterface;
88
use App\WorkspaceMgmt\Domain\Entity\Workspace;
9+
use App\WorkspaceMgmt\Facade\Dto\WorkspaceCommitDto;
10+
use App\WorkspaceMgmt\Facade\Dto\WorkspaceGitInfoDto;
911
use App\WorkspaceMgmt\Infrastructure\Adapter\GitAdapterInterface;
1012
use App\WorkspaceMgmt\Infrastructure\Adapter\GitHubAdapterInterface;
1113
use App\WorkspaceMgmt\Infrastructure\Service\GitHubUrlServiceInterface;
14+
use DateTimeImmutable;
1215
use Doctrine\ORM\EntityManagerInterface;
1316
use Psr\Log\LoggerInterface;
1417
use RuntimeException;
@@ -270,4 +273,28 @@ private function buildPrBody(
270273

271274
return implode("\n", $lines);
272275
}
276+
277+
/**
278+
* Get git context information for a workspace.
279+
*/
280+
public function getGitInfo(Workspace $workspace, int $commitLimit = 10): WorkspaceGitInfoDto
281+
{
282+
$workspacePath = $this->getWorkspacePath($workspace);
283+
284+
$currentBranch = $this->gitAdapter->getCurrentBranch($workspacePath);
285+
$rawCommits = $this->gitAdapter->getRecentCommits($workspacePath, $commitLimit);
286+
$branches = $this->gitAdapter->getBranches($workspacePath);
287+
288+
$commits = array_map(
289+
static fn (array $raw): WorkspaceCommitDto => new WorkspaceCommitDto(
290+
$raw['hash'],
291+
$raw['subject'],
292+
$raw['body'],
293+
new DateTimeImmutable($raw['timestamp'])
294+
),
295+
$rawCommits
296+
);
297+
298+
return new WorkspaceGitInfoDto($currentBranch, $commits, $branches);
299+
}
273300
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\WorkspaceMgmt\Facade\Dto;
6+
7+
use DateTimeImmutable;
8+
9+
/**
10+
* DTO representing a git commit.
11+
*/
12+
final readonly class WorkspaceCommitDto
13+
{
14+
public function __construct(
15+
public string $hash,
16+
public string $message,
17+
public string $body,
18+
public DateTimeImmutable $committedAt,
19+
) {
20+
}
21+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\WorkspaceMgmt\Facade\Dto;
6+
7+
/**
8+
* DTO representing git context information for a workspace.
9+
*/
10+
final readonly class WorkspaceGitInfoDto
11+
{
12+
/**
13+
* @param list<WorkspaceCommitDto> $recentCommits
14+
* @param list<string> $localBranches
15+
*/
16+
public function __construct(
17+
public string $currentBranch,
18+
public array $recentCommits,
19+
public array $localBranches,
20+
) {
21+
}
22+
}

src/WorkspaceMgmt/Facade/WorkspaceMgmtFacade.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use App\WorkspaceMgmt\Domain\Service\WorkspaceGitService;
1010
use App\WorkspaceMgmt\Domain\Service\WorkspaceService;
1111
use App\WorkspaceMgmt\Domain\Service\WorkspaceStatusGuard;
12+
use App\WorkspaceMgmt\Facade\Dto\WorkspaceGitInfoDto;
1213
use App\WorkspaceMgmt\Facade\Dto\WorkspaceInfoDto;
1314
use App\WorkspaceMgmt\Facade\Enum\WorkspaceStatus;
1415
use App\WorkspaceMgmt\Infrastructure\Adapter\FilesystemAdapterInterface;
@@ -257,6 +258,13 @@ public function runBuild(string $workspaceId): string
257258
return $process->getOutput();
258259
}
259260

261+
public function getGitInfo(string $workspaceId, int $commitLimit = 10): WorkspaceGitInfoDto
262+
{
263+
$workspace = $this->getWorkspaceOrFail($workspaceId);
264+
265+
return $this->gitService->getGitInfo($workspace, $commitLimit);
266+
}
267+
260268
/**
261269
* Resolve a relative path to an absolute path and validate it's within the workspace.
262270
*/

src/WorkspaceMgmt/Facade/WorkspaceMgmtFacadeInterface.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace App\WorkspaceMgmt\Facade;
66

7+
use App\WorkspaceMgmt\Facade\Dto\WorkspaceGitInfoDto;
78
use App\WorkspaceMgmt\Facade\Dto\WorkspaceInfoDto;
89

910
/**
@@ -137,4 +138,15 @@ public function writeWorkspaceFile(string $workspaceId, string $relativePath, st
137138
* @throws \Symfony\Component\Process\Exception\ProcessFailedException if the build fails
138139
*/
139140
public function runBuild(string $workspaceId): string;
141+
142+
/**
143+
* Get git context information for a workspace.
144+
*
145+
* Returns the current branch name, recent commits (with hash, message, body, timestamp),
146+
* and all local branches.
147+
*
148+
* @param string $workspaceId the workspace ID
149+
* @param int $commitLimit the maximum number of recent commits to return
150+
*/
151+
public function getGitInfo(string $workspaceId, int $commitLimit = 10): WorkspaceGitInfoDto;
140152
}

src/WorkspaceMgmt/Infrastructure/Adapter/GitAdapterInterface.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,32 @@ public function push(string $workspacePath, string $branchName, string $token):
7070
* @return bool true if the branch has commits that differ from the base branch, false otherwise
7171
*/
7272
public function hasBranchDifferences(string $workspacePath, string $branchName, string $baseBranch = 'main'): bool;
73+
74+
/**
75+
* Get the name of the currently checked-out branch.
76+
*
77+
* @param string $workspacePath the workspace directory
78+
*
79+
* @return string the current branch name
80+
*/
81+
public function getCurrentBranch(string $workspacePath): string;
82+
83+
/**
84+
* Get the N most recent commits on the current branch.
85+
*
86+
* @param string $workspacePath the workspace directory
87+
* @param int $limit the maximum number of commits to return
88+
*
89+
* @return list<array{hash: string, subject: string, body: string, timestamp: string}>
90+
*/
91+
public function getRecentCommits(string $workspacePath, int $limit = 10): array;
92+
93+
/**
94+
* Get all local branch names.
95+
*
96+
* @param string $workspacePath the workspace directory
97+
*
98+
* @return list<string>
99+
*/
100+
public function getBranches(string $workspacePath): array;
73101
}

src/WorkspaceMgmt/Infrastructure/Adapter/GitCliAdapter.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,81 @@ private function ensureCleanRemoteUrl(string $workspacePath): void
218218
}
219219
}
220220

221+
public function getCurrentBranch(string $workspacePath): string
222+
{
223+
$process = new Process(['git', 'rev-parse', '--abbrev-ref', 'HEAD']);
224+
$process->setWorkingDirectory($workspacePath);
225+
$process->setTimeout(self::TIMEOUT_SECONDS);
226+
227+
$this->runProcess($process, 'Failed to get current branch');
228+
229+
return trim($process->getOutput());
230+
}
231+
232+
public function getRecentCommits(string $workspacePath, int $limit = 10): array
233+
{
234+
// Use NUL byte (\x00) as field separator to safely handle multiline bodies
235+
// Format: hash\x00subject\x00body\x00timestamp\x00
236+
$process = new Process([
237+
'git',
238+
'log',
239+
'-n', (string) $limit,
240+
'--pretty=format:%H%x00%s%x00%b%x00%aI%x00',
241+
]);
242+
$process->setWorkingDirectory($workspacePath);
243+
$process->setTimeout(self::TIMEOUT_SECONDS);
244+
245+
$this->runProcess($process, 'Failed to get recent commits');
246+
247+
$output = $process->getOutput();
248+
if (trim($output) === '') {
249+
return [];
250+
}
251+
252+
// Split by double NUL (end of each commit record)
253+
$records = explode("\x00\x00", rtrim($output, "\x00"));
254+
$commits = [];
255+
256+
foreach ($records as $record) {
257+
$fields = explode("\x00", $record);
258+
if (count($fields) < 4) {
259+
continue;
260+
}
261+
262+
[$hash, $subject, $body, $timestamp] = $fields;
263+
264+
$commits[] = [
265+
'hash' => trim($hash),
266+
'subject' => trim($subject),
267+
'body' => trim($body),
268+
'timestamp' => trim($timestamp),
269+
];
270+
}
271+
272+
return $commits;
273+
}
274+
275+
public function getBranches(string $workspacePath): array
276+
{
277+
$process = new Process(['git', 'branch', '--format=%(refname:short)']);
278+
$process->setWorkingDirectory($workspacePath);
279+
$process->setTimeout(self::TIMEOUT_SECONDS);
280+
281+
$this->runProcess($process, 'Failed to get branches');
282+
283+
$output = trim($process->getOutput());
284+
if ($output === '') {
285+
return [];
286+
}
287+
288+
$branches = array_filter(
289+
explode("\n", $output),
290+
static fn (string $branch): bool => trim($branch) !== ''
291+
);
292+
293+
return array_values(array_map('trim', $branches));
294+
}
295+
221296
private function runProcess(Process $process, string $errorMessage): void
222297
{
223298
$process->run();

src/WorkspaceMgmt/TestHarness/SimulatedGitAdapter.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,21 @@ public function hasBranchDifferences(string $workspacePath, string $branchName,
7474
return $this->realGitAdapter->hasBranchDifferences($workspacePath, $branchName, $baseBranch);
7575
}
7676

77+
public function getCurrentBranch(string $workspacePath): string
78+
{
79+
return $this->realGitAdapter->getCurrentBranch($workspacePath);
80+
}
81+
82+
public function getRecentCommits(string $workspacePath, int $limit = 10): array
83+
{
84+
return $this->realGitAdapter->getRecentCommits($workspacePath, $limit);
85+
}
86+
87+
public function getBranches(string $workspacePath): array
88+
{
89+
return $this->realGitAdapter->getBranches($workspacePath);
90+
}
91+
7792
private function copyDirectory(string $source, string $target): void
7893
{
7994
if (!is_dir($source)) {

0 commit comments

Comments
 (0)