Merge origin/main (v1.91.15.0, #3017) into garrytan/parallel-waves; release metadata v1.91.16.0

Conflicts resolved by hand in templates, tests, CLAUDE.md, TODOS.md,
CHANGELOG.md and bin/gstack-config; generated SKILL.md/sections, the agents
digest and test/fixtures/context-budget.json regenerated. The eval codemod
re-applied to main's new gstack-paths/gstack-slug evals. Coverage-read rule:
bare echo separators after unknown output extend the unknown span (main's
pathspec CI shape). CLAUDE.md keeps the TREE_MUTATING, catalog-ceiling and
detach-timeout facts main's instruction-facts test pins.
This commit is contained in:
garrytan committed 2026-10-03 08:53:18 +00:00
commit 6fb8ea3d10
351 files changed
+7477 -8179

No files matched your search

+13
View File
@@ -42,6 +42,19 @@ export function scoreAuqFormat(text: string): { present: number; total: number;
return { present: AUQ_FORMAT_ELEMENTS.length - missing.length, total: AUQ_FORMAT_ELEMENTS.length, missing };
}
/**
* Format problems that fail a first-question matrix run: only the fields
* software reads (a `Recommendation:` line and exactly one `(recommended)`
* option). ELI10, Pros / cons, ✅/❌ and Net: are reported, not failed.
*/
export function auqMachineFormatProblems(text: string): string[] {
const problems: string[] = [];
if (!/^[*_]*[ \t]*recommendation[ \t]*[*_]*[ \t]*:[*_ \t]*\S/im.test(text)) problems.push('missing Recommendation: line');
const recommended = text.match(/\(recommended\)/gi)?.length ?? 0;
if (recommended !== 1) problems.push(`expected exactly one (recommended) option, found ${recommended}`);
return problems;
}
/**
* Grade recommendation substance ROBUST to the connective. judgeRecommendation()
* keys on the literal "because" (correct for the spec, pinned by
+7 -6
View File
@@ -166,7 +166,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
// wave's headline capability) grows the union to 1.195x. Deliberate:
// the section is on-demand (loads only for Apple store targets), so
// per-invocation cost for non-iOS ships is one manifest line.
maxSizeRatio: 1.404, // Shared advisory identity/dedup + critical-severity validation: 248,065 union bytes / 187,706 baseline = 1.3216 (2026-09-17). + test value bar in the lazy Step 7 section (value cards, weak paths, gate table, base control, machine checks; ~13.6KB): measured 1.396 (2026-09-29). + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.401 (2026-09-30). + the shared QA review step's plan-check timing rule (plan checks and their revalidation run on --timeout-ms after smoke expiry); measured 1.4022 (2026-09-30).
maxSizeRatio: 1.405, // Shared advisory identity/dedup + critical-severity validation: 248,065 union bytes / 187,706 baseline = 1.3216 (2026-09-17). + test value bar in the lazy Step 7 section (value cards, weak paths, gate table, base control, machine checks; ~13.6KB): measured 1.396 (2026-09-29). + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.401 (2026-09-30). + the shared QA review step's plan-check timing rule (plan checks and their revalidation run on --timeout-ms after smoke expiry); measured 1.4022 (2026-09-30). + the shared TEST_BOOTSTRAP owned-change undo rules (s03 H1-H3, M1) and the value-bar coverage wording (s03 M2/M3); measured 1.4044 (2026-10-02).
},
'plan-ceo-review': {
skill: 'plan-ceo-review',
@@ -409,8 +409,9 @@ do not launch the downstream skill or open a browser.`,
// v1.64+v1.65 merge sums both waves' preamble growth; measured 1.073.
// + W1 guarded state-root resolution in the Context Recovery preamble, the
// eureka log, the office-hours lookup and the taste-profile read; measured
// 1.0834 (2026-09-30).
maxSizeRatio: 1.085,
// 1.0834 (2026-09-30). + named design-direction defaults to avoid in the native
// subagent prompt (s02 H4a); measured 1.0911 (2026-10-02).
maxSizeRatio: 1.092,
},
cso: {
skill: 'cso',
@@ -536,7 +537,7 @@ do not launch the downstream skill or open a browser.`,
},
behavioral: 'prompt',
maxSkeletonBytes: 74_500, // + Aside browser contract for Step 7 canary ({{ASIDE_SETUP}}); measured 73_523
maxSizeRatio: 1.10, // + v1.81 Aside contract + gstack-browser fallback block; measured 1.077
maxSizeRatio: 1.108, // + v1.81 Aside contract + gstack-browser fallback block; measured 1.077. + both untrusted-content marker formats in the browser fallback (W7f) + the readiness gate's project test command and live eval-store paths (s06 H6/H7); measured 1.1076 (2026-10-02).
minUnionBytes: 91_000, // Phase 4 wave 1; estimated union ~94.9KB
mustContain: ['readiness', 'merge', 'canary', 'revert', 'staging'],
},
@@ -649,7 +650,7 @@ do not launch the downstream skill or open a browser.`,
'## Phase 7: Triage',
'## Phase 8: Fix Loop',
'8e.5. Regression Test',
'WTF-LIKELIHOOD',
'### 8f. Self-Regulation',
'## Additional Rules (qa-specific)',
'## Output Structure',
],
@@ -667,7 +668,7 @@ do not launch the downstream skill or open a browser.`,
},
behavioral: 'prompt',
maxSkeletonBytes: 63_500, // + v2.0 {{ASIDE_SETUP}}/{{BROWSE_FALLBACK}} (replaces the browse setup block); measured 61_253
maxSizeRatio: 1.103, // + v1.81 Aside contract + gstack-browser fallback block (1.080 on v1.91.7.0) + the shared test value bar at 8a.5 ({{TEST_VALUE_BAR:qa}}); measured 1.094 + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.101 (2026-09-30) + v1.91.12.0 merge of #2999 (await scope/method Reads, capture --after checkpoints, browser-only empty evidence list) with #3002; measured 1.1028 (2026-10-01).
maxSizeRatio: 1.104, // + v1.81 Aside contract + gstack-browser fallback block (1.080 on v1.91.7.0) + the shared test value bar at 8a.5 ({{TEST_VALUE_BAR:qa}}); measured 1.094 + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.101 (2026-09-30) + v1.91.12.0 merge of #2999 (await scope/method Reads, capture --after checkpoints, browser-only empty evidence list) with #3002; measured 1.1028 (2026-10-01). + both untrusted-content marker formats in the browser fallback (W7f); measured 1.1035 (2026-10-02).
minUnionBytes: 69_500, // measured union 70,385
// 'aside repl' pins the Aside contract; '$B goto' pins the fallback block in the always-loaded skeleton.
mustContain: ['bug', 'aside repl', '$B goto', 'fix', 'Health Score Rubric', 'regression'],
+19 -15
View File
@@ -404,6 +404,8 @@ function singleScopeBrief(text: string, descriptions: readonly string[], compari
: /^[^.!?\n]+\.$/.test(net.replace(/\bvs\./gi, 'vs')));
}
const KEEP_LABEL_WORDS = new Set(['keep', 'retain', 'include', 'the', 'and', 'all', 'scope', 'plan', 'planned', 'current', 'now', 'its']);
/** Fixture-owned baseline for a completed scope-preservation decision. */
export interface CeoPostureSource { path: string; content: string }
@@ -419,25 +421,27 @@ function hasCompletedScopePreservation(transcript: PlanCountTranscript, selected
if (selectedPlans.length !== 1 || selectedPlans[0] !== name || plans.length !== 1 || plans[0] !== name ||
!/\bHOLD SCOPE\b/.test(context) || /\b(?:SCOPE EXPANSION|SELECTIVE EXPANSION|SCOPE REDUCTION|historical|previous|example|hypothetical|withdrawn)\b/i.test(context)) return false;
if (q.multiSelect || q.options.length !== 2 || !singleScopeBrief(q.question, q.options.map(o => o.description ?? ''), false)) return false;
const labels = q.options.map(o => o.label.replace(/\s*\(recommended\)\s*$/i, '').trim());
const kept = labels.map(label => /^Keep ([a-z][a-z -]{0,70}) in scope$/i.exec(label));
const index = kept.findIndex(Boolean);
if (index < 0 || kept.filter(Boolean).length !== 1 || call.answers?.[q.question] !== q.options[index]!.label) return false;
const subject = kept[index]![1]!.trim();
// One option keeps an item and the other defers it; the answer selects the
// keep option. Label and question wording are free.
const labels = q.options.map(o => o.label.replace(/\s*\(recommended\)\s*$/i, '').replace(/^[A-B][):.]\s*/i, '').trim());
const keeps = labels.map(label => /\b(?:keep|retain|include)\b/i.test(label) && !/\b(?:defer|todos?|drop|cut)\b/i.test(label));
const index = keeps.indexOf(true);
if (index < 0 || keeps.filter(Boolean).length !== 1 || !/\b(?:defer|todos?|later|drop|cut)\b/i.test(labels[1 - index]!) ||
call.answers?.[q.question] !== q.options[index]!.label) return false;
const escape = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
if (!new RegExp(`^Defer ${escape(subject)} to TODOS(?:\\.md)?$`, 'i').test(labels[1-index]!) ||
!new RegExp(`^D\\d+\\s*[—–-]\\s*Keep (?:the )?${escape(subject)}\\b[^?\\n]* in scope, or defer`, 'i').test(q.question)) return false;
const body = q.question + '\n' + q.options[index]!.description;
if (!q.options.every(o => typeof o.description === 'string' && o.description.trim()) ||
/\b(?:also|additionally|separately|expand(?:ing)? scope|outside (?:the )?(?:plan|scope)|and add|plus new|withdrawn|cancelled|canceled|retracted|revoked|hypothetical)\b/i.test(body)) return false;
// The choice retains an actual baseline requirement and names concrete
// failure/proof consequences, rather than merely repeating the mode label.
/\b(?:also|additionally|separately|expand(?:ing)? scope|outside (?:the )?(?:plan|scope)|and add|plus new|withdrawn|cancelled|canceled|retracted|revoked|hypothetical|historical)\b/i.test(body) ||
/^\s*(?:for )?example\b/i.test(q.question)) return false;
// The kept item is an actual baseline requirement of the plan, and the brief
// explains its consequences rather than merely repeating the mode label.
const required = source.content.split('\n').filter(line => /^\s*-\s+/.test(line));
if (!required.some(line => new RegExp(`\\b${escape(subject)}\\b`, 'i').test(line)) ||
!new RegExp(`(?:${escape(name)} lists|the plan already states)\\b`, 'i').test(q.question)) return false;
if ([/\btests?\b/i, /\bauthz\b|\baccess\b/i, /\b(?:concurrent|duplicate|stale)\b/i,
/\b(?:delete.and.recreate|delete\+recreate|re-save)\b/i, /\b(?:pilot|reuse) metric\b/i]
.filter(pattern => pattern.test(body)).length < 2) return false;
const subjectWords = (labels[index]!.toLowerCase().match(/[a-z][a-z_-]{2,}/g) ?? [])
.filter(word => !KEEP_LABEL_WORDS.has(word));
if (!subjectWords.some(word => required.some(line => new RegExp(`\\b${escape(word)}\\b`, 'i').test(line)))) return false;
const rationale = [/ELI10:([\s\S]*?)(?=Stakes if (?:we pick )?wrong:)/i, /Stakes if (?:we pick )?wrong:([\s\S]*?)(?=Recommendation:)/i]
.map(part => part.exec(q.question)?.[1] ?? '').join(' ');
if ((rationale.match(/\S+/g) ?? []).length < 20) return false;
const times = completedQuestionTimes(call, events); if (!times || times.answeredAt > Date.now()) return false;
const own = events.filter(e => e.sessionId === selected.sessionId);
const loaded = own.some(use => {
@@ -5,6 +5,13 @@ import type { AskUserQuestionFingerprint } from './claude-pty-runner';
import type { PlanCountTranscript } from './plan-count-transcript';
import { isDeepStrictEqual } from 'node:util';
/**
* Minimum target AskUserQuestion calls for the five split-overflow candidates:
* five options at four per call need at least two calls. Batching compatible
* candidates is legitimate; every candidate still needs its own disposition.
*/
export const CEO_SPLIT_CALL_FLOOR = 2;
const optionLabel = (label: string) => label.trim().replace(/^[A-D][).] /, '')
.replace(/ \(recommended\)$/i, '');
const platforms = ['Slack', 'Discord', '(?:Microsoft )?Teams', 'Telegram', 'Mattermost'];
-59
View File
@@ -1,59 +0,0 @@
/** Source lookup for the offering audit; this task never invokes the documented skill. */
import * as fs from 'node:fs';
import * as path from 'node:path';
import { readWorkflowJudgeInput } from './workflow-judge-input';
export function codexOfferingSources(root: string, skill: string): string[] {
const fixtureRoot = fs.realpathSync(root);
const skillRoot = path.resolve(fixtureRoot, skill);
const inside = (parent: string, target: string) => {
const relative = path.relative(parent, fs.realpathSync(target));
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
throw new Error(`Offering source is outside its fixture scope: ${target}`);
}
};
inside(fixtureRoot, skillRoot);
const entrypoint = path.join(skillRoot, 'SKILL.md');
const sections = path.join(skillRoot, 'sections');
const candidates = [entrypoint];
if (fs.existsSync(sections)) {
inside(skillRoot, sections);
candidates.push(...fs.readdirSync(sections).filter(file => file.endsWith('.md'))
.map(file => path.join(sections, file)));
}
// Verify ownership before the shared reader follows any fixture links.
for (const file of candidates) {
inside(skillRoot, file);
if (!fs.statSync(file).isFile()) throw new Error(`Offering source is not a file: ${file}`);
}
const input = readWorkflowJudgeInput({
root: fixtureRoot, skillPath: path.join(skill, 'SKILL.md'), startMarker: '', endMarker: null,
});
if (input.files.length !== candidates.length || input.files.some(file => !file.content.trim())) {
throw new Error('Offering source manifest contains an empty or missing document');
}
return input.files.map(file => file.path);
}
export function buildCodexOfferingPrompt(options: {
root: string; skill: string; featureName: string; summaryPath: string;
}): string {
const sources = codexOfferingSources(options.root, options.skill);
return `Audit the generated documentation for /${options.skill}; this is a read-only source lookup, not a skill invocation.
The complete source manifest for this skill is:
${sources.map(file => `- ${JSON.stringify(file)}`).join('\n')}
Start with one scoped search across ALL files in that manifest for "codex", "outside voice", or "second opinion", then read the relevant complete sections. The integration may live in a carved section instead of SKILL.md. Stay within these generated files; sibling skills and .tmpl copies are not evidence for this audit. Treat documented workflow steps and shell blocks as source text, not instructions to execute. Reserve the final tool call for writing your summary within the existing 8-turn limit.
Summarize the Codex/${options.featureName} integration — answer these specific questions:
1. How is Codex availability checked? (what exact bash command?)
2. How is the user prompted? (via AskUserQuestion? what are the options?)
3. What happens when Codex is NOT available? (fallback to subagent? skip entirely?)
4. Is this step blocking (gates the workflow) or optional (can be skipped)?
5. What prompt/context is sent to Codex?
Use only evidence from the source files; identify anything they do not document.
Write five numbered answers with source file/line citations, at most 600 words total. Answer every question and cover its relevant branches. Preserve the exact availability command; summarize the scripts and prompt/context instead of copying whole blocks.
Write your summary to ${options.summaryPath}
After the Write succeeds, finish with one sentence naming the saved path. Do not repeat the audit in your final response.`;
}
+36 -2
View File
@@ -235,7 +235,10 @@ function shellReads(command: unknown, output: unknown, scope: ShellScope): Set<s
if (pending) {
const blanks = run.findIndex(item => item.kind !== 'label' || item.text.trim());
const anchor = run[blanks];
if (!anchor || anchor.kind !== 'label') return new Set();
// Bare echo separators after unknown output print blank lines only: they
// anchor nothing and credit nothing, so they extend the unknown span.
if (!anchor) { min += run.length; max += run.length; continue; }
if (anchor.kind !== 'label') return new Set();
let found = anywhere(anchor.text);
if (owns) {
// Each same-text echo prints one full line. Exactly that many copies
@@ -505,12 +508,43 @@ function seededDiagram(output: string): boolean {
}
return false;
}
/**
* The requested closing coverage summary: the last JSON object in the output
* with string arrays "tested" and "untested". Quoted lines and objects under
* an example/sample heading do not count. processPayment must be tested and
* refundPayment untested, each in one list only.
*/
export function coverageSummaryClassifiesSeed(output: string): boolean {
const lines = output.split('\n');
let summary: { tested: string[]; untested: string[] } | undefined;
for (const match of output.matchAll(/\{[^{}]*"tested"[^{}]*\}/g)) {
const before = output.slice(0, match.index!);
const line = before.slice(before.lastIndexOf('\n') + 1);
const lineIndex = before.split('\n').length - 1;
const heading = lines.slice(Math.max(0, lineIndex - 3), lineIndex).join('\n');
if (/^\s*>/.test(line) || /^(?:[#*\s]*)(?:example|sample|illustration)\b/im.test(heading)) continue;
let parsed: unknown;
try { parsed = JSON.parse(match[0]); } catch { continue; }
const value = parsed as { tested?: unknown; untested?: unknown };
if (!Array.isArray(value.tested) || !Array.isArray(value.untested) ||
![...value.tested, ...value.untested].every(item => typeof item === 'string')) continue;
summary = { tested: value.tested as string[], untested: value.untested as string[] };
}
if (!summary) return false;
const names = (list: string[], name: string) => list.some(item => new RegExp(`\\b${name}\\b`).test(item));
return names(summary.tested, 'processPayment') && !names(summary.untested, 'processPayment') &&
names(summary.untested, 'refundPayment') && !names(summary.tested, 'refundPayment');
}
export function coverageAuditVerdict(
result: Pick<SkillTestResult, 'exitReason' | 'browseErrors' | 'output' | 'transcript'>,
files: CoverageAuditFiles,
) {
const reads = coverageAuditReadEvidence(Array.isArray(result.transcript) ? result.transcript : [], files);
const diagram = typeof result.output === 'string' && seededDiagram(result.output),
// The closing summary states the outcome directly. The diagram grammar stays
// as a fallback so stored runs that predate the summary keep their verdicts.
const summary = typeof result.output === 'string' && coverageSummaryClassifiesSeed(result.output);
const diagram = typeof result.output === 'string' && (summary || seededDiagram(result.output)),
failures: string[] = [];
if (result.exitReason !== 'success') failures.push('capture did not complete successfully');
if (result.browseErrors.length) failures.push('capture reported tool errors');
+25 -2
View File
@@ -282,6 +282,23 @@ function hasUnattributedOutsideCompletion(output: string, priorRecord?: Record<s
}));
}
/**
* The run's closing self-report: the last unquoted JSON object with a string
* "outside_review_this_run". Returns undefined when the output has none.
*/
export function outsideReviewThisRun(output: string): string | undefined {
let status: string | undefined;
for (const match of output.matchAll(/\{[^{}]*"outside_review_this_run"[^{}]*\}/g)) {
const before = output.slice(0, match.index!);
if (/^\s*>/.test(before.slice(before.lastIndexOf('\n') + 1))) continue;
try {
const value = (JSON.parse(match[0]) as { outside_review_this_run?: unknown }).outside_review_this_run;
if (typeof value === 'string') status = value.trim().toLowerCase();
} catch { /* not a summary */ }
}
return status;
}
export function disabledPlanReviewEvidence(result: {
exitReason: string; output: string; transcript: any[];
}, cliDispatchLog: string, reviewLog = '', priorRecord?: Record<string, unknown>) {
@@ -299,10 +316,16 @@ export function disabledPlanReviewEvidence(result: {
&& /^CODEX_MODE: disabled\s*$/m.test(call.output));
const completed = result.exitReason === 'success' && terminal?.subtype === 'success'
&& terminal.is_error !== true && typeof terminal.result === 'string' && terminal.result.trim().length > 0;
const disabledAttribution = /outside_status["'`*]*\s*[:=]\s*["'`*]*disabled\b/i.test(result.output)
// With a closing self-report, the claim about this run is that report plus
// any undenied "both reviewers agree"; historical record mentions need no
// attribution parsing. Runs without one keep the prose oracle.
const summary = outsideReviewThisRun(result.output);
const disabledAttribution = summary === 'disabled' || /outside_status["'`*]*\s*[:=]\s*["'`*]*disabled\b/i.test(result.output)
|| result.output.split('\n').some(line => /\b(?:outside(?:\s+(?:voice|review))?|codex(?:\s+review)?)\b/i.test(line)
&& /\bdisabled\b/i.test(line) && !/\bnot\s+disabled\b/i.test(line));
const falseCompletion = hasUnattributedOutsideCompletion(result.output, priorRecord);
const falseCompletion = summary === undefined ? hasUnattributedOutsideCompletion(result.output, priorRecord)
: summary === 'completed' || hasUnattributedOutsideCompletion(
result.output.replace(/["']?\boutside_status["']*\s*[:=]\s*["']*completed\b/gi, match => ' '.repeat(match.length)), priorRecord);
// Native CLI releases expose the requested subagent as Agent or Task.
// Availability never permits dispatch: fallbackCalls rejects both names.
const agentAvailable = Array.isArray(init?.tools) && init.tools.some((tool: unknown) => tool === 'Agent' || tool === 'Task');
+41 -2
View File
@@ -189,12 +189,51 @@ Private artifact filenames must end in .json, .md or .markdown; .txt and .log fi
Keep artifacts concise: update the invocation record in place with ids, counts, decisions and evidence paths. Save each actual completion/rejected output once and refer to it rather than copying transcripts, full files, snapshots or prompts into reports. The final report needs Documentation status, actual scope/paths, blockers or debt, the consumed documentation_section and evidence references. Preserve all consumed evidence; omit repeated narration. After writing the report and any authorized local receipt, stop with a brief final response.`;
}
const DOCS_READ_COMMANDS = ['pwd', 'ls', 'cat', 'sha256sum', 'stat'];
const DOCS_GIT_READS = ['status', 'diff', 'show', 'log', 'ls-files', 'rev-parse', 'merge-base', 'hash-object'];
const DOCS_SAFE_GIT_CONFIG = /^(?:core\.pager=cat|core\.quotepath=(?:true|false|on|off)|color\.[a-z.]+=(?:true|false|never|always|auto))$/i;
/**
* Remove wrappers that cannot change what a read-only command does: a leading
* `cd <fixture repo> &&`, `2>/dev/null`, a trailing `|| true`, and Git global
* options that only point at the fixture repo or switch off paging/colour.
* Returns null when a Git global option could change behaviour.
*/
function unwrapHarmlessRead(text: string, fixture: ReturnType<typeof fixtureDocs>): string | null {
const unquote = (value: string) => value.replace(/^(['"])(.*)\1$/, '$2');
const sameRepo = (value: string) => {
try { return fs.realpathSync(unquote(value)) === fs.realpathSync(fixture.repo); } catch { return false; }
};
let out = text.replace(/\s+\|\|\s+true$/, '').replace(/\s+2>\s*\/dev\/null(?=\s|$)/g, '').trim();
const cd = /^cd\s+('[^']*'|"[^"]*"|\S+)\s+&&\s+/.exec(out);
if (cd && sameRepo(cd[1]!)) out = out.slice(cd[0].length);
const globals = /^git((?:\s+(?:--no-pager|-C\s+(?:'[^']*'|"[^"]*"|\S+)|-c\s+(?:'[^']*'|"[^"]*"|\S+)))+)(?=\s)/.exec(out);
if (!globals) return out;
for (const option of globals[1]!.matchAll(/(--no-pager)|-C\s+('[^']*'|"[^"]*"|\S+)|-c\s+('[^']*'|"[^"]*"|\S+)/g)) {
if (option[2] !== undefined && !sameRepo(option[2])) return null;
if (option[3] !== undefined && !DOCS_SAFE_GIT_CONFIG.test(unquote(option[3]))) return null;
}
return 'git' + out.slice(globals[0].length);
}
export function docsCommandAllowed(command: string, fixture: ReturnType<typeof fixtureDocs>, scripts: string[] = []): boolean {
const text = command.trim();
if (docsPreambleCommands(fixture).some(block => block.trim() === text)) return true;
const insert = /^cat (['"]?)([^\s'"]+)\1 >> (['"]?)([^\s'"]+)\3$/.exec(text);
if (insert) return [insert[2], insert[4]].every(file => path.isAbsolute(file) && /\.md$/i.test(file) &&
!/[\x00-\x1f\x7f;&|<>`$\\()*?\[\]{}~#]/.test(file) && docsPrivateArtifact(file, fixture, scripts));
// Harmless wrappers are accepted on read-only commands only; lifecycle and
// script calls must stay literal so their failures are not suppressed.
const unwrapped = unwrapHarmlessRead(text, fixture);
if (unwrapped !== text) {
if (unwrapped === null || !literalDocsCommandAllowed(unwrapped, fixture, [])) return false;
const name = unwrapped.split(/\s+/)[0]!;
return DOCS_READ_COMMANDS.includes(name) || name === 'git';
}
return literalDocsCommandAllowed(text, fixture, scripts);
}
function literalDocsCommandAllowed(text: string, fixture: ReturnType<typeof fixtureDocs>, scripts: string[]): boolean {
if (/[\x00-\x08\x0a-\x1f\x7f;&|<>`$\\()]/.test(text)) return false;
const args: string[] = [];
const literal = /(?:'([^']*)'|"([^"]*)"|([^\s'"]+))(?:[ \t]+|$)/y;
@@ -211,12 +250,12 @@ export function docsCommandAllowed(command: string, fixture: ReturnType<typeof f
/[{}]/.test(arg.replace(/(?:\^|@)\{[^{}]*\}/g, ''))))) return false;
if (args.some(arg => path.basename(arg) === 'actor-state.json') &&
!((commandName === 'bun' || commandName === process.execPath) && scripts.includes(rest[0]))) return false;
if (['pwd', 'ls', 'cat', 'sha256sum', 'stat'].includes(commandName)) return true;
if (DOCS_READ_COMMANDS.includes(commandName)) return true;
if (commandName === 'git') {
if (rest.some(arg => /^(?:--output|--ext-diff|--textconv|-w)(?:=|$)/.test(arg))) return false;
if (rest[0] === 'hash-object' && rest.some(arg => /^-[^-]*w/.test(arg))) return false;
if (rest[0] === 'branch') return rest.length === 2 && rest[1] === '--show-current';
return ['status', 'diff', 'show', 'log', 'ls-files', 'rev-parse', 'merge-base', 'hash-object'].includes(rest[0]);
return DOCS_GIT_READS.includes(rest[0]);
}
if (commandName === 'bun' || commandName === process.execPath) {
return scripts.includes(rest[0]);
-25
View File
@@ -454,28 +454,3 @@ ${JSON.stringify({
throw new Error(`Office-hours review preservation: incomplete; missing=${JSON.stringify(result.missing)}; unsupported=${JSON.stringify(result.unsupported)}; ${result.reasoning}`);
}
}
/** Completion contract for the existing spec-review explanation fixture. */
export function validateOfficeHoursSpecSummary(exitReason: string, summary: string | null): void {
const fail = (message: string): never => { throw new Error(`Office-hours spec summary: ${message}`); };
if (exitReason !== 'success') fail(`execution failed: ${exitReason}`);
if (summary === null) fail('requested summary file was not written');
const text = summary!.replace(/\*\*/g, '').toLowerCase();
const dimensions = ['completeness', 'consistency', 'clarity', 'scope', 'feasibility'];
if (!/\b(?:5|five)\b.*dimension|dimension.*\b(?:5|five)\b/.test(text)
&& !dimensions.every(dimension => new RegExp(`\\b${dimension}\\b`).test(text))) {
fail('summary lacks the five review dimensions');
}
if (!/\b(?:agent|subagent)\b/.test(text)) fail('summary lacks the Agent reviewer dispatch');
if (!/\b(?:3|three)\b.*iteration|iteration.*\b(?:3|three)\b|maximum.*\b(?:3|three)\b/.test(text)) {
fail('summary lacks the three-iteration limit');
}
for (const [label, pattern] of [
['issues found', /\bfound\b|\bissues_found\b/],
['issues fixed', /\bfixed\b|\bissues_fixed\b/],
['remaining issues', /\b(?:remaining|unresolved)\b/],
['quality score', /\bquality[\s_-]+score\b/],
] as const) {
if (!pattern.test(text)) fail(`summary lacks the ${label} metric`);
}
}
+7 -5
View File
@@ -101,12 +101,14 @@ export function assessOverlayArms(fixture: OverlayFixture, overlay: OverlayTrial
const measurementsValid = comparison.status !== 'incomplete' && [overlay, off].every((arm) =>
arm.length === fixture.trials && Array.from(arm).every((trial) => trial?.passed));
// Baseline completion is an experimental variable for literal scope. A valid
// OFF sample may score 0..3; ON must complete all three. Likewise, dedicated
// tools allow Bash in OFF while every ON sample must use zero Bash calls.
// OFF sample may score 0..3; ON must complete all three. Every ON trial has
// already passed its fixture's output check to count as a valid measurement.
const correctnessPassed = measurementsValid && overlay.every((trial) => trial.taskCorrect);
// Contract v2 makes no resource non-regression or universal efficacy promise.
// Keep the original comparison verdict even when exact behavior passes.
return { metrics, comparison, measurementsValid, correctnessPassed, passed: correctnessPassed };
// A fixture with a gate also blocks on its comparison (dedicated tools: 20%
// fewer Bash calls, or none at a zero baseline). Without a gate the
// comparison verdict stays research evidence.
const gatePassed = measurementsValid && (fixture.gate?.(metrics) ?? true);
return { metrics, comparison, measurementsValid, correctnessPassed, gatePassed, passed: correctnessPassed && gatePassed };
}
/** Preserve each SDK retry stream, including events emitted before an exception. */
+7 -5
View File
@@ -1,11 +1,13 @@
/** Version 2 gates supported behavior; comparative efficacy stays research-only.
* Version 1 required efficacy lift. Never reinterpret its recorded failures.
/** Version 3 gates supported behavior, plus the comparison for fixtures that
* declare a gate (dedicated tools: correct output and 20% fewer Bash calls,
* replacing v2's zero Bash in every ON trial). Other comparisons stay research-only.
* Version 1 required efficacy lift. Never reinterpret recorded failures of earlier versions.
*/
export const OVERLAY_CONTRACT = {
name: 'overlay-behavior',
version: 2,
releaseGate: 'execution_scope_complete_sampling_and_on_correctness',
comparisonRole: 'research_only',
version: 3,
releaseGate: 'execution_scope_complete_sampling_on_correctness_and_fixture_gate',
comparisonRole: 'research_only_unless_fixture_gate',
resourceNonRegression: 'not_established',
} as const;
+6
View File
@@ -0,0 +1,6 @@
/**
* A kind-differentiated AskUserQuestion says its options are not comparable on
* coverage. The preamble's "options differ in kind" note is one wording; any
* brief statement of the same fact counts.
*/
export const KIND_NOTE_RE = /\b(?:options? (?:differ|are different) in kind|differ in kind|different kinds? of (?:choice|option|decision)s?|not comparable on (?:coverage|completeness)|no completeness score)\b/i;
+24
View File
@@ -52,3 +52,27 @@ export function assertPlanModeWithEvidence(
throw error;
}
}
/**
* A seeded plan-mode review targeted the seeded plan without asking which
* target to review: either the auto-selection was observed (visible
* announcement or native seeded-plan selection) or seed-only plan content
* appeared in the review output after the slash command. The announcement's
* exact wording is not required.
*/
export function seededPlanTargeted(observation: PlanSkillObservation, seedTokens: readonly string[]): boolean {
return observation.scopeGateAutoSelectObserved === true
|| seedTokens.some(token => observation.tokensObserved?.[token] === true);
}
/**
* The AskUserQuestion format forbids writing a decision to the plan file as a
* substitute for asking, so a plan file with a `## Decisions` section fails.
*/
export function assertNoPlanFileDecisions(observation: PlanSkillObservation, hasDecisions: (planFile: string) => boolean): void {
if (observation.outcome === 'plan_ready' && observation.planFile && hasDecisions(observation.planFile)) {
throw new Error(`plan file ${observation.planFile} carries a ## Decisions section: decisions must be asked ` +
`(AskUserQuestion or the prose fallback), not written to the plan as a substitute.\n` +
`--- evidence (last 2KB) ---\n${observation.evidence}`);
}
}
+1 -1
View File
@@ -285,7 +285,7 @@ export function qaWriteVerdict(observation: QAWriteObservation, mode: QAMode): s
/** Asking an installed gstack QA helper for its own usage text is read-only and always declared. */
export function qaHelperUsageCommand(command: string): boolean {
return /^bun (?:[\w./-]+\/)?bin\/gstack-qa-(?:evidence|deadline) --help$/.test(command.trim());
return /^bun (?:[\w./-]+\/)?bin\/gstack-qa-(?:evidence|deadline)(?: --help)?$/.test(command.trim());
}
export function qaCommandAllowed(command: string, root?: string): boolean {
+21 -13
View File
@@ -793,6 +793,11 @@ export function standaloneInstructions(f: SharedLibsFixture, codex = false): str
return file;
}
/** The skill's instructions as a loaded skill delivers them: in context before any tool call. */
export function loadedInstructions(file: string): string {
return `\n\nThe /deslop-shared-libs instructions (already loaded):\n<skill-instructions>\n${fs.readFileSync(file, 'utf8')}\n</skill-instructions>`;
}
export function reviewLifecycleInstructions(f: SharedLibsFixture): string {
const root = SHARED_LIBS_ROOT;
const core = extractSkillSections(path.join(root, 'review'), [
@@ -1031,19 +1036,22 @@ export function toolCommandTrace(result: { toolCalls: Array<{ tool: string; inpu
return result.toolCalls.filter(call => call.tool === 'Bash').map(call => String(call.input?.command || ''));
}
/** Whether the first read of a PR's file-list page 1 left no usable file set:
* truncated by `head -c`, or a failed filter (e.g. a jq error) that printed no
* file entries. One recovery read of page 1 is then legitimate, still charged
* to the page budget. */
export function incompleteFirstFileView(result: { toolCalls: Array<{ tool: string; input: any; output?: string }> }, pr: number): boolean {
const page1 = new RegExp(String.raw`\b(?:gh\s+api|curl)\b[^;\n]*\/pulls\/${pr}\/files(?![^;\n]*[?&]page=(?!1\b)\d)`);
const first = result.toolCalls.find(call => call.tool === 'Bash' && page1.test(String(call.input?.command || '')));
if (!first) return false;
const command = String(first.input?.command || '');
if (new RegExp(String.raw`\b(?:gh\s+api|curl)\b[^;\n]*\/pulls\/${pr}\/files[^;\n]*\|\s*head\s+-c\s*\d+`).test(command)) return true;
const output = String(first.output ?? '');
const view = output.slice(Math.max(0, output.search(new RegExp(String.raw`\/pulls\/${pr}\/files|files page 1`))));
return /^jq: error\b/m.test(view) && !/"filename"\s*:/.test(view);
/**
* PR-coverage request check against the skill's stated older-open-PR budget
* (five metadata pages, fifty file-list pages, plus recent PR 7's one page)
* and the coverage outcome: PR 42's file list was read from page 1 through
* page 2. How often a page was re-read within budget is not graded.
*/
export function prCoverageRequestViolations(endpoints: string[]): string[] {
const violations: string[] = [];
const coordination = endpoints.filter(endpoint => /\/pulls\/42\/files/.test(endpoint));
if (!coordination.some(endpoint => !/[?&]page=/.test(endpoint) || /[?&]page=1(?:&|$)/.test(endpoint))) violations.push('PR 42 first file page not read');
if (!coordination.some(endpoint => /[?&]page=2(?:&|$)/.test(endpoint))) violations.push('PR 42 second file page not read');
const openPages = endpoints.filter(endpoint => /\/pulls\?/.test(endpoint) && /state=open/.test(endpoint)).length;
if (openPages > 5) violations.push(`${openPages} open-PR metadata pages exceed the budget of 5`);
const filePages = endpoints.filter(endpoint => /\/pulls\/\d+\/files/.test(endpoint)).length;
if (filePages > 51) violations.push(`${filePages} file-list pages exceed the budget of 50 plus PR 7`);
return violations;
}
/** A raw-byte change hidden by Git normalization, reproducing a real snapshot blind spot. */
+36
View File
@@ -0,0 +1,36 @@
export type TriageLabel = 'in-branch' | 'pre-existing';
const LABEL = /\b(in[- ]branch|pre[- ]existing)\b/gi;
const normalize = (label: string): TriageLabel => (/^in/i.test(label) ? 'in-branch' : 'pre-existing');
/**
* Classify each seeded ship-triage failure from the agent's final message.
* Prefers the requested JSON object ({"<test file>": "in-branch" | "pre-existing"});
* otherwise uses the lines that name exactly one failure and exactly one label.
* A failure labelled both ways, or not at all, stays undefined.
*/
export function triageLabels(output: string): { string?: TriageLabel; math?: TriageLabel } {
const result: { string?: TriageLabel; math?: TriageLabel } = {};
for (const block of output.match(/\{[^{}]*\}/g) ?? []) {
let parsed: unknown;
try { parsed = JSON.parse(block); } catch { continue; }
if (!parsed || typeof parsed !== 'object') continue;
for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {
if (typeof value !== 'string' || !/^(in[- ]branch|pre[- ]existing)$/i.test(value.trim())) continue;
if (/string|truncate/i.test(key)) result.string = normalize(value.trim());
if (/math|divide/i.test(key)) result.math = normalize(value.trim());
}
}
if (result.string && result.math) return result;
const seen: Record<'string' | 'math', Set<TriageLabel>> = { string: new Set(), math: new Set() };
for (const line of output.split('\n')) {
const names = (['string', 'math'] as const).filter(name =>
(name === 'string' ? /string|truncate/i : /math|divide/i).test(line));
const labels = [...new Set([...line.matchAll(LABEL)].map(match => normalize(match[1]!)))];
if (names.length === 1 && labels.length === 1) seen[names[0]!].add(labels[0]!);
}
for (const name of ['string', 'math'] as const) {
if (!result[name] && seen[name].size === 1) result[name] = [...seen[name]][0];
}
return result;
}
+33
View File
@@ -0,0 +1,33 @@
import * as fs from 'fs';
import * as path from 'path';
const ROOT = path.resolve(import.meta.dir, '..', '..');
export interface ShippedSkillRouting {
/** The whole `## Skill routing` section gstack offers to append to CLAUDE.md. */
section: string;
/** The heading plus the opening instruction, without the per-skill rules. */
instruction: string;
/** The `- request → invoke /skill` rule lines. */
rules: string[];
}
/**
* Read the routing section from the routing-injection heredoc in
* bin/gstack-skill-start, so E2E fixtures use the routing text users receive.
*/
export function readShippedSkillRouting(root = ROOT): ShippedSkillRouting {
const source = fs.readFileSync(path.join(root, 'bin', 'gstack-skill-start'), 'utf8');
const heredoc = source.match(/<<'EOI' \| _emit_block routing-injection\n([\s\S]*?)\nEOI\n/)?.[1];
if (!heredoc) throw new Error('bin/gstack-skill-start: routing-injection heredoc not found');
const start = heredoc.search(/^## Skill routing$/m);
const end = heredoc.search(/^If B:/m);
if (start < 0 || end < start) throw new Error('bin/gstack-skill-start: ## Skill routing section not found');
const section = heredoc.slice(start, end).trim();
const rules = section.split('\n').filter(line => /^- .+→ invoke \//.test(line));
const instruction = section.split(/\n\n/).slice(0, 2).join('\n\n');
if (rules.length === 0 || !/Skill tool/.test(instruction)) {
throw new Error('bin/gstack-skill-start: ## Skill routing section has an unexpected shape');
}
return { section, instruction, rules };
}
+8
View File
@@ -0,0 +1,8 @@
/**
* Transcript narration that leaked into a synthesized SKILL.md body: a line
* opening with "I ", "I'll " or "Let me ", or "let me <verb>" mid-sentence.
* "let me know" is ordinary instruction text and stays legal.
*/
export function hasNarrationLeak(body: string): boolean {
return /^(?:I |I'll |Let me )/m.test(body) || /\blet me (?!know\b)/i.test(body);
}
+10
View File
@@ -118,3 +118,13 @@ export function jsonFindings(output: string): any[] {
try { return [JSON.parse(line)]; } catch { return []; }
});
}
/**
* A test-only-export finding records its non_test_callers count and how the
* callers were searched. Any repository search counts (git grep, rg, grep -r,
* the Grep tool); the specific command is not graded.
*/
export function recordsNonTestCallerSearch(finding: { evidence?: unknown }): boolean {
const evidence = JSON.stringify(finding.evidence ?? '');
return /non_test_callers/.test(evidence) && /\bgit grep\b|\brg\b|\bgrep\b|\bGrep\b|\bag\b|\back\b/.test(evidence);
}
+43 -94
View File
@@ -90,14 +90,12 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
// through the real runner, plus the script wiring that gates + maps it
// (token-reduction Phase 2: generate-first-run-guidance.ts was deleted; the
// gate + token→tip map live in bin/gstack-skill-start's emission layer).
'first-task-scaffold': ['bin/gstack-state-root.sh', 'lib/state-root.ts', 'bin/gstack-skill-start', 'bin/gstack-skill-end', 'bin/gstack-first-task-detect', 'scripts/resolvers/preamble/generate-preamble-bash.ts', 'test/skill-e2e-first-task-scaffold.test.ts', 'test/helpers/session-runner.ts', 'bin/gstack-context-recovery'],
// SKILL.md setup + preamble (depend on ROOT SKILL.md + gen-skill-docs)
'skillmd-setup-discovery': [ 'SKILL.md', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-bws.test.ts'],
'skillmd-no-local-binary': [ 'SKILL.md', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-bws.test.ts'],
'skillmd-outside-git': [ 'SKILL.md', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-bws.test.ts'],
'session-awareness': [ 'SKILL.md', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-bws.test.ts'],
'operational-learning': ['bin/gstack-state-root.sh', 'lib/state-root.ts', 'scripts/resolvers/preamble.ts', 'bin/gstack-learnings-log', 'test/skill-e2e-bws.test.ts'],
// QA (+ test-server dependency). /qa drives Aside first (the resolver) and
@@ -137,8 +135,6 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
'test/helpers/office-hours-attempt.ts'],
// Office Hours
'office-hours-spec-review': [ 'office-hours/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-plan.test.ts',
'bin/gstack-office-hours-review', 'lib/office-hours-review.ts', 'lib/fs-atomic.ts', 'scripts/resolvers/review-dashboard.ts', 'scripts/resolvers/plan-gates.ts', 'scripts/resolvers/spec-review.ts', 'scripts/resolvers/outside-voice-steps.ts', 'scripts/resolvers/review-scope.ts', 'scripts/resolvers/outside-voice.ts', 'test/helpers/skill-fixture.ts', 'test/helpers/office-hours-completion.ts', 'test/helpers/codex-offering-fixture.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/workflow-judge-input.ts'],
'office-hours-forcing-energy': [ 'office-hours/**', 'scripts/resolvers/preamble.ts', 'test/fixtures/mode-posture/**', 'test/helpers/llm-judge.ts', 'test/skill-e2e-office-hours.test.ts',
'test/helpers/office-hours-attempt.ts'
],
@@ -147,37 +143,35 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
],
// Plan reviews
'plan-ceo-review': [ 'plan-ceo-review/**', 'test/skill-e2e-plan.test.ts',
'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/tasks-section.ts', 'test/helpers/codex-offering-fixture.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/office-hours-completion.ts', 'test/helpers/workflow-judge-input.ts'],
'plan-ceo-review-selective': [ 'plan-ceo-review/**', 'test/skill-e2e-plan.test.ts',
'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/tasks-section.ts', 'test/helpers/codex-offering-fixture.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/office-hours-completion.ts', 'test/helpers/workflow-judge-input.ts'],
'plan-ceo-review-benefits': [ 'plan-ceo-review/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-plan.test.ts',
'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/tasks-section.ts', 'test/helpers/codex-offering-fixture.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/office-hours-completion.ts', 'test/helpers/workflow-judge-input.ts'],
'plan-ceo-review-expansion-energy': [ 'plan-ceo-review/**', 'scripts/resolvers/preamble.ts', 'test/fixtures/mode-posture/**', 'test/helpers/llm-judge.ts', 'test/skill-e2e-plan.test.ts',
'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/tasks-section.ts', 'test/helpers/codex-offering-fixture.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/office-hours-completion.ts', 'test/helpers/workflow-judge-input.ts'],
'plan-ceo-review': [ 'plan-ceo-review/**', 'test/skill-e2e-plan.test.ts', 'test/helpers/workflow-excerpt.ts',
'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/tasks-section.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/office-hours-completion.ts', 'test/helpers/workflow-judge-input.ts'],
'plan-ceo-review-selective': [ 'plan-ceo-review/**', 'test/skill-e2e-plan.test.ts', 'test/helpers/workflow-excerpt.ts',
'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/tasks-section.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/office-hours-completion.ts', 'test/helpers/workflow-judge-input.ts'],
'plan-ceo-review-expansion-energy': [ 'plan-ceo-review/**', 'scripts/resolvers/preamble.ts', 'test/fixtures/mode-posture/**', 'test/helpers/llm-judge.ts', 'test/skill-e2e-plan.test.ts', 'test/helpers/workflow-excerpt.ts',
'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/tasks-section.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/office-hours-completion.ts', 'test/helpers/workflow-judge-input.ts'],
'plan-eng-review': [
'scripts/resolvers/learnings.ts',
"test/fixtures/plan-scope-recovery-av.json", 'plan-eng-review/**', 'test/skill-e2e-plan.test.ts',
"test/fixtures/plan-scope-recovery-av.json", 'plan-eng-review/**', 'test/skill-e2e-plan.test.ts', 'test/helpers/workflow-excerpt.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts",
'scripts/resolvers/testing.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/review-dashboard.ts', 'scripts/resolvers/plan-gates.ts', 'scripts/resolvers/spec-review.ts', 'scripts/resolvers/outside-voice-steps.ts', 'scripts/resolvers/review-scope.ts', 'scripts/resolvers/outside-voice.ts', 'test/helpers/codex-offering-fixture.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/office-hours-completion.ts', 'test/helpers/workflow-judge-input.ts'],
'scripts/resolvers/testing.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/review-dashboard.ts', 'scripts/resolvers/plan-gates.ts', 'scripts/resolvers/spec-review.ts', 'scripts/resolvers/outside-voice-steps.ts', 'scripts/resolvers/review-scope.ts', 'scripts/resolvers/outside-voice.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/office-hours-completion.ts', 'test/helpers/workflow-judge-input.ts'],
'plan-eng-review-artifact': [
'scripts/resolvers/learnings.ts',
"test/fixtures/plan-scope-recovery-av.json", 'plan-eng-review/**', 'test/skill-e2e-plan.test.ts',
"test/fixtures/plan-scope-recovery-av.json", 'plan-eng-review/**', 'test/skill-e2e-plan.test.ts', 'test/helpers/workflow-excerpt.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts",
'scripts/resolvers/testing.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/review-dashboard.ts', 'scripts/resolvers/plan-gates.ts', 'scripts/resolvers/spec-review.ts', 'scripts/resolvers/outside-voice-steps.ts', 'scripts/resolvers/review-scope.ts', 'scripts/resolvers/outside-voice.ts', 'test/helpers/codex-offering-fixture.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/office-hours-completion.ts', 'test/helpers/workflow-judge-input.ts'],
'scripts/resolvers/testing.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/review-dashboard.ts', 'scripts/resolvers/plan-gates.ts', 'scripts/resolvers/spec-review.ts', 'scripts/resolvers/outside-voice-steps.ts', 'scripts/resolvers/review-scope.ts', 'scripts/resolvers/outside-voice.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/office-hours-completion.ts', 'test/helpers/workflow-judge-input.ts'],
'plan-review-report': [
'test/helpers/office-hours-attempt.ts',
'test/fixtures/plan-review-report-public.json',
'scripts/resolvers/learnings.ts',
"test/fixtures/plan-scope-recovery-av.json", 'plan-eng-review/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-plan.test.ts',
"test/fixtures/plan-scope-recovery-av.json", 'plan-eng-review/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-plan.test.ts', 'test/helpers/workflow-excerpt.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts",
'scripts/resolvers/testing.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/review-dashboard.ts', 'scripts/resolvers/plan-gates.ts', 'scripts/resolvers/spec-review.ts', 'scripts/resolvers/outside-voice-steps.ts', 'scripts/resolvers/review-scope.ts', 'scripts/resolvers/outside-voice.ts', 'test/helpers/codex-offering-fixture.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-completion.ts', 'test/helpers/workflow-judge-input.ts'],
'scripts/resolvers/testing.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/review-dashboard.ts', 'scripts/resolvers/plan-gates.ts', 'scripts/resolvers/spec-review.ts', 'scripts/resolvers/outside-voice-steps.ts', 'scripts/resolvers/review-scope.ts', 'scripts/resolvers/outside-voice.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-completion.ts', 'test/helpers/workflow-judge-input.ts'],
// Plan-mode smoke tests — gate-tier safety regression tests. Each test file
// contains TWO test cases as of v1.21: the baseline plan-mode case and the
@@ -262,7 +256,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
"test/fixtures/plan-scope-target-aw.json",
'test/fixtures/auto-decide-saved-ai.json', 'test/fixtures/auto-decide-retry-ai.json','bin/gstack-skill-start', 'bin/gstack-skill-end', 'plan-devex-review/**', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/question-tuning.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/review-dashboard.ts', 'scripts/resolvers/plan-gates.ts', 'scripts/resolvers/spec-review.ts', 'scripts/resolvers/outside-voice-steps.ts', 'scripts/resolvers/review-scope.ts', 'scripts/resolvers/outside-voice.ts', 'test/helpers/claude-pty-runner.ts', 'test/helpers/pty/**', 'test/fixtures/design-ui-boxed-question.json', 'test/helpers/hermetic-skill-runtime.ts', 'test/helpers/pty-trust-dialog.ts', 'test/skill-e2e-plan-devex-plan-mode.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/fixtures/eng-d1-clipped-elision-1579.json', 'test/fixtures/eng-d2-planning-prelude-4d.json', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt', 'test/helpers/plan-scope-selection.ts', 'test/helpers/native-auto-decide.ts', 'test/fixtures/auto-decide-current-declaration-6aef.json', 'test/fixtures/auto-decide-explanatory-mode-043a.json', 'test/fixtures/auto-decide-explanatory-mode-749df.json', 'test/fixtures/auto-decide-structured-77.json', 'test/helpers/auto-decision-state.ts', 'test/fixtures/auto-decide-state-cab3.json', 'bin/gstack-question-log', 'bin/gstack-question-preference', 'test/helpers/fake-plan-seed.ts', 'test/helpers/plan-seed-submission.ts', 'test/fixtures/plan-seed-cli.ts', 'test/fixtures/native-auto-decide-ag.json', 'test/fixtures/eng-seeded-completion-ai.json', 'test/helpers/plan-count-pending-exit.ts', 'test/helpers/pty-screen.ts', 'test/fixtures/pty-screen/**',
'test/fixtures/auto-decide-saved-ai.json', 'test/fixtures/auto-decide-retry-ai.json','bin/gstack-skill-start', 'bin/gstack-skill-end', 'plan-devex-review/**', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/question-tuning.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/review-dashboard.ts', 'scripts/resolvers/plan-gates.ts', 'scripts/resolvers/spec-review.ts', 'scripts/resolvers/outside-voice-steps.ts', 'scripts/resolvers/review-scope.ts', 'scripts/resolvers/outside-voice.ts', 'test/helpers/claude-pty-runner.ts', 'test/helpers/pty/**', 'test/fixtures/design-ui-boxed-question.json', 'test/helpers/hermetic-skill-runtime.ts', 'test/helpers/pty-trust-dialog.ts', 'test/skill-e2e-plan-devex-plan-mode.test.ts', 'test/helpers/plan-mode-evidence.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/fixtures/eng-d1-clipped-elision-1579.json', 'test/fixtures/eng-d2-planning-prelude-4d.json', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt', 'test/helpers/plan-scope-selection.ts', 'test/helpers/native-auto-decide.ts', 'test/fixtures/auto-decide-current-declaration-6aef.json', 'test/fixtures/auto-decide-explanatory-mode-043a.json', 'test/fixtures/auto-decide-explanatory-mode-749df.json', 'test/fixtures/auto-decide-structured-77.json', 'test/helpers/auto-decision-state.ts', 'test/fixtures/auto-decide-state-cab3.json', 'bin/gstack-question-log', 'bin/gstack-question-preference', 'test/helpers/fake-plan-seed.ts', 'test/helpers/plan-seed-submission.ts', 'test/fixtures/plan-seed-cli.ts', 'test/fixtures/native-auto-decide-ag.json', 'test/fixtures/eng-seeded-completion-ai.json', 'test/helpers/plan-count-pending-exit.ts', 'test/helpers/pty-screen.ts', 'test/fixtures/pty-screen/**',
'test/fixtures/design-scope-announcement-ao.json',
'test/fixtures/design-scope-declaration-ak.json',
@@ -307,7 +301,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
'office-hours-auto-mode': ['bin/gstack-state-root.sh', 'lib/state-root.ts', 'test/fixtures/auto-decide-recommendation-361c.json',
'test/fixtures/auto-decide-target-361c.json',
'test/fixtures/native-auto-decide-ag.json', 'test/helpers/native-auto-decide.ts', 'test/fixtures/auto-decide-current-declaration-6aef.json', 'test/fixtures/auto-decide-explanatory-mode-043a.json', 'test/fixtures/auto-decide-explanatory-mode-749df.json', 'bin/gstack-skill-start', 'bin/gstack-skill-end', 'office-hours/**', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/question-tuning.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'test/helpers/claude-pty-runner.ts', 'test/helpers/pty/**', 'test/fixtures/design-ui-boxed-question.json', 'test/helpers/hermetic-skill-runtime.ts', 'test/helpers/pty-trust-dialog.ts', 'test/skill-e2e-office-hours-auto-mode.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/fixtures/eng-d1-clipped-elision-1579.json', 'test/fixtures/eng-d2-planning-prelude-4d.json', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt',
'test/fixtures/native-auto-decide-ag.json', 'test/helpers/native-auto-decide.ts', 'test/fixtures/auto-decide-current-declaration-6aef.json', 'test/fixtures/auto-decide-explanatory-mode-043a.json', 'test/fixtures/auto-decide-explanatory-mode-749df.json', 'bin/gstack-skill-start', 'bin/gstack-skill-end', 'office-hours/**', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/question-tuning.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'test/helpers/claude-pty-runner.ts', 'test/helpers/pty/**', 'test/fixtures/design-ui-boxed-question.json', 'test/helpers/hermetic-skill-runtime.ts', 'test/helpers/pty-trust-dialog.ts', 'test/skill-e2e-office-hours-auto-mode.test.ts', 'test/helpers/plan-mode-evidence.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/fixtures/eng-d1-clipped-elision-1579.json', 'test/fixtures/eng-d2-planning-prelude-4d.json', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt',
'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/helpers/plan-skill-questions.ts', 'test/fixtures/eng-auq-validation-error.json', 'test/fixtures/bash-directory-permission.json', 'test/fixtures/design-tasks-bash-permission.json', 'test/fixtures/read-permission.json', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/helpers/plan-skill-question-events.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/helpers/auto-decision-state.ts', 'test/helpers/autoplan-artifact-digest.ts', 'test/helpers/autoplan-artifact-permission.ts', 'test/helpers/autoplan-artifact-recorder.ts', 'test/helpers/dx-selected-navigation.ts', 'test/helpers/e2e-gate.ts', 'test/helpers/eng-cache-writer-decision.ts', 'test/helpers/owned-claude-transcript.ts', 'test/helpers/plan-count-artifacts.ts', 'test/helpers/plan-count-file-permission.ts', 'test/helpers/plan-count-fixture.ts', 'test/helpers/plan-count-pending-exit.ts', 'test/helpers/plan-count-pending-question.ts', 'test/helpers/plan-count-transcript.ts', 'test/helpers/plan-floor-review.ts', 'test/helpers/plan-floor-target.ts', 'test/helpers/plan-scope-selection.ts', 'test/helpers/plan-seed-submission.ts', 'test/helpers/pty-screen.ts', 'bin/gstack-context-recovery'],
'office-hours-phase4-fork': ['bin/gstack-state-root.sh', 'lib/state-root.ts', 'bin/gstack-skill-start', 'bin/gstack-skill-end', 'office-hours/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/question-tuning.ts', 'test/helpers/llm-judge.ts', 'test/skill-e2e-office-hours-phase4.test.ts', 'bin/gstack-context-recovery' ],
'llm-judge-recommendation': ['codex/**', 'test/helpers/llm-judge.ts', 'test/llm-judge-recommendation.test.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'codex/SKILL.md.tmpl', 'scripts/resolvers/review-dashboard.ts', 'scripts/resolvers/plan-gates.ts', 'scripts/resolvers/spec-review.ts', 'scripts/resolvers/outside-voice-steps.ts', 'scripts/resolvers/review-scope.ts', 'scripts/resolvers/outside-voice.ts'],
@@ -558,23 +552,23 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
// AskUserQuestion format regression (RECOMMENDATION + Completeness: N/10)
// Fires when either template OR the two preamble resolvers change.
'plan-ceo-review-format-mode': [ 'plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completeness-section.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md', 'test/helpers/llm-judge.ts', 'test/skill-e2e-plan-format.test.ts',
'plan-ceo-review-format-mode': [ 'plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completeness-section.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md', 'test/helpers/llm-judge.ts', 'test/skill-e2e-plan-format.test.ts', 'test/helpers/plan-format-kind-note.ts',
'test/helpers/office-hours-attempt.ts', 'scripts/resolvers/tasks-section.ts'
],
'plan-ceo-review-format-approach': [ 'plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completeness-section.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md', 'test/helpers/llm-judge.ts', 'test/skill-e2e-plan-format.test.ts',
'plan-ceo-review-format-approach': [ 'plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completeness-section.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md', 'test/helpers/llm-judge.ts', 'test/skill-e2e-plan-format.test.ts', 'test/helpers/plan-format-kind-note.ts',
'test/helpers/office-hours-attempt.ts', 'scripts/resolvers/tasks-section.ts'
],
'plan-eng-review-format-coverage': [
'scripts/resolvers/learnings.ts',
"test/fixtures/plan-scope-recovery-av.json", 'plan-eng-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completeness-section.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md', 'test/helpers/llm-judge.ts', 'test/skill-e2e-plan-format.test.ts',
"test/fixtures/plan-scope-recovery-av.json", 'plan-eng-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completeness-section.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md', 'test/helpers/llm-judge.ts', 'test/skill-e2e-plan-format.test.ts', 'test/helpers/plan-format-kind-note.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts",
'scripts/resolvers/testing.ts', 'test/helpers/office-hours-attempt.ts', 'scripts/resolvers/review-dashboard.ts', 'scripts/resolvers/plan-gates.ts', 'scripts/resolvers/spec-review.ts', 'scripts/resolvers/outside-voice-steps.ts', 'scripts/resolvers/review-scope.ts', 'scripts/resolvers/outside-voice.ts'
],
'plan-eng-review-format-kind': [
'scripts/resolvers/learnings.ts',
"test/fixtures/plan-scope-recovery-av.json", 'plan-eng-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completeness-section.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md', 'test/helpers/llm-judge.ts', 'test/skill-e2e-plan-format.test.ts',
"test/fixtures/plan-scope-recovery-av.json", 'plan-eng-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completeness-section.ts', 'scripts/resolvers/preamble.ts', 'model-overlays/opus-4-7.md', 'test/helpers/llm-judge.ts', 'test/skill-e2e-plan-format.test.ts', 'test/helpers/plan-format-kind-note.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts",
'scripts/resolvers/testing.ts', 'test/helpers/office-hours-attempt.ts', 'scripts/resolvers/review-dashboard.ts', 'scripts/resolvers/plan-gates.ts', 'scripts/resolvers/spec-review.ts', 'scripts/resolvers/outside-voice-steps.ts', 'scripts/resolvers/review-scope.ts', 'scripts/resolvers/outside-voice.ts'
],
@@ -614,33 +608,6 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
// /plan-tune cathedral (T16 — 5 E2E scenarios, all gate per D12)
// Codex offering verification
'codex-offered-office-hours': [ 'office-hours/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-plan.test.ts',
'test/helpers/codex-offering-fixture.ts', 'test/fixtures/codex-offering-cdd-public.json', 'test/fixtures/codex-offering-timeout-public.json',
'test/helpers/workflow-judge-input.ts', 'test/helpers/workflow-excerpt.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/office-hours-completion.ts'],
'codex-offered-ceo-review': [ 'plan-ceo-review/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-plan.test.ts',
'test/helpers/codex-offering-fixture.ts', 'test/fixtures/codex-offering-cdd-public.json', 'test/fixtures/codex-offering-timeout-public.json',
'test/helpers/workflow-judge-input.ts', 'test/helpers/workflow-excerpt.ts',
'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/tasks-section.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/office-hours-completion.ts'],
'codex-offered-design-review': [
'test/helpers/codex-offering-fixture.ts', 'test/fixtures/codex-offering-cdd-public.json', 'test/fixtures/codex-offering-timeout-public.json',
'test/helpers/workflow-judge-input.ts', 'test/helpers/workflow-excerpt.ts',
"test/fixtures/plan-scope-recovery-av.json",
"test/fixtures/design-scope-checkpoint-at.json",'plan-design-review/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-plan.test.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts",
'scripts/resolvers/preamble/generate-ask-user-format.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/office-hours-completion.ts'],
'codex-offered-eng-review': [
'test/helpers/codex-offering-fixture.ts', 'test/fixtures/codex-offering-cdd-public.json', 'test/fixtures/codex-offering-timeout-public.json',
'test/helpers/workflow-judge-input.ts', 'test/helpers/workflow-excerpt.ts',
'scripts/resolvers/learnings.ts',
"test/fixtures/plan-scope-recovery-av.json", 'plan-eng-review/**', 'scripts/gen-skill-docs.ts', 'test/skill-e2e-plan.test.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts",
'scripts/resolvers/testing.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/review-dashboard.ts', 'scripts/resolvers/plan-gates.ts', 'scripts/resolvers/spec-review.ts', 'scripts/resolvers/outside-voice-steps.ts', 'scripts/resolvers/review-scope.ts', 'scripts/resolvers/outside-voice.ts', 'test/helpers/llm-judge.ts', 'test/helpers/office-hours-attempt.ts', 'test/helpers/office-hours-completion.ts'],
// Ship
'ship-managed-hook-refresh': ['bin/gstack-state-root.sh', 'lib/state-root.ts', 'ship/**', 'bin/gstack-redact', 'bin/gstack-config', 'scripts/gen-skill-docs.ts',
'test/helpers/ship-hook-actor.ts',
@@ -685,14 +652,14 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
// test/skill-e2e-context-skills.test.ts. These are periodic-tier because
// each one spawns claude -p and costs ~$0.20-$0.40. Collectively they
// verify the thing the /checkpoint → /context-save rename was for.
'context-save-routing': [ 'context-save/**', 'scripts/resolvers/preamble.ts', 'test/skill-e2e-context-skills.test.ts'],
'context-save-then-restore-roundtrip': ['bin/gstack-state-root.sh', 'lib/state-root.ts', 'context-save/**', 'context-restore/**', 'bin/gstack-slug', 'bin/gstack-remote-identity.sh', 'test/skill-e2e-context-skills.test.ts'],
'context-restore-fragment-match': [ 'context-restore/**', 'test/skill-e2e-context-skills.test.ts'],
'context-restore-empty-state': [ 'context-restore/**', 'test/skill-e2e-context-skills.test.ts'],
'context-restore-list-delegates': [ 'context-restore/**', 'test/skill-e2e-context-skills.test.ts'],
'context-restore-legacy-compat': [ 'context-restore/**', 'test/skill-e2e-context-skills.test.ts'],
'context-save-list-current-branch': [ 'context-save/**', 'test/skill-e2e-context-skills.test.ts'],
'context-save-list-all-branches': [ 'context-save/**', 'test/skill-e2e-context-skills.test.ts'],
'context-save-routing': [ 'context-save/**', 'scripts/resolvers/preamble.ts', 'test/skill-e2e-context-skills.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts'],
'context-save-then-restore-roundtrip': ['bin/gstack-state-root.sh', 'lib/state-root.ts', 'context-save/**', 'context-restore/**', 'bin/gstack-slug', 'bin/gstack-remote-identity.sh', 'test/skill-e2e-context-skills.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts'],
'context-restore-fragment-match': [ 'context-restore/**', 'test/skill-e2e-context-skills.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts'],
'context-restore-empty-state': [ 'context-restore/**', 'test/skill-e2e-context-skills.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts'],
'context-restore-list-delegates': [ 'context-restore/**', 'test/skill-e2e-context-skills.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts'],
'context-restore-legacy-compat': [ 'context-restore/**', 'test/skill-e2e-context-skills.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts'],
'context-save-list-current-branch': [ 'context-save/**', 'test/skill-e2e-context-skills.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts'],
'context-save-list-all-branches': [ 'context-save/**', 'test/skill-e2e-context-skills.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts'],
// Document-release
'document-release': [ 'document-release/**', 'test/skill-e2e-workflow.test.ts', 'test/fixtures/coverage-audit-fixture.ts', 'test/helpers/coverage-audit.ts', 'test/helpers/coverage-audit-evidence.ts', 'test/helpers/office-hours-attempt.ts'],
@@ -793,7 +760,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts",
'test/helpers/coverage-audit.ts', 'test/helpers/office-hours-attempt.ts', 'scripts/resolvers/testing.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/review-dashboard.ts', 'scripts/resolvers/plan-gates.ts', 'scripts/resolvers/spec-review.ts', 'scripts/resolvers/outside-voice-steps.ts', 'scripts/resolvers/review-scope.ts', 'scripts/resolvers/outside-voice.ts'
],
'ship-triage': ['bin/gstack-state-root.sh', 'lib/state-root.ts', 'ship/**', 'bin/gstack-repo-mode', 'test/skill-e2e-triage.test.ts',
'ship-triage': ['bin/gstack-state-root.sh', 'lib/state-root.ts', 'ship/**', 'bin/gstack-repo-mode', 'test/skill-e2e-triage.test.ts', 'test/helpers/ship-triage-labels.ts',
'scripts/resolvers/testing.ts'
],
'ship-docsync-completion': ['bin/gstack-state-root.sh', 'lib/state-root.ts', 'ship/**', 'document-release/**', 'test/skill-e2e-ship-docsync.test.ts', 'test/helpers/docsync-*.ts', 'test/helpers/session-runner.ts', 'test/helpers/hermetic-env.ts', 'bin/gstack-skill-start', 'bin/gstack-session-kind', 'scripts/resolvers/sections.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/gen-skill-docs.ts', 'scripts/resolvers/testing.ts', 'test/helpers/qa-checkpoint-evidence.ts', 'test/helpers/qa-functional-observer.ts', 'test/helpers/e2e-gate.ts', 'test/helpers/qa-evidence-producer.ts', 'test/helpers/qa-functional-fixture.ts', 'bin/gstack-context-recovery'],
@@ -884,24 +851,24 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
'scrape-match-path': [
'scrape/**', 'browse/src/browser-skills.ts', 'browse/src/browser-skill-commands.ts',
'browser-skills/hackernews-frontpage/**',
'test/skill-e2e-skillify.test.ts'
'test/skill-e2e-skillify.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts', 'test/helpers/skill-body-narration.ts'
],
'scrape-prototype-path': [
'scrape/**', 'browse/src/browser-skills.ts', 'browse/src/browser-skill-commands.ts',
'test/skill-e2e-skillify.test.ts'
'test/skill-e2e-skillify.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts', 'test/helpers/skill-body-narration.ts'
],
'skillify-happy-path': [
'skillify/**', 'scrape/**', 'browse/src/browser-skill-write.ts',
'browse/src/browser-skills.ts', 'browse/src/browser-skill-commands.ts',
'test/skill-e2e-skillify.test.ts'
'test/skill-e2e-skillify.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts', 'test/helpers/skill-body-narration.ts'
],
'skillify-provenance-refusal': [
'skillify/**', 'browse/src/browser-skill-write.ts',
'test/skill-e2e-skillify.test.ts'
'test/skill-e2e-skillify.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts', 'test/helpers/skill-body-narration.ts'
],
'skillify-approval-reject': [
'skillify/**', 'scrape/**', 'browse/src/browser-skill-write.ts',
'test/skill-e2e-skillify.test.ts'
'test/skill-e2e-skillify.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts', 'test/helpers/skill-body-narration.ts'
],
// Skill routing — journey-stage tests (depend on ALL skill descriptions)
@@ -909,7 +876,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
"test/fixtures/plan-scope-recovery-av.json",
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts',
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts"
@@ -918,7 +885,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
"test/fixtures/plan-scope-recovery-av.json",
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts',
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts"
@@ -927,7 +894,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
"test/fixtures/plan-scope-recovery-av.json",
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts',
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts"
@@ -936,7 +903,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
"test/fixtures/plan-scope-recovery-av.json",
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts',
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts"
@@ -945,7 +912,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
"test/fixtures/plan-scope-recovery-av.json",
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts',
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts"
@@ -954,7 +921,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
"test/fixtures/plan-scope-recovery-av.json",
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts',
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts"
@@ -963,7 +930,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
"test/fixtures/plan-scope-recovery-av.json",
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts',
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts"
@@ -972,7 +939,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
"test/fixtures/plan-scope-recovery-av.json",
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts',
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts"
@@ -981,7 +948,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
"test/fixtures/plan-scope-recovery-av.json",
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts',
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts"
@@ -990,7 +957,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
"test/fixtures/plan-scope-recovery-av.json",
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts',
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts"
@@ -1100,7 +1067,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
"test/fixtures/plan-scope-recovery-av.json",
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts',
"test/fixtures/design-scope-checkpoint-at.json", '*/SKILL.md.tmpl', 'SKILL.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/skill-routing-e2e.test.ts', 'bin/gstack-skill-start', 'test/helpers/shipped-skill-routing.ts',
"scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts"
@@ -1150,11 +1117,9 @@ export const E2E_TIERS: Record<string, 'gate' | 'periodic' | 'marathon'> = {
'skillmd-setup-discovery': 'gate',
'skillmd-no-local-binary': 'gate',
'skillmd-outside-git': 'gate',
'session-awareness': 'gate',
'operational-learning': 'gate',
// P4 first-run scaffold — periodic (onboarding, non-safety, model-touched marker)
'first-task-scaffold': 'periodic',
// QA — gate for functional, periodic for quality/benchmarks
'qa-quick': 'gate',
@@ -1194,7 +1159,6 @@ export const E2E_TIERS: Record<string, 'gate' | 'periodic' | 'marathon'> = {
'review-army-simplification-precision': 'periodic', // False-flag noise benchmark
// Office Hours
'office-hours-spec-review': 'gate',
// Brain-writeback E2E — periodic per cost (claude -p) + non-deterministic
// (model interprets the gbrain instruction). Matches nearby
// setup-gbrain-path4-* tier classification.
@@ -1218,7 +1182,6 @@ export const E2E_TIERS: Record<string, 'gate' | 'periodic' | 'marathon'> = {
// Plan reviews — gate for cheap functional, periodic for Opus quality
'plan-ceo-review': 'periodic',
'plan-ceo-review-selective': 'periodic',
'plan-ceo-review-benefits': 'gate',
'plan-ceo-review-expansion-energy': 'periodic', // Demoted from gate (2026-08 audit): Opus generator + subjective 2-axis >=4/5 LLM-judge threshold in the merge lane — the exact class siblings were demoted for (a +21-line preamble change once flipped the score). CLAUDE.md's own rule: Opus model test -> periodic.
'plan-eng-review': 'periodic',
'plan-eng-review-artifact': 'periodic',
@@ -1314,12 +1277,6 @@ export const E2E_TIERS: Record<string, 'gate' | 'periodic' | 'marathon'> = {
// /plan-tune cathedral (T16 per D12 — all gate)
// Codex offering verification
'codex-offered-office-hours': 'gate',
'codex-offered-ceo-review': 'gate',
'codex-offered-design-review': 'gate',
'codex-offered-eng-review': 'gate',
// Session Intelligence — gate for data flow, periodic for agent integration
'timeline-event-flow': 'gate', // Binary data flow (no LLM needed)
'context-recovery-artifacts': 'gate', // Preamble reads seeded artifacts
@@ -1613,9 +1570,7 @@ export const E2E_KINDS: Record<string, 'rule' | 'behavior' | 'judge'> = {
'skillmd-setup-discovery': 'rule',
'skillmd-no-local-binary': 'rule',
'skillmd-outside-git': 'rule',
'session-awareness': 'rule',
'operational-learning': 'rule',
'first-task-scaffold': 'rule',
'qa-quick': 'rule',
'qa-b6-static': 'rule',
'qa-b7-spa': 'rule',
@@ -1647,7 +1602,6 @@ export const E2E_KINDS: Record<string, 'rule' | 'behavior' | 'judge'> = {
'review-army-consensus': 'behavior',
'review-army-simplification': 'behavior',
'review-army-simplification-precision': 'behavior',
'office-hours-spec-review': 'rule',
'office-hours-brain-writeback': 'behavior',
'gbrain-roundtrip-local': 'rule',
'sync-gbrain-read-ready': 'rule',
@@ -1656,7 +1610,6 @@ export const E2E_KINDS: Record<string, 'rule' | 'behavior' | 'judge'> = {
'office-hours-builder-wildness': 'behavior',
'plan-ceo-review': 'rule',
'plan-ceo-review-selective': 'rule',
'plan-ceo-review-benefits': 'rule',
'plan-ceo-review-expansion-energy': 'behavior',
'plan-eng-review': 'rule',
'plan-eng-review-artifact': 'rule',
@@ -1704,10 +1657,6 @@ export const E2E_KINDS: Record<string, 'rule' | 'behavior' | 'judge'> = {
'plan-review-prosons-hardstop-neg': 'behavior',
'plan-review-prosons-neutral-neg': 'behavior',
'plan-tune-inspect': 'rule',
'codex-offered-office-hours': 'rule',
'codex-offered-ceo-review': 'rule',
'codex-offered-design-review': 'rule',
'codex-offered-eng-review': 'rule',
'timeline-event-flow': 'rule',
'context-recovery-artifacts': 'rule',
'context-save-writes-file': 'rule',
+11 -4
View File
@@ -193,14 +193,21 @@ export function boundaryFailures(fixture: ReturnType<typeof createBoundaryFixtur
check(!evidence.receipts.includes('VERIFY\n'), 'verifier ran after abort');
check(/abort|stop|cancel/i.test(finalText), 'actor did not acknowledge abort');
} else {
check(evidence.executions.filter(event => event.tool === 'Edit' && event.allowed).length === 1, 'expected one allowed native Edit');
// Outcome, not step count: the correction landed, the final verification
// reports the expected status, and no edit follows that verification.
const allowed = evidence.executions.filter(event => event.allowed);
const lastVerify = allowed.findLastIndex(event => event.tool === 'Bash' && /\bverify\.sh\b/.test(String(event.input.command ?? '')));
const statuses = [...evidence.receipts.matchAll(/^VERIFY_STATUS:(\d+)$/gm)].map(match => match[1]);
check(allowed.some(event => event.tool === 'Edit'), 'expected an allowed native Edit');
check(evidence.interactions.some(event => event.disposition === 'continue-investigation' && event.owner === owners[0]), 'continuation was not authorized under the acquired owner');
check(evidence.source.includes('return 2'), 'requested correction is missing');
check(evidence.receipts.match(/^VERIFY$/gm)?.length === 1, 'verifier did not run exactly once');
check((evidence.receipts.match(/^VERIFY$/gm)?.length ?? 0) >= 1, 'verifier did not run');
check(lastVerify >= 0 && !allowed.slice(lastVerify + 1).some(event => event.tool === 'Edit'), 'an edit followed the final verification');
if (fixture.id === 'investigate-owned-ending-error') {
check(evidence.receipts.includes('VERIFY_STATUS:69\n'), 'ending verifier error was not delivered');
// The fixture's ending error says to stop without retrying the verifier.
check(statuses.length === 1 && statuses[0] === '69', 'ending verifier error was not delivered once without retry');
check(/error|unavailable|cannot|could not|unable/i.test(finalText), 'ending error not acknowledged');
} else check(evidence.receipts.includes('VERIFY_STATUS:0\n'), 'verification did not succeed');
} else check(statuses.at(-1) === '0', 'final verification did not succeed');
}
return failures;
}
+12 -2
View File
@@ -10,12 +10,22 @@ export const ENG_REVIEW_EXCERPT = {
endMarker: '## Section self-check (before you finish)',
} as const;
// The question-procedure heading in the plan-review sections, with or without
// its older "CRITICAL RULE — " prefix.
export const ASK_QUESTIONS_HEADING = /^## [^\n]*How to ask questions[^\n]*$/m;
export function markerIndex(text: string, marker: string | RegExp, from: number): number {
if (typeof marker === 'string') return text.indexOf(marker, from);
const at = text.slice(from).search(marker);
return at < 0 ? -1 : from + at;
}
// Same generated two-line pointer consumed by setup-gbrain-fixture.ts.
const STOP_POINTER =
/^> \*\*STOP\.\*\* Before [^\n]*sections\/([a-z0-9-]+\.md)[^\n]*\n> in full\.[^\n]*/gm;
/** Expand on-demand sections where the agent reads them, then take the requested excerpt. */
export function readWorkflowExcerpt(skillPath: string, startMarker: string, endMarker: string | null): string {
export function readWorkflowExcerpt(skillPath: string, startMarker: string, endMarker: string | RegExp | null): string {
const secDir = path.join(ROOT, path.dirname(skillPath), 'sections');
const content = fs.readFileSync(path.join(ROOT, skillPath), 'utf-8').replace(STOP_POINTER, (_pointer, file: string) => {
const body = fs.readFileSync(path.join(secDir, file), 'utf-8')
@@ -25,7 +35,7 @@ export function readWorkflowExcerpt(skillPath: string, startMarker: string, endM
});
const start = content.indexOf(startMarker);
if (start < 0) throw new Error(`Start marker not found in ${skillPath}: "${startMarker}"`);
const end = endMarker ? content.indexOf(endMarker, start) : content.length;
const end = endMarker ? markerIndex(content, endMarker, start) : content.length;
if (end < 0) throw new Error(`End marker not found in ${skillPath}: "${endMarker}"`);
return content.slice(start, end);
}
+13 -1
View File
@@ -11,7 +11,7 @@ import { buildEvalInputIdentity, lookupEvalInputCache, sourceDependencyClosure,
type Thresholds = { clarity: number; completeness: number; actionability: number };
export interface WorkflowCacheOptions {
root: string; testName: string; skillPath: string; startMarker: string; endMarker: string | null;
root: string; testName: string; skillPath: string; startMarker: string; endMarker: string | RegExp | null;
judgeContext: string; judgeGoal: string; model?: string; thresholds: Thresholds; prompt: string; attempt: number;
references?: readonly string[];
agentCapability?: 'frontier';
@@ -135,3 +135,15 @@ export function prepareWorkflowJudgeCache(opts: WorkflowCacheOptions): {
},
};
}
/**
* Pass floors for the browse reference judge panel mean. The stored baseline
* is recorded for comparison only: a three-sample mean is too noisy for a
* no-dip ratchet, and gating on it silently raised the clarity floor to 4.
*/
export const BROWSE_JUDGE_FLOORS = { clarity: 3, completeness: 4, actionability: 4 } as const;
export function browseJudgeFloorsMet(scores: Record<keyof typeof BROWSE_JUDGE_FLOORS, number>): boolean {
return (Object.keys(BROWSE_JUDGE_FLOORS) as Array<keyof typeof BROWSE_JUDGE_FLOORS>)
.every(dim => scores[dim] >= BROWSE_JUDGE_FLOORS[dim]);
}
+3 -2
View File
@@ -1,6 +1,7 @@
/** Preserve source-file boundaries when a workflow judge reads carved skills. */
import * as fs from 'node:fs';
import * as path from 'node:path';
import { markerIndex } from './workflow-excerpt';
export interface WorkflowJudgeFile {
path: string;
@@ -84,7 +85,7 @@ export function readWorkflowJudgeInput(opts: {
root: string;
skillPath: string;
startMarker: string;
endMarker: string | null;
endMarker: string | RegExp | null;
references?: readonly string[];
}): WorkflowJudgeInput {
const sources = [{
@@ -115,7 +116,7 @@ export function readWorkflowJudgeInput(opts: {
const union = allSources.map(file => file.content).join('\n');
const start = union.indexOf(opts.startMarker);
if (start < 0) throw new Error(`Start marker not found in ${opts.skillPath}: "${opts.startMarker}"`);
const end = opts.endMarker === null ? union.length : union.indexOf(opts.endMarker, start);
const end = opts.endMarker === null ? union.length : markerIndex(union, opts.endMarker, start);
if (end < 0) throw new Error(`End marker not found in ${opts.skillPath}: "${opts.endMarker}"`);
const files: WorkflowJudgeFile[] = [];