feat: add git integration enhancements (phase 1)

Real-time git awareness:
- Auto-refresh git context on window focus and after run completion
- Periodic background polling (60s interval, configurable via settings)
- Pre-run working tree snapshot capture for project-backed runs
- Debounced refresh scheduling to coalesce rapid triggers

Backend (main process):
- GitService.captureWorkingTreeSnapshot() with per-file metadata
- Enriched parseWorkingTree() producing ProjectGitWorkingTreeFile entries
- AryxAppService: scheduleProjectGitRefresh(), periodic timer, focus hook
- preRunGitSnapshot persisted on SessionRunRecord with normalization

Frontend (renderer):
- Settings toggle for auto-refresh (General > Git section)
- RunTimeline: git baseline indicator showing branch and change summary
- Enhanced GitContextBadge tooltip with change breakdown details
- ChatPane: enriched git tooltip with staged/modified/untracked counts
- New IPC channel: setGitAutoRefreshEnabled

Tests: 8 new tests covering snapshot capture, refresh scheduling,
scratchpad skip behavior, and auto-refresh setting persistence.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-31 16:37:02 +01:00
co-authored by Copilot
parent 49933f218b
commit 44d0ab07db
17 changed files with 845 additions and 24 deletions
+54
View File
@@ -121,4 +121,58 @@ describe('GitService', () => {
head: undefined,
});
});
test('captures a structured pre-run working tree snapshot', async () => {
const service = createService({
'rev-parse --show-toplevel': 'C:\\workspace\\repo\n',
'status --porcelain=1 --untracked-files=all': 'R src\\old.ts -> src\\new.ts\n M src\\app.ts\n?? notes.txt\nUU conflict.txt\n',
'branch --show-current': 'feature/git-context\n',
});
await expect(service.captureWorkingTreeSnapshot('C:\\workspace\\repo', '2026-03-23T19:00:00.000Z')).resolves.toEqual({
scannedAt: '2026-03-23T19:00:00.000Z',
repoRoot: 'C:\\workspace\\repo',
branch: 'feature/git-context',
changedFileCount: 4,
changes: {
staged: 1,
unstaged: 1,
untracked: 1,
conflicted: 1,
},
files: [
{
path: 'src\\new.ts',
previousPath: 'src\\old.ts',
stagedStatus: 'renamed',
unstagedStatus: undefined,
},
{
path: 'src\\app.ts',
stagedStatus: undefined,
unstagedStatus: 'modified',
},
{
path: 'notes.txt',
unstagedStatus: 'untracked',
},
{
path: 'conflict.txt',
stagedStatus: 'unmerged',
unstagedStatus: 'unmerged',
isConflicted: true,
},
],
});
});
test('returns no working tree snapshot outside a git repository', async () => {
const service = createService({
'rev-parse --show-toplevel': createGitError('fatal: not a git repository', {
stderr: 'fatal: not a git repository (or any of the parent directories): .git',
}),
});
await expect(service.captureWorkingTreeSnapshot('C:\\workspace\\not-a-repo', '2026-03-23T19:00:00.000Z')).resolves.toBeUndefined();
});
});