mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-03 09:56:57 +02:00
test(shared-libs): seed the lifecycle replay's first Step 3 pass instead of replaying it
shared-libs-review-lifecycle ran ~88% of its 300 s session budget (12-run census median 265 s, 4/24 sessions timed out). The fixture now executes pass 1's Step 3 once with the real logger and Git: a real unused REVIEW_START, then the diff, inventories, attributes/config/index flags, gstack-review-read output and every file's bytes and sha256, saved to one observation. The model resumes at Step 4 with an exact four-file first read, the observation named as the authoritative pass-1 repository read, one post-fix verification, an explicit pass-2 read list and a twelve-line summary. Pass 2 still runs its own --start, diff, reads, fingerprint and stage actor before --finish. The actor scope now states that a current settled final-pass actor result supplies the replaced QA/adversarial prerequisites and that the no-credit disclosure is a reporting label: one r1 session persisted completed:false from that ambiguity. New assertions: the final binding never uses the seeded token's start or tree, and the observation was read; free controls finish the seeded token (binding changed) and omit the observation read, and both fail.
This commit is contained in:
1 parent
ac177337a1
commit
ea7fbbcb7c
4 files changed
+127
-11
No files matched your search
@@ -862,7 +862,57 @@ export interface SharedReviewStageActor {
|
|||||||
verify(events: any[]): boolean;
|
verify(events: any[]): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reviewPrompt(f: SharedLibsFixture, instructions: string, specialistInput: string, resumed?: SharedReviewResume | Pick<SharedReviewStageActor, 'actorCommand'>): string {
|
export interface SharedLifecycleSeed {
|
||||||
|
token: string;
|
||||||
|
startedAt: string;
|
||||||
|
startWtree: string;
|
||||||
|
diffBase: string;
|
||||||
|
observation: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pass 1's Step 3, executed once by the fixture with the real logger and Git: fetch is a no-op against the
|
||||||
|
* pinned remote, the start token precedes every read, and the saved observation is never rewritten. */
|
||||||
|
export function seedLifecycleFirstPass(f: SharedLibsFixture): SharedLifecycleSeed {
|
||||||
|
const observation = path.join(f.root, 'pass1-observation.md');
|
||||||
|
if (fs.existsSync(observation)) throw new Error('Pass 1 observation already seeded');
|
||||||
|
const env = { ...process.env, ...f.env, PATH: process.env.PATH, GSTACK_HOME: f.state };
|
||||||
|
const diffBase = fixtureGit(f, 'merge-base', 'origin/main', 'HEAD');
|
||||||
|
const token = execFileSync(path.join(SHARED_LIBS_ROOT, 'bin/gstack-review-log'), ['--start', 'review'],
|
||||||
|
{ cwd: f.repo, env, encoding: 'utf8', timeout: 30_000 }).trim();
|
||||||
|
const start = JSON.parse(fs.readFileSync(path.join(f.state, 'projects/fixture-shared-libs/.review-starts', `${token}.json`), 'utf8'));
|
||||||
|
const diff = fixtureGit(f, 'diff', '--no-ext-diff', '--no-textconv', diffBase);
|
||||||
|
const untracked = fixtureGit(f, 'ls-files', '--others', '--exclude-standard');
|
||||||
|
const tracked = fixtureGit(f, 'ls-files');
|
||||||
|
const flagged = fixtureGit(f, 'ls-files', '-v').split('\n').filter(line => line && !line.startsWith('H '));
|
||||||
|
const attributes = ['.gitattributes', '.git/info/attributes'].filter(file => fs.existsSync(path.join(f.repo, file)));
|
||||||
|
const records = execFileSync(path.join(SHARED_LIBS_ROOT, 'bin/gstack-review-read'), [], { cwd: f.repo, env, encoding: 'utf8', timeout: 30_000 });
|
||||||
|
const files = [...tracked.split('\n'), ...untracked.split('\n')].filter(Boolean).map(relative => {
|
||||||
|
const bytes = fs.readFileSync(path.join(f.repo, relative));
|
||||||
|
return `### ${relative} (${bytes.length} bytes, sha256 ${createHash('sha256').update(bytes).digest('hex')})\n\`\`\`\n${bytes.toString('utf8')}\`\`\``;
|
||||||
|
});
|
||||||
|
fs.writeFileSync(observation, `# Pass 1 Step 3 observation (fixture-captured once, in this order)
|
||||||
|
1. git fetch origin main --quiet: exit 0; origin/main is pinned at ${f.tip}.
|
||||||
|
2. DIFF_BASE=$(git merge-base origin/main HEAD) = ${diffBase}; branch ${fixtureGit(f, 'symbolic-ref', '--short', 'HEAD')}; HEAD ${fixtureGit(f, 'rev-parse', 'HEAD')}.
|
||||||
|
3. gstack-review-log --start review printed pass 1 REVIEW_START ${token} (started_at ${start.started_at}). Step 3 leaves it unused; only the final pass's token is finished.
|
||||||
|
4. Reads after the token:
|
||||||
|
- Untracked non-ignored files (git ls-files --others --exclude-standard): ${untracked || '(none)'}
|
||||||
|
- Attribute files: ${attributes.join(', ') || '(none: no .gitattributes or .git/info/attributes)'}; index flags other than H (git ls-files -v): ${flagged.join('; ') || '(none)'}
|
||||||
|
- Repository-local config (git config --local --list):\n${fixtureGit(f, 'config', '--local', '--list')}
|
||||||
|
- gstack-review-read:\n${records.trim()}
|
||||||
|
|
||||||
|
## git diff --no-ext-diff --no-textconv ${diffBase}
|
||||||
|
\`\`\`diff
|
||||||
|
${diff}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Repository files (tracked and untracked)
|
||||||
|
${files.join('\n\n')}
|
||||||
|
`, { mode: 0o600, flag: 'wx' });
|
||||||
|
return { token, startedAt: start.started_at, startWtree: start.wtree, diffBase, observation };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reviewPrompt(f: SharedLibsFixture, instructions: string, specialistInput: string, resumed?: SharedReviewResume | Pick<SharedReviewStageActor, 'actorCommand'>, seed?: SharedLifecycleSeed): string {
|
||||||
|
if (seed && !(resumed && 'actorCommand' in resumed)) throw new Error('A seeded first pass belongs to the edit-capable lifecycle replay');
|
||||||
const scope = resumed && 'actorCommand' in resumed ? `This is an edit-capable component replay with an explicitly declared SYNTHETIC prerequisite actor, not an end-to-end QA/adversarial evaluation. Completed maintainability findings are supplied in ${specialistInput}; verify them against real source.
|
const scope = resumed && 'actorCommand' in resumed ? `This is an edit-capable component replay with an explicitly declared SYNTHETIC prerequisite actor, not an end-to-end QA/adversarial evaluation. Completed maintainability findings are supplied in ${specialistInput}; verify them against real source.
|
||||||
Component scope override for every pass:
|
Component scope override for every pass:
|
||||||
1. Execute the real core/checklist, source/identity/snapshot checks, merge, Fix-First decisions, approved source edits, re-review with a new REVIEW_START, zero-edit convergence and final persistence yourself. Preserve the workflow's permissions and decision questions.
|
1. Execute the real core/checklist, source/identity/snapshot checks, merge, Fix-First decisions, approved source edits, re-review with a new REVIEW_START, zero-edit convergence and final persistence yourself. Preserve the workflow's permissions and decision questions.
|
||||||
@@ -871,7 +921,7 @@ Component scope override for every pass:
|
|||||||
\`\`\`sh
|
\`\`\`sh
|
||||||
${resumed.actorCommand}
|
${resumed.actorCommand}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
4. All prior receipts are preserved. Source-changing cycles invalidate earlier results: after edits, repeat the core review and invoke the actor again on the new zero-edit pass before final persistence. Never refresh an old receipt's hashes or relabel it as a new invocation. Missing, failed, stale or wrong-state results require noncompletion. The actor cannot complete core/checklist review, approve edits, answer decision questions or establish convergence for you. Apply the production COMPLETED/CONVERGED rules to your own work plus the current supplied results; never ask the question actor to override completion.
|
4. All prior receipts are preserved. Source-changing cycles invalidate earlier results: after edits, repeat the core review and invoke the actor again on the new zero-edit pass before final persistence. Never refresh an old receipt's hashes or relabel it as a new invocation. Missing, failed, stale or wrong-state results require noncompletion. The actor cannot complete core/checklist review, approve edits, answer decision questions or establish convergence for you. Apply the production COMPLETED/CONVERGED rules to your own work plus the current supplied results: only a current settled:true actor result from the final pass supplies the replaced Step 4.7 QA and Step 4.8 native adversarial prerequisites for those rules. It does not complete your own remaining work, and the no-credit disclosure below is a reporting label, not a missing stage. Never ask the question actor to override completion.
|
||||||
5. In the final QA/verification summary, identify the actor results as simulated fixture-stage interactions, not actual QA or native adversarial execution; they receive no actual native coverage credit. Report any real post-fix verification separately. Separate genuine QA/native evaluations remain required; this component replay cannot satisfy them.`
|
5. In the final QA/verification summary, identify the actor results as simulated fixture-stage interactions, not actual QA or native adversarial execution; they receive no actual native coverage credit. Report any real post-fix verification separately. Separate genuine QA/native evaluations remain required; this component replay cannot satisfy them.`
|
||||||
: resumed ? `This is a bounded, no-edit resumed-stage fixture. The completed maintainability result is supplied in ${specialistInput}; verify its findings against real source. Read ${resumed.input}: it supplies clearly labeled SYNTHETIC settled Step 4.7 QA and Step 4.8 native adversarial prerequisite results for this isolated fixture state, not evidence that this model executed those stages and never actual native coverage credit. Other specialists and outside providers are not dispatched in this fixture. Do not dispatch or rerun them.
|
: resumed ? `This is a bounded, no-edit resumed-stage fixture. The completed maintainability result is supplied in ${specialistInput}; verify its findings against real source. Read ${resumed.input}: it supplies clearly labeled SYNTHETIC settled Step 4.7 QA and Step 4.8 native adversarial prerequisite results for this isolated fixture state, not evidence that this model executed those stages and never actual native coverage credit. Other specialists and outside providers are not dispatched in this fixture. Do not dispatch or rerun them.
|
||||||
Execute the core/checklist, merge, Fix-First decisions, source/identity/snapshot checks and final persistence yourself. Do not edit target source or Git index flags. A finding that requires edits blocks this bounded replay: report it honestly, without suppressing it or claiming completion. Before final persistence, after your final source checks, run this fixture prerequisite check as the sole command in its Bash call and inspect the entire JSON result:
|
Execute the core/checklist, merge, Fix-First decisions, source/identity/snapshot checks and final persistence yourself. Do not edit target source or Git index flags. A finding that requires edits blocks this bounded replay: report it honestly, without suppressing it or claiming completion. Before final persistence, after your final source checks, run this fixture prerequisite check as the sole command in its Bash call and inspect the entire JSON result:
|
||||||
@@ -881,8 +931,18 @@ ${resumed.checkCommand}
|
|||||||
Only a current result with settled:true supplies the required QA and native adversarial prerequisites; it does not complete your own remaining work. Apply the workflow's unchanged COMPLETED and CONVERGED rules to that combined evidence. Missing, failed, blocked, malformed or stale prerequisites require noncompletion, never an override based on scope. Any source, branch, base, index or configuration change invalidates these supplied results and blocks this bounded no-edit replay; do not regenerate them or claim completion. In the final summary identify QA and native adversarial results as synthetic fixture inputs, not stages you executed.`
|
Only a current result with settled:true supplies the required QA and native adversarial prerequisites; it does not complete your own remaining work. Apply the workflow's unchanged COMPLETED and CONVERGED rules to that combined evidence. Missing, failed, blocked, malformed or stale prerequisites require noncompletion, never an override based on scope. Any source, branch, base, index or configuration change invalidates these supplied results and blocks this bounded no-edit replay; do not regenerate them or claim completion. In the final summary identify QA and native adversarial results as synthetic fixture inputs, not stages you executed.`
|
||||||
: `This is a fixture of the core, merge, Fix-First, and final persistence stages. Specialist input for the merge stage is supplied in ${specialistInput}; verify it against the real source. Do not dispatch additional specialists or outside providers. Never claim that omitted stages completed.
|
: `This is a fixture of the core, merge, Fix-First, and final persistence stages. Specialist input for the merge stage is supplied in ${specialistInput}; verify it against the real source. Do not dispatch additional specialists or outside providers. Never claim that omitted stages completed.
|
||||||
Required reviewer coverage for this scoped replay is the core/checklist review plus the supplied completed maintainability result. Verify the supplied findings against actual source. Other specialist and provider stages are outside this invocation's scope, not unavailable required reviewers. If a required stage or its result actually fails or is missing, preserve the workflow's non-completion rules.`;
|
Required reviewer coverage for this scoped replay is the core/checklist review plus the supplied completed maintainability result. Verify the supplied findings against actual source. Other specialist and provider stages are outside this invocation's scope, not unavailable required reviewers. If a required stage or its result actually fails or is missing, preserve the workflow's non-completion rules.`;
|
||||||
|
const seeded = seed ? `
|
||||||
|
Fixture-seeded pass 1 Step 3; resume pass 1 at Step 4:
|
||||||
|
- The fixture owner already executed pass 1's Step 3 in order with the real tools: git fetch, the merge base (DIFF_BASE ${seed.diffBase}), \`gstack-review-log --start review\` (pass 1 REVIEW_START ${seed.token}), then the diff and every repository read. It saved them once at ${seed.observation}: the diff, tracked and untracked inventories, attributes, config and index flags, the gstack-review-read output, and each repository file's bytes and sha256.
|
||||||
|
- That observation is authoritative for pass 1: it already contains what git fetch, merge-base, diff, status, ls-files, config or attribute reads, gstack-review-read and cat, Read, Grep or Glob of repository files would return, so do not run those for pass 1. Pass 1's REVIEW_START stays unused, as Step 3 says; never finish it.
|
||||||
|
- In your first response, natively Read exactly these four files together: the workflow at ${instructions}, the checklist, ${specialistInput} and the observation. No ls, Glob, Grep or --help is needed.
|
||||||
|
- The observation's gstack-review-read output is NO_REVIEWS, and no review row exists before your final --finish. So Step 5.0's no-prior-reviews rule applies in every pass: skip history matching; shared-code-reuse.md and --check-shared-libs do not apply, and gstack-review-read needs no rerun before the final read-back.
|
||||||
|
- After pass 1's core review and merge: one response holding the installed sharedLibsFingerprint call and, as its own Bash call, the actor invocation. Then Step 5: an auto-fix and the AskUserQuestion may share a response.
|
||||||
|
- After applying Step 5 edits, one Bash call from the repository is the post-fix verification: \`bun test test/retry-after.test.ts\` plus, only if caller exports changed, one bun -e import check. Pass 2 reruns it only if pass 2 edits.
|
||||||
|
- Pass 2 executes Step 3 itself. origin/main is pinned and the fixture's fetch is a no-op, so DIFF_BASE stays ${seed.diffBase}; run --start as the sole command in its Bash call. Then, in one response, run \`git diff ${seed.diffBase}\` and natively Read src/retry-worker.ts, src/retry-route.ts and lib/retry-after.ts; the observation's bytes stay current for files you did not edit. Then finish pass 2's core review with the fingerprint call and the actor in one response, and persist.
|
||||||
|
- Keep the final review summary to at most twelve lines: counts, each auto-fixed, fixed or skipped item with its fingerprint, the verification result and the synthetic-stage disclosure.` : '';
|
||||||
return `Read the fixture workflow at ${instructions} first. Review this repository's current diff against origin/main using that workflow and the actual checklist at ${SHARED_LIBS_ROOT}/review/checklist.md.
|
return `Read the fixture workflow at ${instructions} first. Review this repository's current diff against origin/main using that workflow and the actual checklist at ${SHARED_LIBS_ROOT}/review/checklist.md.
|
||||||
${scope}
|
${scope}${seeded}
|
||||||
The trusted harness infrastructure is fixed; do not rediscover it:
|
The trusted harness infrastructure is fixed; do not rediscover it:
|
||||||
- Trusted asset roots: the installed review skill is ${SHARED_LIBS_ROOT}/review (checklist ${SHARED_LIBS_ROOT}/review/checklist.md, sections ${SHARED_LIBS_ROOT}/review/sections/). Resolve any path the workflow gives relative to the installed /review SKILL.md directory under ${SHARED_LIBS_ROOT}, so ../qa/sections/<name>.md is ${SHARED_LIBS_ROOT}/qa/sections/<name>.md. The gstack helpers are under ${SHARED_LIBS_ROOT}/bin and ${SHARED_LIBS_ROOT}/lib; the provider wrappers git, gh and curl are under ${f.bin}.
|
- Trusted asset roots: the installed review skill is ${SHARED_LIBS_ROOT}/review (checklist ${SHARED_LIBS_ROOT}/review/checklist.md, sections ${SHARED_LIBS_ROOT}/review/sections/). Resolve any path the workflow gives relative to the installed /review SKILL.md directory under ${SHARED_LIBS_ROOT}, so ../qa/sections/<name>.md is ${SHARED_LIBS_ROOT}/qa/sections/<name>.md. The gstack helpers are under ${SHARED_LIBS_ROOT}/bin and ${SHARED_LIBS_ROOT}/lib; the provider wrappers git, gh and curl are under ${f.bin}.
|
||||||
- Documented helper interfaces, used as-is: \`gstack-review-log --start review\`; \`gstack-review-log --check-shared-libs REVIEW_START\` with the finding on stdin; \`gstack-review-log '<record>' --finish REVIEW_START\`; and \`gstack-review-read\`.
|
- Documented helper interfaces, used as-is: \`gstack-review-log --start review\`; \`gstack-review-log --check-shared-libs REVIEW_START\` with the finding on stdin; \`gstack-review-log '<record>' --finish REVIEW_START\`; and \`gstack-review-read\`.
|
||||||
|
|||||||
@@ -103,7 +103,8 @@ describe('bounded shared-code revalidation prompt', () => {
|
|||||||
'All prior receipts are preserved', 'Source-changing cycles invalidate earlier results',
|
'All prior receipts are preserved', 'Source-changing cycles invalidate earlier results',
|
||||||
'invoke the actor again on the new zero-edit pass', "Never refresh an old receipt's hashes",
|
'invoke the actor again on the new zero-edit pass', "Never refresh an old receipt's hashes",
|
||||||
'Missing, failed, stale or wrong-state results require noncompletion', 'cannot complete core/checklist review',
|
'Missing, failed, stale or wrong-state results require noncompletion', 'cannot complete core/checklist review',
|
||||||
'no actual native coverage credit']) expect(prompt).toContain(rule);
|
'only a current settled:true actor result from the final pass supplies the replaced Step 4.7 QA and Step 4.8 native adversarial prerequisites',
|
||||||
|
'a reporting label, not a missing stage', 'no actual native coverage credit']) expect(prompt).toContain(rule);
|
||||||
expect(prompt).not.toContain('Required reviewer coverage for this scoped replay');
|
expect(prompt).not.toContain('Required reviewer coverage for this scoped replay');
|
||||||
expect(prompt).not.toContain('Do not edit target source');
|
expect(prompt).not.toContain('Do not edit target source');
|
||||||
});
|
});
|
||||||
@@ -237,7 +238,7 @@ describe('bounded shared-code revalidation prompt', () => {
|
|||||||
expect(await invoke()).toBe(result);
|
expect(await invoke()).toBe(result);
|
||||||
expect(calls).toEqual([[f, 'shared-libs-review-revalidation', reviewRevalidationPrompt(f, instructions, input, resumed), 'skip', { attempt, prerequisiteSource: 'synthetic-fixture-input' }]]);
|
expect(calls).toEqual([[f, 'shared-libs-review-revalidation', reviewRevalidationPrompt(f, instructions, input, resumed), 'skip', { attempt, prerequisiteSource: 'synthetic-fixture-input' }]]);
|
||||||
const lifecycle = source.slice(source.indexOf("test('shared-libs-review-lifecycle'"), source.indexOf("test('shared-libs-review-revalidation'"));
|
const lifecycle = source.slice(source.indexOf("test('shared-libs-review-lifecycle'"), source.indexOf("test('shared-libs-review-revalidation'"));
|
||||||
expect(lifecycle).toContain('reviewPrompt(f, instructions, input, stageActor)');
|
expect(lifecycle).toContain('reviewPrompt(f, instructions, input, stageActor, seed)');
|
||||||
expect(lifecycle).not.toContain('reviewRevalidationPrompt(');
|
expect(lifecycle).not.toContain('reviewRevalidationPrompt(');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ function runner(f: fixtures.SharedLibsFixture, mode: string, observations: any[]
|
|||||||
events.push({ type: 'user', message: { content: [{ type: 'tool_result', tool_use_id: id, content: 'written' }] } });
|
events.push({ type: 'user', message: { content: [{ type: 'tool_result', tool_use_id: id, content: 'written' }] } });
|
||||||
};
|
};
|
||||||
const actor = session.stageActor;
|
const actor = session.stageActor;
|
||||||
await invoke(`cat ${fixtures.shellQuote(path.join(fixtures.SHARED_LIBS_ROOT, 'review/checklist.md'))} src/retry-worker.ts src/retry-route.ts lib/retry-after.ts`);
|
await invoke(`cat ${fixtures.shellQuote(path.join(fixtures.SHARED_LIBS_ROOT, 'review/checklist.md'))}${mode === 'unread observation' ? '' : ' ' + fixtures.shellQuote(path.join(f.root, 'pass1-observation.md'))}`);
|
||||||
const initial = mode === 'missing' ? undefined : await invoke(actor.actorCommand, true);
|
const initial = mode === 'missing' ? undefined : await invoke(actor.actorCommand, true);
|
||||||
const question = mode === 'post-fix verification' ? structuredClone(stageScope.decision.call.input) : { questions: [{ question: 'Apply the supported shared-code advisory?', options: [
|
const question = mode === 'post-fix verification' ? structuredClone(stageScope.decision.call.input) : { questions: [{ question: 'Apply the supported shared-code advisory?', options: [
|
||||||
{ label: 'Fix as recommended', description: 'Replace both inline parsers with the existing helper.' },
|
{ label: 'Fix as recommended', description: 'Replace both inline parsers with the existing helper.' },
|
||||||
@@ -76,7 +76,8 @@ function runner(f: fixtures.SharedLibsFixture, mode: string, observations: any[]
|
|||||||
expect(verification).toContain('0 fail');
|
expect(verification).toContain('0 fail');
|
||||||
expect(verification).toContain(`worker===lib ${approved} route===lib ${approved} sample 42 5`);
|
expect(verification).toContain(`worker===lib ${approved} route===lib ${approved} sample 42 5`);
|
||||||
}
|
}
|
||||||
const token = await invoke(`${fixtures.shellQuote(path.join(fixtures.SHARED_LIBS_ROOT, 'bin/gstack-review-log'))} --start review`);
|
const started = await invoke(`${fixtures.shellQuote(path.join(fixtures.SHARED_LIBS_ROOT, 'bin/gstack-review-log'))} --start review`);
|
||||||
|
const token = mode === 'seeded token' ? fs.readFileSync(path.join(f.root, 'pass1-observation.md'), 'utf8').match(/pass 1 REVIEW_START ([0-9a-f-]{36})/)![1] : started;
|
||||||
await invoke('git diff origin/main && cat src/retry-worker.ts src/retry-route.ts lib/retry-after.ts');
|
await invoke('git diff origin/main && cat src/retry-worker.ts src/retry-route.ts lib/retry-after.ts');
|
||||||
const finding = { severity: 'INFORMATIONAL', confidence: 9, advisory: true, category: 'shared-libs',
|
const finding = { severity: 'INFORMATIONAL', confidence: 9, advisory: true, category: 'shared-libs',
|
||||||
path: 'src/retry-worker.ts', line: 1, summary: 'Use the established retry contract',
|
path: 'src/retry-worker.ts', line: 1, summary: 'Use the established retry contract',
|
||||||
@@ -155,7 +156,7 @@ function lifecycle(mode: string) {
|
|||||||
let registered: () => Promise<void>;
|
let registered: () => Promise<void>;
|
||||||
new Function('deps', `const { test, captures, fs, path, expect, CAPTURE_LONG_MS, createHash, sharedLibsFingerprint,
|
new Function('deps', `const { test, captures, fs, path, expect, CAPTURE_LONG_MS, createHash, sharedLibsFingerprint,
|
||||||
createSharedLibsFixture, seedReviewSources, reviewLifecycleInstructions, specialistFixture, createLifecyclePrerequisiteActor,
|
createSharedLibsFixture, seedReviewSources, reviewLifecycleInstructions, specialistFixture, createLifecyclePrerequisiteActor,
|
||||||
reviewPrompt, runSharedInteractive, reviewRecords, toolCommandTrace } = deps;
|
seedLifecycleFirstPass, reviewPrompt, runSharedInteractive, reviewRecords, toolCommandTrace } = deps;
|
||||||
${transpile(record + '\n' + registration)}`)({
|
${transpile(record + '\n' + registration)}`)({
|
||||||
...fixtures, createHash, sharedLibsFingerprint, path, expect, CAPTURE_LONG_MS, createLifecyclePrerequisiteActor,
|
...fixtures, createHash, sharedLibsFingerprint, path, expect, CAPTURE_LONG_MS, createLifecyclePrerequisiteActor,
|
||||||
fs: { ...fs, rmSync: (root: string) => { expect(roots).toContain(root); } },
|
fs: { ...fs, rmSync: (root: string) => { expect(roots).toContain(root); } },
|
||||||
@@ -168,7 +169,12 @@ function lifecycle(mode: string) {
|
|||||||
} },
|
} },
|
||||||
createSharedLibsFixture: (name: string) => { const f = fixtures.createSharedLibsFixture(name); roots.push(f.root); return f; },
|
createSharedLibsFixture: (name: string) => { const f = fixtures.createSharedLibsFixture(name); roots.push(f.root); return f; },
|
||||||
runSharedInteractive: async (f: fixtures.SharedLibsFixture, name: string, prompt: string, choose: string, options: any) => {
|
runSharedInteractive: async (f: fixtures.SharedLibsFixture, name: string, prompt: string, choose: string, options: any) => {
|
||||||
expect(prompt).toBe(fixtures.reviewPrompt(f, path.join(f.root, 'review-lifecycle.md'), path.join(f.root, 'specialist-input.jsonl'), options.stageActor));
|
const start = fs.readFileSync(path.join(f.root, 'pass1-observation.md'), 'utf8').match(/REVIEW_START ([0-9a-f-]{36}) \(started_at ([^)]+)\)/)!;
|
||||||
|
const seed = { token: start[1], startedAt: start[2], observation: path.join(f.root, 'pass1-observation.md'),
|
||||||
|
startWtree: JSON.parse(fs.readFileSync(path.join(f.state, 'projects/fixture-shared-libs/.review-starts', `${start[1]}.json`), 'utf8')).wtree,
|
||||||
|
diffBase: fixtures.fixtureGit(f, 'rev-parse', 'origin/main') };
|
||||||
|
expect(prompt).toBe(fixtures.reviewPrompt(f, path.join(f.root, 'review-lifecycle.md'), path.join(f.root, 'specialist-input.jsonl'), options.stageActor, seed));
|
||||||
|
expect(prompt).toContain(`pass 1 REVIEW_START ${seed.token}`);
|
||||||
if (mode === 'post-fix verification') {
|
if (mode === 'post-fix verification') {
|
||||||
expect(prompt).toContain('replaces the entire Step 4.7 QA and Step 4.8 native adversarial stages');
|
expect(prompt).toContain('replaces the entire Step 4.7 QA and Step 4.8 native adversarial stages');
|
||||||
expect(prompt).toContain('Existing tests and caller/import checks needed to verify your source fixes still run');
|
expect(prompt).toContain('Existing tests and caller/import checks needed to verify your source fixes still run');
|
||||||
@@ -228,6 +234,49 @@ test.each(['missing', 'no-call', 'stale', 'relabeled old', 'late edit', 'edited
|
|||||||
}
|
}
|
||||||
}, 30_000);
|
}, 30_000);
|
||||||
|
|
||||||
|
test.each([['seeded token', '"changed"'], ['unread observation', 'toBe(expected)']])(
|
||||||
|
'the registered lifecycle callbacks reject a final record that relies on the %s', async (mode, message) => {
|
||||||
|
const adapter = lifecycle(mode);
|
||||||
|
await expect(adapter.invoke()).rejects.toThrow(message);
|
||||||
|
expect(adapter.rows).toHaveLength(2);
|
||||||
|
for (const { row } of adapter.rows) expect(row.passed).toBe(false);
|
||||||
|
}, 30_000);
|
||||||
|
|
||||||
|
test('the seeded first pass takes a real unused start before its reads and cannot finish an edited candidate', () => {
|
||||||
|
const f = fixtures.createSharedLibsFixture('seeded-pass');
|
||||||
|
roots.push(f.root);
|
||||||
|
fixtures.seedReviewSources(f);
|
||||||
|
const source = () => Object.fromEntries(Object.entries(fixtures.snapshotFixture(f.repo)).filter(([file]) => !file.startsWith('.git/objects')));
|
||||||
|
const repoBefore = source();
|
||||||
|
const seed = fixtures.seedLifecycleFirstPass(f);
|
||||||
|
expect(source()).toEqual(repoBefore);
|
||||||
|
const start = JSON.parse(fs.readFileSync(path.join(f.state, 'projects/fixture-shared-libs/.review-starts', `${seed.token}.json`), 'utf8'));
|
||||||
|
expect(start).toMatchObject({ skill: 'review', branch: 'feature/a', wtree: fixtures.fixtureWorkingTree(f), started_at: seed.startedAt });
|
||||||
|
expect(seed.diffBase).toBe(fixtures.fixtureGit(f, 'rev-parse', 'origin/main'));
|
||||||
|
const observation = fs.readFileSync(seed.observation, 'utf8');
|
||||||
|
expect(fs.statSync(seed.observation).mode & 0o777).toBe(0o600);
|
||||||
|
expect(observation.indexOf(`pass 1 REVIEW_START ${seed.token}`)).toBeLessThan(observation.indexOf('## git diff'));
|
||||||
|
expect(observation).toContain('+const unusedRetryDiagnostic = "unused";');
|
||||||
|
expect(observation).toContain('NO_REVIEWS');
|
||||||
|
for (const relative of ['src/retry-worker.ts', 'src/retry-route.ts', 'lib/retry-after.ts', 'src/scheduler.ts', 'test/retry-after.test.ts']) {
|
||||||
|
expect(observation).toContain(`### ${relative} (`);
|
||||||
|
expect(observation).toContain(createHash('sha256').update(fs.readFileSync(path.join(f.repo, relative))).digest('hex'));
|
||||||
|
}
|
||||||
|
expect(() => fixtures.seedLifecycleFirstPass(f)).toThrow('already seeded');
|
||||||
|
const instructions = path.join(f.root, 'review-lifecycle.md'), input = path.join(f.root, 'specialist-input.jsonl');
|
||||||
|
expect(() => fixtures.reviewPrompt(f, instructions, input, undefined, seed)).toThrow('edit-capable lifecycle replay');
|
||||||
|
const seeded = fixtures.reviewPrompt(f, instructions, input, { actorCommand: 'cat /fx/current.json' }, seed);
|
||||||
|
for (const value of [seed.observation, seed.token, seed.diffBase, 'never finish it', 'Pass 2 executes Step 3 itself',
|
||||||
|
'run --start as the sole command', 'supplies the replaced Step 4.7 QA and Step 4.8 native adversarial prerequisites']) expect(seeded).toContain(value);
|
||||||
|
expect(fixtures.reviewPrompt(f, instructions, input, { actorCommand: 'cat /fx/current.json' })).not.toContain('Fixture-seeded pass 1');
|
||||||
|
fixtures.fixtureWrite(f, 'src/retry-worker.ts', fs.readFileSync(path.join(f.repo, 'src/retry-worker.ts'), 'utf8').replace('const unusedRetryDiagnostic = "unused";\n', ''));
|
||||||
|
const env = { ...process.env, ...f.env, PATH: process.env.PATH, GSTACK_HOME: f.state };
|
||||||
|
execFileSync(path.join(fixtures.SHARED_LIBS_ROOT, 'bin/gstack-review-log'), [JSON.stringify({ skill: 'review', status: 'clean',
|
||||||
|
issues_found: 0, critical: 0, informational: 0, findings: [], completed: true, converged: true }), '--finish', seed.token],
|
||||||
|
{ cwd: f.repo, env, encoding: 'utf8', timeout: 30_000 });
|
||||||
|
expect(fixtures.reviewRecords(f).at(-1).review_binding).toMatchObject({ state: 'changed', started_at: seed.startedAt, start_wtree: seed.startWtree });
|
||||||
|
});
|
||||||
|
|
||||||
test('the actor helper selects both existing neighboring native bodies', () => {
|
test('the actor helper selects both existing neighboring native bodies', () => {
|
||||||
for (const file of ['test/helpers/shared-libs-path-fixture.ts']) {
|
for (const file of ['test/helpers/shared-libs-path-fixture.ts']) {
|
||||||
const selected = selectTests([file], E2E_TOUCHFILES, GLOBAL_TOUCHFILES).selected;
|
const selected = selectTests([file], E2E_TOUCHFILES, GLOBAL_TOUCHFILES).selected;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
installHostileGitConfig, installInterpreterCanary, installNormalizingFilter,
|
installHostileGitConfig, installInterpreterCanary, installNormalizingFilter,
|
||||||
isGuardedGitRequest, isInternalClaudeGitRequest,
|
isGuardedGitRequest, isInternalClaudeGitRequest,
|
||||||
installSourceShims, readRequests, reviewLifecycleInstructions, reviewPrompt, reviewRevalidationPrompt,
|
installSourceShims, readRequests, reviewLifecycleInstructions, reviewPrompt, reviewRevalidationPrompt,
|
||||||
reviewRecords, runSharedCapture, runSharedInteractive, seedOpportunitySources,
|
reviewRecords, runSharedCapture, runSharedInteractive, seedLifecycleFirstPass, seedOpportunitySources,
|
||||||
seedReviewSources, seedSkippedAdvisory, snapshotFixture, specialistFixture,
|
seedReviewSources, seedSkippedAdvisory, snapshotFixture, specialistFixture,
|
||||||
sharedReadOnlyViolations, standaloneInstructions, toolCommandTrace, type SharedLibsFixture,
|
sharedReadOnlyViolations, standaloneInstructions, toolCommandTrace, type SharedLibsFixture,
|
||||||
SharedCaptureAccumulator, type SharedCaptureAttempt, SHARED_LIBS_ROOT,
|
SharedCaptureAccumulator, type SharedCaptureAttempt, SHARED_LIBS_ROOT,
|
||||||
@@ -155,9 +155,10 @@ describeE2E('Shared-code safety and review lifecycle (gate)', () => {
|
|||||||
const instructions = reviewLifecycleInstructions(f);
|
const instructions = reviewLifecycleInstructions(f);
|
||||||
const input = specialistFixture(f);
|
const input = specialistFixture(f);
|
||||||
const stageActor = createLifecyclePrerequisiteActor(f);
|
const stageActor = createLifecyclePrerequisiteActor(f);
|
||||||
|
const seed = seedLifecycleFirstPass(f);
|
||||||
let questions: any[] = [];
|
let questions: any[] = [];
|
||||||
await recordCapture(attempt, choose, 'shared-libs-review-lifecycle', async () => {
|
await recordCapture(attempt, choose, 'shared-libs-review-lifecycle', async () => {
|
||||||
const capture = await runSharedInteractive(f, 'shared-libs-review-lifecycle', reviewPrompt(f, instructions, input, stageActor), choose, { stageActor, attempt });
|
const capture = await runSharedInteractive(f, 'shared-libs-review-lifecycle', reviewPrompt(f, instructions, input, stageActor, seed), choose, { stageActor, attempt });
|
||||||
questions = capture.questions;
|
questions = capture.questions;
|
||||||
return capture.result;
|
return capture.result;
|
||||||
}, result => {
|
}, result => {
|
||||||
@@ -174,6 +175,11 @@ describeE2E('Shared-code safety and review lifecycle (gate)', () => {
|
|||||||
completed: true, converged: true });
|
completed: true, converged: true });
|
||||||
expect(last.review_binding.state).toBe('verified');
|
expect(last.review_binding.state).toBe('verified');
|
||||||
expect(last.review_binding.branch_id).toBe(createHash('sha256').update('feature/a').digest('hex'));
|
expect(last.review_binding.branch_id).toBe(createHash('sha256').update('feature/a').digest('hex'));
|
||||||
|
// The seeded pass-1 token stays unused; the model's own post-edit start binds the final record.
|
||||||
|
expect(last.review_binding.started_at).not.toBe(seed.startedAt);
|
||||||
|
expect(last.review_binding.start_wtree).not.toBe(seed.startWtree);
|
||||||
|
expect(result.toolCalls.some((call: any) => call.tool === 'Read' && call.input?.file_path === seed.observation)
|
||||||
|
|| toolCommandTrace(result).some(command => command.includes(seed.observation))).toBe(true);
|
||||||
// Earlier cycles cannot stand in for the final zero-edit record. Both
|
// Earlier cycles cannot stand in for the final zero-edit record. Both
|
||||||
// the explicit choice and its original identity must survive there.
|
// the explicit choice and its original identity must survive there.
|
||||||
const findings = last.findings || [];
|
const findings = last.findings || [];
|
||||||
|
|||||||
Reference in new issue
Block a user