mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-03 23:37:53 +01:00
autoplan shadow: /ship review fixes
- register test/fixtures/shadow-fake-model.ts in FREE_FIXTURES (free-fixtures inventory failed) - withRunLock: count dead-owner reclaims apart from waitMs iterations, so a long wait can still reclaim - buildPage: fill the decider template in one pass, so slot names inside the plan stay plan text - revise regate: the shared publish step prints the GATE and ESCALATE lines once Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
a30cbb153c
commit
3bc7341b02
5 files changed
+16
-8
No files matched your search
@@ -23,7 +23,7 @@ import * as path from 'node:path';
|
||||
import { exportRun } from './autoplan-export';
|
||||
import { applyRenames, approve, closeDraftDirections, finishGate, planReopen } from './autoplan-gate';
|
||||
import { carryLines, voidPendingCheck } from './autoplan-carry';
|
||||
import { atCap, escalateLine, pendingDeltas, roundCap, roundOf } from './autoplan-budget';
|
||||
import { atCap, pendingDeltas, roundCap, roundOf } from './autoplan-budget';
|
||||
import { diffStat, lineDiff } from './autoplan-confirm';
|
||||
import { SETTLEABLE_CHANGES } from './autoplan-reconcile';
|
||||
import { normalizeRevision, reviewHash, scaffold } from './autoplan-plan-text';
|
||||
@@ -112,9 +112,7 @@ function approveOrRegate(state: RunState, at: string): string[] {
|
||||
if (pendingDeltas(state).length) {
|
||||
finishGate(state);
|
||||
const carried = carryLines(state);
|
||||
if (state.status !== 'gate_pending') return carried;
|
||||
const escalate = escalateLine(state);
|
||||
return [...carried, `GATE: gate_rev=${state.gate_rev} (a plan change after a round cap needs the owner) render with: gstack-gate render ${path.join(state.out, 'gate.json')}`, ...(escalate ? [escalate] : [])];
|
||||
return carried;
|
||||
}
|
||||
return [...approve(state, at), `APPROVED: gate_rev=${state.gate_rev} (every accepted change is in the plan or settled)`];
|
||||
}
|
||||
|
||||
+2
-2
@@ -50,7 +50,7 @@ export function withRunLock<T>(out: string, fn: () => T, opts: { steal?: boolean
|
||||
const mine = JSON.stringify({ pid: process.pid, host: os.hostname(), at: now() });
|
||||
const staged = `${lock}.${process.pid}.${crypto.randomBytes(4).toString('hex')}`;
|
||||
fs.writeFileSync(staged, mine);
|
||||
for (let i = 0; ; i++) {
|
||||
for (let reclaims = 0; ;) {
|
||||
try {
|
||||
fs.linkSync(staged, lock);
|
||||
fs.unlinkSync(staged);
|
||||
@@ -69,7 +69,7 @@ export function withRunLock<T>(out: string, fn: () => T, opts: { steal?: boolean
|
||||
if (alive && Date.now() < waitUntil) { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100); continue; }
|
||||
if (alive) { fs.rmSync(staged, { force: true }); throw new RunError('RUN_LOCKED', unreadable ? `${lock} is being written by another command` : `pid ${owner.pid} on ${owner.host} since ${owner.at}`, 3); }
|
||||
try { fs.unlinkSync(lock); } catch { /* raced with its owner's exit */ }
|
||||
if (i > 50) { fs.rmSync(staged, { force: true }); throw new RunError('RUN_LOCKED', `could not reclaim ${lock}`, 3); }
|
||||
if (++reclaims > 50) { fs.rmSync(staged, { force: true }); throw new RunError('RUN_LOCKED', `could not reclaim ${lock}`, 3); }
|
||||
}
|
||||
}
|
||||
try { return fn(); } finally { try { if (fs.readFileSync(lock, 'utf8') === mine) fs.unlinkSync(lock); } catch { /* already gone */ } }
|
||||
|
||||
@@ -205,8 +205,8 @@ export function buildPage(state: RunState, list: GateList, deciderText: string):
|
||||
});
|
||||
const planText = fs.existsSync(state.active_plan) ? fs.readFileSync(state.active_plan, 'utf8') : '';
|
||||
const plan = (extractHeading(planText, 'Implementation plan') ?? planText).slice(0, PLAN_CHARS);
|
||||
const prompt = deciderText.replace('{{PLAN}}', () => plan).replace('{{QUESTIONS}}', () => '```json\n' + JSON.stringify(items.map(i => i.frozen), null, 2) + '\n```')
|
||||
.replace('{{IDS}}', () => items.map(i => i.item.id).join(', '));
|
||||
const slots: Record<string, string> = { PLAN: plan, QUESTIONS: '```json\n' + JSON.stringify(items.map(i => i.frozen), null, 2) + '\n```', IDS: items.map(i => i.item.id).join(', ') };
|
||||
const prompt = deciderText.replace(/\{\{(PLAN|QUESTIONS|IDS)\}\}/g, (_, k: string) => slots[k]!);
|
||||
return { prompt, items, plan_sha256: sha256(planText), prompt_sha256: sha256(deciderText), request_sha256: sha256(prompt) };
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,15 @@ describe('the reply contract: exactly one valid object per requested id', () =>
|
||||
expect(page.prompt).toContain('"id": "uc1"');
|
||||
expect(page.prompt).not.toContain('{{');
|
||||
});
|
||||
test('slot names inside the plan text stay plan text; the template slots are still filled', () => {
|
||||
const planFile = path.join(TMP, 'slot-plan.md');
|
||||
fs.writeFileSync(planFile, '## Implementation plan\n\nfill {{QUESTIONS}} and {{IDS}} in {{PLAN}}\n');
|
||||
const p = buildPage({ ...readState(out), active_plan: planFile }, gateOf(out), decider());
|
||||
expect(p.prompt).toContain('fill {{QUESTIONS}} and {{IDS}} in {{PLAN}}');
|
||||
expect(p.prompt).toContain('"id": "uc1"');
|
||||
expect(p.prompt).toContain('uc1, uc2, p1');
|
||||
expect(p.prompt.split('{{').length).toBe(4);
|
||||
});
|
||||
test('a good reply is clean', () => {
|
||||
expect(parsePage(reply(valid), page.items).clean).toBe(true);
|
||||
});
|
||||
|
||||
@@ -200,6 +200,7 @@ export const FREE_FIXTURES: Readonly<Record<string, readonly string[]>> = {
|
||||
'test/fixtures/review-design-lite-reports-ci-36633323521.json': ['test/review-finalization-budget.test.ts'],
|
||||
'test/fixtures/review-gate-integrity-opus-reports.json': ['test/gate-integrity-templates.test.ts'],
|
||||
'test/fixtures/session-update-heartbeat.sh': ['test/session-update-heartbeat.test.ts'],
|
||||
'test/fixtures/shadow-fake-model.ts': ['test/autoplan-shadow-driver.test.ts', 'test/autoplan-shadow.test.ts'],
|
||||
'test/fixtures/shard-cli-parity/**': ['test/shard-cli-parity.test.ts'],
|
||||
'test/fixtures/shard-equivalence/**': ['test/shard-engine-equivalence.test.ts'],
|
||||
'test/fixtures/shared-libs-claude-version-probe.json': ['test/shared-libs-fixture.test.ts'],
|
||||
|
||||
Reference in new issue
Block a user