mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 00:03:15 +01:00
Merge remote-tracking branch 'origin/main' into capy/fl2-F1
This commit is contained in:
commit
78fbd0ba32
9 files changed
+180
-15
No files matched your search
@@ -700,7 +700,7 @@ export function qaCallerSessionOptions(fixture: QaCallerFixture, runId: string):
|
||||
prompt: `Load gstack's /${fixture.caller} supplied parent phase from caller-${fixture.caller}.md and resume it on the selected working-tree diff against origin/main. This excerpt comes from ${fixture.runtime}/${fixture.caller}/SKILL.md; resolve installed-relative references there, not from the excerpt file or product directory. That path identifies the asset base, not another entrypoint: do not read or invoke the full parent SKILL.md or rerun its preamble. Earlier preamble/branch/base setup is complete; use the existing local origin/main ref without fetch. Earlier-phase asset locators are ${fixture.runtime}/review/checklist.md and ${fixture.runtime}/qa/templates/functional-report-template.md. Read those files directly when referenced; recursive Glob does not follow the installed asset symlinks. Cross-project learnings are configured off in this owned fixture. ${fixture.reviewStart ? `The actual review-start helper already captured REVIEW_START=${fixture.reviewStart} for this unchanged core pass; retain that token. ` : ''} This fixture evaluates only the supplied parent phase, not later publication stages. The fixture owner recorded these read-only observations when this phase began; they are current until an input changes, so use them instead of re-running those commands, and recheck freshness before completion outputs:\n${fixture.startObservations}\n Read README.md for the project contract and commands. Use diagnostic-client commands such as \`bun scripts/probe.ts <literal>\` for exploratory discoveries and their checkpoint evidence. A required \`bun run test\` is separate suite verification: report it as verification, never as a diagnostic observation or checkpoint anchor/target. Use the production evidence helper to publish each diagnostic checkpoint as \`reports/exploration-NNN.json\`, not inside a nested directory; do not transcribe its observed payload. ${fixture.caseId === 'ship-exploratory-plan-checks' ? 'The previously discovered plan is PLAN.md.' : 'No plan file was found.'} There is no remote service and no release publication is authorized. There is no interactive approver; do not invent answers or permission. Keep normal parent decision gates. Before the completion report and each completed:true review record, read HANDOFF.md and reports/HANDOFF.md if present for any concurrent collaborator update, await the results, and compare evidence with current inputs. If a probe's snapshot differs from an earlier probe's, read reports/HANDOFF.md first; it names any collaborator change. A gstack-review-log completed:true record is a completion, not preliminary bookkeeping; a later handoff read cannot validate an earlier completion.\n\nDeadline bookkeeping additionally permits \`bun ${fixture.runtime}/bin/gstack-qa-deadline start ${fixture.cwd}/reports/deadline.json SECONDS [EARLIER_UTC]\`, \`bun ${fixture.runtime}/bin/gstack-qa-deadline status ${fixture.cwd}/reports/deadline.json\`, and \`bun ${fixture.runtime}/bin/gstack-qa-deadline run ${fixture.cwd}/reports/deadline.json -- bun scripts/probe.ts [literal]\`. These are closed literal forms: SECONDS must be positive and at most 300, EARLIER_UTC is the optional caller absolute deadline: use the section clock's Hard deadline UTC, never its Runner entry UTC, reserve-start time or a clock-read time. The child is only the existing diagnostic client with zero or one literal argument. Resolve these exact helper and state paths; do not use variables, another helper, another state file, nested wrappers, scripts, operators or substitutions. Only this helper may create or change reports/deadline.json and its .qa-deadline- temporary files; never use Write/Edit/MultiEdit on those paths. Record the full outer run command in checkpoints and evidence; keep the unchanged child JSON as observed, separate from prefixed guard diagnostics. A completed expired guard-run is not a probe or a pass: retain its unused checkpoint, report not-run coverage and do not restart the deadline. Keep the 12-probe smoke limit. Required suites and explicit plan checks are outside the bounded smoke budget, not permission to reset it.\n\nFunctional evidence uses the same production helper and existing diagnostic client: \`bun ${fixture.runtime}/bin/gstack-qa-evidence capture ${fixture.cwd}/reports NNN --public --deadline ${fixture.cwd}/reports/deadline.json -- bun scripts/probe.ts [literal]\`. These diagnostic receipts are declared public/synthetic, so --public is approved; a fresh three-digit ID is required each time. Explicit plan probes outside the smoke budget may replace --deadline with --timeout-ms 10000; this does not reset or bypass the smoke deadline. After the first capture, put causal intent in the next capture itself by adding \`--after PREV --hypothesis 'causal hypothesis'\` before \`--\` (PREV = the most recent complete capture ID); the helper publishes reports/exploration-NNN.json linking capture PREV's observation to that probe before running it, so one call is both checkpoi
Line truncated
|
||||
appendSystemPrompt: `Caller execution scheduling (fixture contract):
|
||||
This session has at most 25 assistant turns, including required verification and final artifacts. The command boundary applies to each Bash call, not to the number of independent tool calls in an assistant turn.
|
||||
After required clock and approval prerequisites settle, issue independent source Reads and read-only discovery together as separate native tool calls once their paths and inputs are known. For example, after the first clock read, one response can Read the caller excerpt, README.md, every product file and the referenced checklist and templates. Wait for their results before decisions that depend on them.
|
||||
After required clock and approval prerequisites settle, issue independent source Reads and read-only discovery together as separate native tool calls once their paths and inputs are known. Read-only discovery is Read, Glob, Grep and the declared inventory, Git and review-read forms; a capture, a deadline guard start or run, or a diagnostic-client run is a probe, never discovery. For example, after the first clock read, one response can Read the caller excerpt, README.md, every product file and the referenced checklist and templates. Wait for their results before decisions that depend on them.
|
||||
The completion reserve is for required verification, affected-input revalidation and artifacts, not an earlier deadline. Keep completing required work within the actual remaining deadline; reserve entry alone is not a reason to stop. Use each native probe's snapshot to distinguish current from superseded evidence before deciding which checks still need revalidation.
|
||||
Never group diagnostic probes, checkpoint publication with its next probe (a capture carrying --after is one probe, not a group), or any action with the clock/status/approval result it needs. Shell composition remains forbidden outside the declared forms. Preserve every required Read, probe, verification, freshness check, approval and report field; the turn limit does not authorize skipping work or reporting incomplete work as passed.`,
|
||||
workingDirectory: fixture.cwd,
|
||||
|
||||
@@ -108,6 +108,53 @@ describe('caller native-event observer controls', () => {
|
||||
['exploratory', 'system-functional'].map(id => [id, ready.find(tool => String(tool.input.file_path).endsWith(`/qa/sections/${id}.md`))] as const))).toEqual([]);
|
||||
});
|
||||
|
||||
test('census 38212265208: a probe batched with the caller section Read is flagged, and the scheduling contract never calls a probe discovery', async () => {
|
||||
// Native order of census 38212265208 (gate-census-6, ship-exploratory-late-input): provider
|
||||
// message, tool, input; outputs abbreviated. Capture 001 shares a response with the Step 9 section Read.
|
||||
const R = '/home/runner/.cache/gstack-paid-shard-iHlpIM/tmp/qc-cALQYz';
|
||||
const capture = (id: string, extra = '') => `bun ${R}/host/runtime/bin/gstack-qa-evidence capture ${R}/product/reports ${id} --public --deadline ${R}/product/reports/deadline.json ${extra}-- bun scripts/probe.ts`;
|
||||
const replay: Array<[string, string, Record<string, string>, string]> = [
|
||||
['YvqEApde', 'Read', { file_path: `${R}/host/runtime/ship/sections/plan-completion.md` }, '## Step 8: Plan Completion Audit'],
|
||||
['YvqEApde', 'Read', { file_path: `${R}/host/runtime/ship/sections/review-army.md` }, '## Step 9: Pre-Landing Review'],
|
||||
['YvqEApde', 'Bash', { command: `${capture('001')} 3` }, '{"id":"probe-b5ae2d9c-af87-4e8a-9aab-76972f826079","charter":"happy","input":"3"}'],
|
||||
['w9Gt4kmL', 'Read', { file_path: `${R}/host/runtime/qa/sections/exploratory.md` }, '# Shared exploratory QA'],
|
||||
['w9Gt4kmL', 'Read', { file_path: `${R}/host/runtime/bin/gstack-qa-evidence` }, '#!/usr/bin/env bun'],
|
||||
['w9Gt4kmL', 'Bash', { command: `${R}/host/runtime/bin/gstack-review-read` }, 'NO_REVIEWS'],
|
||||
['w9Gt4kmL', 'Bash', { command: `${R}/host/runtime/bin/gstack-specialist-stats` }, 'SPECIALIST_STATS: 0 reviews analyzed'],
|
||||
['cba4VKcQ', 'Bash', { command: `${capture('002', "--after 001 --hypothesis 'upper bound 9 should still print 18' ")} 9` }, '{"id":"probe-baf59bfd-ac54-4a4a-8bec-2cd416154bbf","charter":"plan:nine","input":"9"}'],
|
||||
['cba4VKcQ', 'Read', { file_path: `${R}/host/runtime/qa/sections/scope.md` }, '### Select the surface before setup'],
|
||||
['cba4VKcQ', 'Read', { file_path: `${R}/host/runtime/qa/sections/system-functional.md` }, '# Functional QA with repository-native tools'],
|
||||
];
|
||||
const replayTools = (rows: typeof replay) => callerTools(rows.flatMap(([message, name, input, output], i) =>
|
||||
nativeCall(`toolu_${i}`, name, input, output).map((event: any) => event.type === 'assistant'
|
||||
? { ...event, session_id: 'census-38212265208', message: { ...event.message, id: `msg_${message}` } } : event)));
|
||||
const probeErrors = (tools: ReturnType<typeof callerTools>) => tools.filter(tool => / capture /.test(String(tool.input.command)))
|
||||
.flatMap(probe => callerProbeReadErrors(probe, ['exploratory', 'system-functional']
|
||||
.map(id => [id, tools.find(tool => String(tool.input.file_path).endsWith(`/qa/sections/${id}.md`))] as const)));
|
||||
const tools = replayTools(replay);
|
||||
const section = tools.find(tool => String(tool.input.file_path).endsWith('/ship/sections/review-army.md'))!;
|
||||
expect(tools.find(tool => / capture /.test(String(tool.input.command)))!.messageId).toBe(section.messageId);
|
||||
expect(probeErrors(tools)).toEqual([
|
||||
'probe preceded resource read: exploratory', 'probe preceded resource read: system-functional',
|
||||
'probe preceded resource read: system-functional',
|
||||
]);
|
||||
const isProbe = ([, , input]: (typeof replay)[number]) => / capture /.test(input.command ?? '');
|
||||
expect(probeErrors(replayTools([...replay.filter(row => !isProbe(row)),
|
||||
...replay.filter(isProbe).map(([message, ...rest]) => [`${message}-after-reads`, ...rest] as (typeof replay)[number])]))).toEqual([]);
|
||||
|
||||
const fixture = createQaCallerFixture('ship-exploratory-late-input');
|
||||
try {
|
||||
await runQaCaller(fixture, 'free-read-probe-contract', async options => {
|
||||
const contract = options.appendSystemPrompt!;
|
||||
const discovery = contract.match(/Read-only discovery is [^.]*;([^.]*)\./);
|
||||
expect(discovery?.[1]).toMatch(/capture/);
|
||||
expect(discovery?.[1]).toMatch(/guard start/);
|
||||
expect(discovery?.[1]).toMatch(/never discovery/);
|
||||
return { exitReason: 'error_max_turns', transcript: [] } as unknown as SkillTestResult;
|
||||
});
|
||||
} finally { await fixture.close(); fs.rmSync(fixture.root, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test.each(['pending-result', 'same-event', 'same-message-id'])('method Read %s cannot authorize a dependent probe', ordering => {
|
||||
const observed = evidence();
|
||||
const events = observed.result.transcript as any[];
|
||||
|
||||
@@ -2,6 +2,7 @@ import { expect, test } from 'bun:test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
|
||||
@@ -158,3 +159,62 @@ afterAll(async () => {
|
||||
}
|
||||
}, 10_000);
|
||||
}
|
||||
|
||||
// review-army-simplification headroom (#3153): the session's premise matches its staged
|
||||
// diff and the fixture stages only the Review Army section and its one checklist.
|
||||
test.each(['structure-finding', 'no-structure-finding', 'disavowed-frame'])
|
||||
('review-army-simplification registered session contract: %s', scenario => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'simplification-contract-'));
|
||||
const facts = path.join(dir, 'facts.json');
|
||||
const script = path.join(dir, 'callback.test.ts');
|
||||
fs.writeFileSync(script, `
|
||||
import {describe,mock,test} from 'bun:test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import {spawnSync} from 'node:child_process';
|
||||
const root=${JSON.stringify(ROOT)},scenario=${JSON.stringify(scenario)},facts=${JSON.stringify(facts)};
|
||||
const actual=await import(path.join(root,'test/helpers/session-runner.ts'));
|
||||
let capturedRun={};
|
||||
mock.module(path.join(root,'test/helpers/e2e-helpers.ts'),()=>({
|
||||
ROOT:root,runId:'simplification-contract',
|
||||
describeIfSelected:(name,ids,body)=>{if(ids.includes('review-army-simplification'))describe(name,body);},
|
||||
testConcurrentIfSelected:(id,body,timeout)=>{if(id==='review-army-simplification')test(id,body,timeout);},
|
||||
logCost(){},createEvalCollector:()=>null,finalizeEvalCollector:async()=>{},
|
||||
recordE2E:(_collector,_name,_suite,result)=>fs.writeFileSync(facts,JSON.stringify({...capturedRun,exitReason:result.exitReason})),
|
||||
}));
|
||||
mock.module(path.join(root,'test/helpers/session-runner.ts'),()=>({...actual,runSkillTest:async options=>{
|
||||
const cwd=options.workingDirectory;
|
||||
const shortstat=spawnSync('git',['diff','--shortstat','main...HEAD'],{cwd,encoding:'utf8',timeout:5000}).stdout;
|
||||
const staged=spawnSync('git',['ls-files','--others','--exclude-standard'],{cwd,encoding:'utf8',timeout:5000}).stdout.split('\\n').filter(Boolean).sort();
|
||||
capturedRun={prompt:options.prompt,timeout:options.timeout,maxTurns:options.maxTurns,staged,
|
||||
diffLines:[...shortstat.matchAll(/(\\d+) (?:insertion|deletion)/g)].reduce((sum,m)=>sum+Number(m[1]),0)};
|
||||
fs.writeFileSync(path.join(cwd,'review-output.md'),{
|
||||
'structure-finding':'{"category":"native","path":"date_utils.js","line":7} DateFormatter re-implements Intl.DateTimeFormat.',
|
||||
'no-structure-finding':'Simplification: nothing to report.',
|
||||
'disavowed-frame':'{"category":"native"} Lean already. Ship.',
|
||||
}[scenario]);
|
||||
return {exitReason:'success',browseErrors:[],toolCalls:[],transcript:[],output:'Saved.'};
|
||||
}}));
|
||||
await import(path.join(root,'test/skill-e2e-review-army.test.ts'));
|
||||
`);
|
||||
try {
|
||||
const child = spawnSync(process.execPath, ['test', script], {
|
||||
cwd: dir, encoding: 'utf8', timeout: 20_000,
|
||||
env: {PATH: process.env.PATH ?? '', HOME: dir, TMPDIR: dir, NO_COLOR: '1'},
|
||||
});
|
||||
expect(child.status, child.stdout + child.stderr).toBe(scenario === 'structure-finding' ? 0 : 1);
|
||||
expect(fs.existsSync(facts), child.stdout + child.stderr).toBe(true);
|
||||
const observed = JSON.parse(fs.readFileSync(facts, 'utf8'));
|
||||
expect(observed.timeout).toBe(180_000);
|
||||
expect(observed.maxTurns).toBe(20);
|
||||
expect(observed.diffLines).toBe(68);
|
||||
expect(observed.prompt).toContain(`DIFF_LINES: ${observed.diffLines}`);
|
||||
expect(observed.prompt).not.toMatch(/over 100 lines/i);
|
||||
expect(observed.prompt).toContain('/review --simplification');
|
||||
expect(observed.staged).toEqual(['review-army.md', 'review-specialists/simplification.md']);
|
||||
expect(observed.prompt).toContain('at most 30 lines');
|
||||
expect(observed.prompt).toContain('brief acknowledgement');
|
||||
} finally {
|
||||
fs.rmSync(dir, {recursive: true, force: true});
|
||||
}
|
||||
});
|
||||
@@ -752,6 +752,7 @@ Write ${dir}/review-output.md with only the selection line, the SPECIALIST REVIE
|
||||
|
||||
describeIfSelected('Review Army: Simplification activation', ['review-army-simplification'], () => {
|
||||
let dir: string;
|
||||
let observations: string;
|
||||
|
||||
beforeAll(() => {
|
||||
const repo = setupRepo('army-simplification');
|
||||
@@ -769,24 +770,27 @@ describeIfSelected('Review Army: Simplification activation', ['review-army-simpl
|
||||
repo.run('git', ['add', '.']);
|
||||
repo.run('git', ['commit', '-m', 'add date utils']);
|
||||
|
||||
copyReviewFiles(dir);
|
||||
observations = stageReviewArmySession(dir, `SCOPE_FRONTEND=false
|
||||
SCOPE_BACKEND=true
|
||||
SCOPE_PROMPTS=false
|
||||
SCOPE_TESTS=false
|
||||
SCOPE_DOCS=false
|
||||
SCOPE_CONFIG=false
|
||||
SCOPE_MIGRATIONS=false
|
||||
SCOPE_API=false
|
||||
SCOPE_AUTH=false`, ['simplification'], ['simplification']);
|
||||
});
|
||||
|
||||
afterAll(() => { try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} });
|
||||
|
||||
testConcurrentIfSelected('review-army-simplification', async () => {
|
||||
const result = await runSkillTest({
|
||||
prompt: `You are in a git repo on a feature branch that adds a JS utility file.
|
||||
Read review-SKILL.md for instructions. Also read review-checklist.md.
|
||||
The specialist checklists are in review-specialists/ (testing.md, simplification.md, etc.).
|
||||
prompt: `You are the /review parent on branch feature/date-utils, a JS utility change. The caller invoked /review --simplification.
|
||||
${reviewArmyScope(observations, 'its Step 4.6 merge')}
|
||||
Selection: --simplification force-includes the Simplification specialist; the recorded DIFF_LINES is below its size gate, so only the flag selects it. Dispatch no other specialist.
|
||||
The checklist for this capture is review-specialists/simplification.md. Instead of launching a subagent, read it and apply it yourself against the recorded diff.
|
||||
|
||||
Skip the preamble, lake intro, telemetry sections.
|
||||
Run Step 4.5 (Review Army) only.
|
||||
The base branch is main. The diff is over 100 lines, so the Simplification specialist should activate.
|
||||
|
||||
For the specialist dispatch, read review-specialists/simplification.md and apply it against the diff.
|
||||
|
||||
Write your findings to ${dir}/review-output.md`,
|
||||
Write ${dir}/review-output.md with only the selection line and the SPECIALIST REVIEW block with its PR Quality Score and Simplification footer, in at most 30 lines. After saving the report, finish with a brief acknowledgement rather than repeating the findings in the final response.`,
|
||||
workingDirectory: dir,
|
||||
maxTurns: 20,
|
||||
timeout: 180_000,
|
||||
|
||||
Reference in new issue
Block a user