fix: preserve changes when diff fails

This commit is contained in:
2026-08-23 16:12:20 +08:00
parent 7e024093b1
commit f9e21195c1
3 changed files with 50 additions and 5 deletions

View File

@@ -98,6 +98,10 @@
`changes.touched` ACK bridge; full diff/preview data stays Main-only; and
untracked reads plus previews are both limited to 8 KiB while size/mtime
detect equal-length changes beyond that window.
- Closed the follow-up review finding by making per-candidate Git diff failure,
non-zero exit, or output-limit rejection non-fatal: the Main snapshot keeps
the changed relative path and status while omitting only the unavailable
diff. The regression uses the same `Git output is too large` failure shape.
- The packaged smoke now has two halves: a staged production-closure Pi
process check and a controlled Electron-Node authenticated bridge check that
actually executes browser status/screenshot attachment and game asset
@@ -114,7 +118,7 @@
`Makelore/index.tsx`; none are in this task's changed scope).
- Final focused PI-090/Pi projection/bridge/game/resource suite: 12 files,
71 tests passed. The planner's five minimal failure shapes are also covered
by the 4-file 19-test tracker/bridge/runtime subset.
by the final 4-file 20-test tracker/bridge/runtime subset.
- Final serial full suite
(`corepack pnpm vitest run --maxWorkers=1 --no-file-parallelism`): 203 files,
2226 tests passed, 2 skipped. Two preceding parallel full-suite runs each

View File

@@ -325,10 +325,17 @@ export class ConversationChangeTracker {
diff?: string;
truncated?: boolean;
}> {
const [working, staged] = await Promise.all([
this.git.run(projectPath, ['diff', '--no-ext-diff', '--no-color', '--relative', '--', relativePath]),
this.git.run(projectPath, ['diff', '--cached', '--no-ext-diff', '--no-color', '--relative', '--', relativePath]),
]);
let working: GitCommandResult;
let staged: GitCommandResult;
try {
[working, staged] = await Promise.all([
this.git.run(projectPath, ['diff', '--no-ext-diff', '--no-color', '--relative', '--', relativePath]),
this.git.run(projectPath, ['diff', '--cached', '--no-ext-diff', '--no-color', '--relative', '--', relativePath]),
]);
} catch {
return {};
}
if (working.code !== 0 || staged.code !== 0) return {};
const bounded = boundedText(`${staged.stdout}${working.stdout}`, MAX_DIFF_BYTES);
return {
...(bounded.text ? { diff: bounded.text } : {}),

View File

@@ -143,6 +143,40 @@ describe('PI-090 product tools', () => {
)).files).toEqual([expect.objectContaining({ path: 'notes.txt', preview: 'local notes\n' })]);
});
it('keeps the changed path when a candidate diff is unavailable or oversized', async () => {
const root = await temporaryRoot('makelore-pi-diff-unavailable-');
await writeFile(path.join(root, 'tracked.txt'), 'changed\n', 'utf8');
let statusReads = 0;
const tracker = new ConversationChangeTracker({
async run(_projectPath, args) {
if (args[0] === 'rev-parse' && args[1] === '--is-inside-work-tree') {
return { code: 0, stdout: 'true\n' };
}
if (args[0] === 'rev-parse') return { code: 0, stdout: `${'a'.repeat(40)}\n` };
if (args[0] === 'status') {
statusReads += 1;
return {
code: 0,
stdout: statusReads === 1
? ''
: '1 .M N... 100644 100644 100644 abc abc tracked.txt\0',
};
}
if (args[0] === 'diff') throw new Error('Git output is too large');
throw new Error(`Unexpected Git call: ${args.join(' ')}`);
},
});
await tracker.beginRun({
conversationId: 'conversation-a', runId: 'run-a', projectPath: root,
});
const snapshot = await tracker.recordTouchedPaths(
'conversation-a', 'run-a', ['tracked.txt'],
);
expect(snapshot.files).toEqual([{
path: 'tracked.txt', status: 'modified',
}]);
});
it('supports no-git projects and rejects paths outside the project', async () => {
const root = await temporaryRoot('makelore-pi-no-git-');
await writeFile(path.join(root, 'notes.txt'), 'local notes\n', 'utf8');