mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-11 11:10:13 +01:00
fix: complete review dialogs and verify methodology delivery
This commit is contained in:
1 parent
ccb4e543cf
commit
eedeaeb36f
34 files changed
+2328
-46
No files matched your search
@@ -0,0 +1,144 @@
|
||||
/** Exact parent tool-delivery audit; neither preparation nor a claimed range is a Read. */
|
||||
import { createHash } from 'node:crypto';
|
||||
import { lstatSync, readFileSync, realpathSync } from 'node:fs';
|
||||
import { basename, dirname, join, sep } from 'node:path';
|
||||
import type { NativePublicToolEvent } from './plan-count-transcript';
|
||||
|
||||
export interface MethodologyReadBinding {
|
||||
phase: string;
|
||||
path: string;
|
||||
sha256: string;
|
||||
bytes: number;
|
||||
content: string;
|
||||
lines: number;
|
||||
}
|
||||
export interface AutoplanMethodReadAudit {
|
||||
phase: string;
|
||||
sessionId: string;
|
||||
dispatchToolUseId: string;
|
||||
at: string;
|
||||
passed: boolean;
|
||||
methodologyPath?: string;
|
||||
sha256?: string;
|
||||
ranges: Array<{ startLine: number; endLine: number; toolUseId: string }>;
|
||||
missing: Array<{ startLine: number; endLine: number }>;
|
||||
error?: string;
|
||||
}
|
||||
const hash = (text: string | Buffer) => createHash('sha256').update(text).digest('hex');
|
||||
const object = (x: unknown): x is Record<string, any> => x !== null && typeof x === 'object' && !Array.isArray(x);
|
||||
const identity = (x: unknown): x is string => typeof x === 'string' && x.trim().length > 0;
|
||||
const integer = (x: unknown): x is number => Number.isSafeInteger(x) && (x as number) > 0;
|
||||
const phaseOf = (prompt: string) => /^You are the independent (CEO|DESIGN|DX|ENG) reviewer for this phase\.\n/.exec(prompt)?.[1]?.toLowerCase();
|
||||
|
||||
/** Paths come from the actual dispatch but may only resolve inside this fixture's owned roots. */
|
||||
export function loadAutoplanMethodologyBinding(prompt: string, ownedRoots: string[]): MethodologyReadBinding {
|
||||
const phase = phaseOf(prompt);
|
||||
const pathLine = /^Read file: ("[^\n]+")$/m.exec(prompt)?.[1];
|
||||
if (!phase || !pathLine) throw new Error('Unrecognized native phase dispatch');
|
||||
const nativePath: unknown = JSON.parse(pathLine);
|
||||
const roots = ownedRoots.map(root => realpathSync(root));
|
||||
const read = (path: unknown): Buffer => {
|
||||
if (typeof path !== 'string' || realpathSync(path) !== path ||
|
||||
!roots.some(root => path.startsWith(root + sep))) throw new Error('Artifact outside owned fixture roots or aliased');
|
||||
const stat = lstatSync(path);
|
||||
if (!stat.isFile() || stat.size > 32 * 1024 * 1024 ||
|
||||
(process.platform !== 'win32' && (stat.mode & 0o777) !== 0o444)) throw new Error('Artifact is not immutable bounded regular data');
|
||||
return readFileSync(path);
|
||||
};
|
||||
if (typeof nativePath !== 'string' || basename(nativePath) !== 'native-prompt.md' ||
|
||||
!basename(dirname(nativePath)).startsWith(`autoplan-${phase}-`)) throw new Error('Foreign phase native input');
|
||||
const native = read(nativePath);
|
||||
const snapshot = JSON.parse(read(join(dirname(nativePath), 'snapshot.json')).toString('utf8'));
|
||||
if (snapshot.schemaVersion !== 2 || snapshot.phase !== phase || snapshot.nativePromptPath !== nativePath ||
|
||||
snapshot.nativeDispatchPrompt !== prompt || snapshot.nativePromptSha256 !== hash(native) ||
|
||||
snapshot.nativePromptBytes !== native.length || snapshot.nativePromptLines !== native.toString('utf8').split('\n').length ||
|
||||
!object(snapshot.methodology)) throw new Error('Dispatch does not match immutable snapshot');
|
||||
const identity = snapshot.methodology;
|
||||
if (typeof identity.methodologyPath !== 'string' || basename(identity.methodologyPath) !== 'methodology.md' ||
|
||||
!basename(dirname(identity.methodologyPath)).startsWith(`autoplan-${phase}-`)) throw new Error('Foreign methodology identity');
|
||||
const content = read(identity.methodologyPath);
|
||||
const manifestBytes = read(join(dirname(identity.methodologyPath), 'methodology.json'));
|
||||
const manifest = JSON.parse(manifestBytes.toString('utf8'));
|
||||
if (hash(manifestBytes) !== identity.manifestSha256 || manifest.phase !== phase ||
|
||||
manifest.methodologyPath !== identity.methodologyPath || hash(content) !== identity.sha256 ||
|
||||
manifest.sha256 !== identity.sha256 || content.length !== identity.bytes || manifest.bytes !== identity.bytes ||
|
||||
content.toString('utf8').split('\n').length !== identity.lines || manifest.lines !== identity.lines) {
|
||||
throw new Error('Methodology bytes do not match dispatched snapshot');
|
||||
}
|
||||
return { phase, path: identity.methodologyPath, content: content.toString('utf8'),
|
||||
sha256: identity.sha256, bytes: identity.bytes, lines: identity.lines };
|
||||
}
|
||||
|
||||
/**
|
||||
* The caller supplies only public records from its existing owned parent transcript reader.
|
||||
* Bind session + tool ID, actual request/result order, exact path and every delivered line.
|
||||
* A child Read, error, self-report, future result or plausible hash alone supplies no coverage.
|
||||
*/
|
||||
export function auditAutoplanMethodReads(
|
||||
events: NativePublicToolEvent[],
|
||||
bindingFor: (prompt: string) => MethodologyReadBinding,
|
||||
): AutoplanMethodReadAudit[] {
|
||||
const audits: AutoplanMethodReadAudit[] = [];
|
||||
for (let dispatchIndex = 0; dispatchIndex < events.length; dispatchIndex++) {
|
||||
const dispatch = events[dispatchIndex]!;
|
||||
if (dispatch.kind !== 'use' || dispatch.name !== 'Agent' || typeof dispatch.input?.prompt !== 'string') continue;
|
||||
const phase = phaseOf(dispatch.input.prompt);
|
||||
if (!phase) continue;
|
||||
const audit: AutoplanMethodReadAudit = { phase, sessionId: dispatch.sessionId,
|
||||
dispatchToolUseId: dispatch.toolUseId, at: dispatch.timestamp, passed: false, ranges: [], missing: [] };
|
||||
audits.push(audit);
|
||||
try {
|
||||
const dispatchTime = Date.parse(dispatch.timestamp);
|
||||
if (!identity(dispatch.sessionId) || !identity(dispatch.toolUseId) || !Number.isFinite(dispatchTime)) {
|
||||
throw new Error('Invalid native dispatch identity or timestamp');
|
||||
}
|
||||
const binding = bindingFor(dispatch.input.prompt);
|
||||
if (binding.phase !== phase || !integer(binding.lines) || binding.lines > 1_000_000 ||
|
||||
hash(binding.content) !== binding.sha256 || Buffer.byteLength(binding.content) !== binding.bytes ||
|
||||
binding.content.split('\n').length !== binding.lines) throw new Error('Invalid methodology binding');
|
||||
audit.methodologyPath = binding.path; audit.sha256 = binding.sha256;
|
||||
const lines = binding.content.split('\n');
|
||||
const covered = new Set<number>();
|
||||
const requests = new Map<string, NativePublicToolEvent>();
|
||||
const results = new Map<string, string>();
|
||||
for (const event of events.slice(0, dispatchIndex)) {
|
||||
if (event.sessionId !== dispatch.sessionId || !identity(event.toolUseId) ||
|
||||
!Number.isFinite(Date.parse(event.timestamp)) || Date.parse(event.timestamp) > dispatchTime) continue;
|
||||
if (event.kind === 'use') {
|
||||
const old = requests.get(event.toolUseId);
|
||||
if (old && JSON.stringify(old) !== JSON.stringify(event)) throw new Error('Conflicting native tool identity');
|
||||
requests.set(event.toolUseId, event); continue;
|
||||
}
|
||||
const request = requests.get(event.toolUseId);
|
||||
if (!request || request.name !== 'Read' || request.input?.file_path !== binding.path ||
|
||||
Date.parse(request.timestamp) > Date.parse(event.timestamp)) continue;
|
||||
const signature = JSON.stringify({ file: event.file, isError: event.isError });
|
||||
const previous = results.get(event.toolUseId);
|
||||
if (previous !== undefined && previous !== signature) throw new Error('Conflicting native Read results');
|
||||
results.set(event.toolUseId, signature);
|
||||
const file = event.file;
|
||||
if (event.isError || !object(file) || file.filePath !== binding.path || typeof file.content !== 'string' ||
|
||||
!integer(file.startLine) || !integer(file.numLines) || file.totalLines !== binding.lines ||
|
||||
file.startLine + file.numLines - 1 > binding.lines) continue;
|
||||
const offset = request.input?.offset ?? 1;
|
||||
const limit = request.input?.limit;
|
||||
if (offset !== file.startLine || (limit !== undefined && (!integer(limit) || file.numLines > limit))) continue;
|
||||
const expected = lines.slice(file.startLine - 1, file.startLine - 1 + file.numLines).join('\n');
|
||||
if (file.content !== expected) continue;
|
||||
if (previous !== undefined) continue;
|
||||
const end = file.startLine + file.numLines - 1;
|
||||
audit.ranges.push({ startLine: file.startLine, endLine: end, toolUseId: event.toolUseId });
|
||||
for (let line = file.startLine; line <= end; line++) covered.add(line);
|
||||
}
|
||||
for (let line = 1; line <= binding.lines; line++) {
|
||||
if (covered.has(line)) continue;
|
||||
const startLine = line;
|
||||
while (line < binding.lines && !covered.has(line + 1)) line++;
|
||||
audit.missing.push({ startLine, endLine: line });
|
||||
}
|
||||
audit.passed = audit.missing.length === 0;
|
||||
if (!audit.passed) audit.error = 'Incomplete successful parent methodology Read content before native dispatch';
|
||||
} catch (error) { audit.error = String(error); }
|
||||
}
|
||||
return audits;
|
||||
}
|
||||
@@ -12,10 +12,16 @@ export function pickCeoRecommendedApproach(fp: AskUserQuestionFingerprint): numb
|
||||
const ids = [...q.question.matchAll(/<gstack-qid:\s*([a-z0-9-]+)\s*>/gi)];
|
||||
if (ids.length !== 1 || (q.question.match(/<gstack-qid/gi)?.length ?? 0) !== 1) return null;
|
||||
const qid = ids[0]![1]!.toLowerCase();
|
||||
const decision = /^D\s*([1-9]\d*)\s*[—–:-]\s*/i.exec(q.question.trim());
|
||||
const approachId = /^plan-ceo(?:-review)?-approach(?:-selection)?(?:-d([1-9]\d*))?$/.exec(qid);
|
||||
// Native routing ids may carry the explicit decision number. An id for a
|
||||
// different decision cannot borrow this question's recommendation policy.
|
||||
const planApproachId = approachId !== null &&
|
||||
(!approachId[1] || approachId[1] === decision?.[1]);
|
||||
const question = q.question.trim().replace(/^D\s*\d+\s*[—–:-]\s*/i, '');
|
||||
// Native approach menus vary their routing id and use/follow wording. Match
|
||||
// the selector's structure, retaining the explicit recommendation below.
|
||||
const planApproach = /^plan-ceo(?:-review)?-approach(?:-selection)?$/.test(qid) &&
|
||||
const planApproach = planApproachId &&
|
||||
/^Which implementation approach should this plan (?:use|follow)\?(?:\s|$)/i.test(question);
|
||||
const testApproach = qid === 'plan-ceo-review-impl-approach' &&
|
||||
/^Which implementation approach for the [a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\(\) tests\?(?:\s|$)/i.test(question);
|
||||
@@ -23,7 +29,7 @@ export function pickCeoRecommendedApproach(fp: AskUserQuestionFingerprint): numb
|
||||
// complete direct question; the routing id alone cannot authorize a choice.
|
||||
const directQuestion = question.replace(/\s*<gstack-qid:[^>]+>\s*$/i, '').trim();
|
||||
const component = /^Which implementation approach for (?:the|this) ((?:[a-z_$][\w$.-]*\s+){0,5})(?:handler|endpoint|service|module|component|adapter|client|worker|pipeline|integration)\?$/i.exec(directQuestion);
|
||||
const componentApproach = /^plan-ceo(?:-review)?-approach(?:-selection)?$/.test(qid) &&
|
||||
const componentApproach = planApproachId &&
|
||||
component !== null && !/\b(?:and|or|then)\b/i.test(component[1]!);
|
||||
if (!planApproach && !testApproach && !componentApproach) return null;
|
||||
if (fp.options.length !== q.options.length || !fp.options.every((option, i) =>
|
||||
|
||||
@@ -203,6 +203,32 @@ function unrunEngNavigation(fp: AskUserQuestionFingerprint, question: string): n
|
||||
return manual + 1;
|
||||
}
|
||||
|
||||
/** A completed review can explain the cost of skipping its next required gate. */
|
||||
function explainedRequiredEngNavigation(fp: AskUserQuestionFingerprint, question: string): number | null {
|
||||
const call = fp.nativeCall!, q = call.questions[0]!;
|
||||
if (!call.sessionId || !call.toolUseId || call.failed !== false ||
|
||||
(fp.nativeQuestionIndex !== undefined && fp.nativeQuestionIndex !== 0) ||
|
||||
q.header.trim() !== 'Next step' || q.options.length !== 2 || fp.options.length !== 2 ||
|
||||
!fp.options.every((o, i) => o.index === i + 1 && o.label === q.options[i]!.label)) return null;
|
||||
if (call.answered === false) {
|
||||
if (call.answers !== undefined || call.answeredAt !== undefined ||
|
||||
(call.unansweredQuestionIndices !== undefined &&
|
||||
(call.unansweredQuestionIndices.length !== 1 || call.unansweredQuestionIndices[0] !== 0))) return null;
|
||||
} else if (call.answered !== true || !Array.isArray(call.unansweredQuestionIndices) ||
|
||||
call.unansweredQuestionIndices.length || Object.keys(call.answers ?? {}).length !== 1) return null;
|
||||
const compact = (s: string | undefined) => (s ?? '').replace(/\s+/g, ' ').trim();
|
||||
const match = /^What(?:['’]s| is) next after this CEO review\? ELI10: The CEO review is done and the plan is CLEARED\. But Eng Review is the required shipping gate [—–-] it covers architecture, test plan rigor, and implementation correctness in more depth\. Running it next locks in the plan before implementation starts\. Stakes if we pick wrong: Skipping eng review means the plan goes to implementation without a required gate check [—–-] leaving architecture and test-correctness gaps unverified\. Recommendation: ([A-Z]) because the dashboard shows Eng Review at 0 runs [—–-] required gate, not yet cleared\. Note: options differ in kind, not coverage [—–-] no completeness score\.$/.exec(compact(question));
|
||||
if (!match) return null;
|
||||
const labels = q.options.map(o => o.label.trim().replace(/^[A-Z][).]\s*/, '').replace(/\s*\(Recommended\)$/, ''));
|
||||
const run = labels.indexOf('Run /plan-eng-review next'), manual = labels.indexOf('Skip — handle reviews manually');
|
||||
if (run < 0 || manual < 0 || run === manual || !q.options[run]!.label.startsWith(`${match[1]}) `)) return null;
|
||||
// All question prose and each role-specific description must be closed
|
||||
// navigation. The risk explanation is not a new CEO repair decision.
|
||||
if (!/^Required shipping gate\. Covers implementation correctness, test plan rigor, and any architecture concerns\. Takes ~[1-9]\d* minutes\.$/.test(compact(q.options[run]!.description)) ||
|
||||
compact(q.options[manual]!.description) !== 'Proceed to implementation without the eng review gate. CEO review findings still apply.') return null;
|
||||
return manual + 1;
|
||||
}
|
||||
|
||||
/** Shared closed-review guards; next-review sequencing is still navigation. */
|
||||
function closedNavigationContext(context: string): boolean {
|
||||
const unfinished = context.replace(/\b(?:no|0)\s+unresolved\s+(?:decisions|gaps|issues|findings)\b/gi, '');
|
||||
@@ -238,6 +264,8 @@ function manualHandoffIndex(fp: AskUserQuestionFingerprint): number | null {
|
||||
const questionText = declaration.replace(/<gstack-qid:[^>]+>/gi, '').trim();
|
||||
const unrunNavigation = id ? unrunEngNavigation(fp, questionText) : null;
|
||||
if (unrunNavigation !== null) return unrunNavigation;
|
||||
const explainedNavigation = id ? explainedRequiredEngNavigation(fp, questionText) : null;
|
||||
if (explainedNavigation !== null) return explainedNavigation;
|
||||
const recappedNavigation = Boolean(id) &&
|
||||
/^What(?:['’]s|\s+is)\s+the\s+next\s+(?:steps?|review)\s+after\s+(?:this|the)\s+CEO\s+review\?$/i.test(questionText) &&
|
||||
q.options.some(option => resolvedCeoRecap(option.description ?? ''));
|
||||
|
||||
@@ -187,7 +187,7 @@ function hasProseStaleFillFinding(report: string): boolean {
|
||||
const context = text + (independent ? '' : ' ' + normalize(next));
|
||||
|
||||
const finding = /\b(?:P[0-3]|missing|gap|bug|defect|violat\w*|unsafe|incorrect)\b|\bno\s+mention\s+of\s+(?:this|the)\s+race\b/i.test(context);
|
||||
const subsequentRead = /\b(?:next|later|subsequent|new|fresh)\s+(?:read\w*|request\w*|caller\w*)\b/i.test(context);
|
||||
const subsequentRead = /\b(?:next|later|subsequent|new|fresh|future)\s+(?:read\w*|request\w*|caller\w*)\b/i.test(context);
|
||||
const remedy = context.split(/[.!?]\s+/).some(sentence =>
|
||||
(/\b(?:guard|serialize|serialise|coordinate|prevent|reject|skip)\w*\b/i.test(sentence) &&
|
||||
/\b(?:cache|fill|refill|write|mutation|invalidation)\w*\b/i.test(sentence)) ||
|
||||
@@ -227,7 +227,7 @@ function hasProseStaleFillFinding(report: string): boolean {
|
||||
const originalCaller = /\b(?:original|already[- ]pending)\s+(?:pending\s+)?(?:caller|reader|request)\b|\bpending\s+caller\b/i.test(claim);
|
||||
const onlyEarlierReturn = originalCaller && /\b(?:return|receiv|observ)\w*\b/i.test(claim)
|
||||
&& /\b(?:old|earlier|previous|pre[- ]write)\s+(?:snapshot|value|result|version)\b/i.test(claim)
|
||||
&& !fillPattern.test(claim) && !/\b(?:next|later|subsequent|new|fresh)\s+(?:read\w*|request\w*|caller\w*)\b/i.test(claim);
|
||||
&& !fillPattern.test(claim) && !/\b(?:next|later|subsequent|new|fresh|future)\s+(?:read\w*|request\w*|caller\w*)\b/i.test(claim);
|
||||
if (!(onlyEarlierReturn && subsequentRead && (violation || remedy))) return false;
|
||||
}
|
||||
return finding || subsequentRead || remedy;
|
||||
|
||||
@@ -1346,6 +1346,31 @@ function nativePacketQuestionIndex(visible: string, call: NativePlanQuestionCall
|
||||
return matched.length === 1 ? matched[0]! : null;
|
||||
}
|
||||
|
||||
/** Notes can share the left option row while remaining aligned to the preview. */
|
||||
function hasSidebarPreviewNotes(visible: string): boolean {
|
||||
// The footer and active pane establish the protocol. A notes phrase in an
|
||||
// option label alone is insufficient: require its aligned, complete box.
|
||||
if (!/(?:^|\n)[\t │┃]*(?:[☐□][^\n]+|←[^\n]*[☐☒][^\n]*✔\s*Submit\s*→)[\s\S]*❯\s*[1-9]\.[\s\S]*\nEnter\s+to\s+select\s*·\s*↑\/↓\s+to\s+navigate\s*·\s*n\s+to\s+add\s+notes\s*·\s*(?:Tab\s+to\s+switch\s+questions\s*·\s*)?Esc\s+to\s+cancel\s*$/i.test(visible)) return false;
|
||||
const header = [...visible.matchAll(/(?:^|\n)[\t │┃]*(?:[☐□][^\n]+|←[^\n]*[☐☒][^\n]*✔\s*Submit\s*→)/g)].at(-1);
|
||||
if (!header) return false;
|
||||
const lines = visible.slice(header.index).split('\n');
|
||||
return lines.some((line, notesIndex) => {
|
||||
if (!/[\t ]{2,}Notes: press n to add notes[\t ]*$/i.test(line)) return false;
|
||||
const column = line.indexOf('Notes:');
|
||||
// The hint may align with a wrapped continuation of the option label.
|
||||
let optionIndex = notesIndex;
|
||||
while (optionIndex > 0 && /^[\t ]{4,}\S[^│┌└]*$/.test(lines[optionIndex]!.slice(0, column).trimEnd())) optionIndex--;
|
||||
if (!/^[\t ]*(?:❯\s*)?[1-9]\.\s*\S[^│┌└]*$/.test(lines[optionIndex]!.slice(0, column).trimEnd())) return false;
|
||||
const before = lines.slice(0, notesIndex);
|
||||
const top = before.findLastIndex(row => /^┌─+┐[\t ]*$/.test(row.slice(column)));
|
||||
const bottom = before.findLastIndex(row => /^└─+┘[\t ]*$/.test(row.slice(column)));
|
||||
const width = top < 0 ? 0 : before[top]!.slice(column).trimEnd().length;
|
||||
return top >= 0 && bottom > top + 1 && before[bottom]!.slice(column).trimEnd().length === width &&
|
||||
before.slice(top + 1, bottom).every(row => /^│[^\n]*│[\t ]*$/.test(row.slice(column)) &&
|
||||
row.slice(column).trimEnd().length === width);
|
||||
});
|
||||
}
|
||||
|
||||
/** Preview digits focus an option; ordinary native digits accept or toggle it. */
|
||||
export function planCountQuestionInput(visible: string, fp: AskUserQuestionFingerprint, index: number): string {
|
||||
if (!Number.isInteger(index) || index < 1 || index > 9) throw new RangeError(`Invalid numbered option: ${index}`);
|
||||
@@ -1357,7 +1382,7 @@ export function planCountQuestionInput(visible: string, fp: AskUserQuestionFinge
|
||||
// flushed yet. Require the complete active pane, not a quoted notes hint.
|
||||
const normalized = stripPtyResidue(visible).replace(/\r+\n?/g, '\n');
|
||||
const preview = /(?:^|\n)[\t │┃]*(?:[☐□][^\n]+|←[^\n]*[☐☒][^\n]*✔\s*Submit\s*→)[\s\S]*❯\s*[1-9]\.[\s\S]*\n[\t │┃]*Notes:[^\n]*\n[\s\S]*\nEnter\s+to\s+select\s*·\s*↑\/↓\s+to\s+navigate\s*·\s*n\s+to\s+add\s+notes\s*·\s*(?:Tab\s+to\s+switch\s+questions\s*·\s*)?Esc\s+to\s+cancel\s*$/i.test(normalized);
|
||||
if (preview) return `${index}\r`;
|
||||
if (preview || hasSidebarPreviewNotes(normalized)) return `${index}\r`;
|
||||
if (native) return String(index);
|
||||
if (/❯?\s*[1-9]\.\s*\[[ ✓✔xX]\]/m.test(visible)) return `${index}\r`;
|
||||
// Native JSONL may flush only after submission. Its complete tab bar and
|
||||
@@ -1883,7 +1908,7 @@ function isCompletedDxHandoff(call: NativePlanQuestionCall): boolean {
|
||||
if (!call.answered || call.failed || call.questions.length !== 1 ||
|
||||
!Array.isArray(call.unansweredQuestionIndices) || call.unansweredQuestionIndices.length) return false;
|
||||
const q = call.questions[0]!;
|
||||
if (resolvedDxTaskNavigation(call) || specifiedDxTaskNavigation(call)) return true;
|
||||
if (resolvedDxTaskNavigation(call) || specifiedDxTaskNavigation(call) || architecturalDxTaskNavigation(call)) return true;
|
||||
const header = q.header.trim().replace(/^D\s*\d+\s*(?:[—–:-]\s*)?/i, '');
|
||||
const question = q.question.replace(/^D\s*\d+\s*[—–:-]\s*/i, '');
|
||||
const completionLines: string[] = [];
|
||||
@@ -1952,6 +1977,30 @@ function isCompletedDxHandoff(call: NativePlanQuestionCall): boolean {
|
||||
q.options.some(o => o.label === call.answers?.[q.question]);
|
||||
}
|
||||
|
||||
/** The next gate may validate architecture already decided by this DX review. */
|
||||
function architecturalDxTaskNavigation(call: NativePlanQuestionCall): boolean {
|
||||
const q = call.questions[0]!;
|
||||
if (call.answered !== true || call.failed !== false || !call.sessionId || !call.toolUseId ||
|
||||
q.multiSelect || q.header.trim() !== 'Next steps' || q.options.length !== 3 ||
|
||||
new Set(q.options.map(o => o.label)).size !== 3 || Object.keys(call.answers ?? {}).length !== 1) return false;
|
||||
const compact = (s: string | undefined) => (s ?? '').replace(/\s+/g, ' ').trim();
|
||||
const match = /^D[1-9]\d* [—–-] Next steps: DX Review is complete \((?:10|[0-9])\/10 → (?:10|[0-9])\/10, ([1-9]\d*) P1 tasks, TTHW target achievable\)\. The ([1-9]\d*) fixes include architectural decisions \(demo CI exemption, arg order normalization\) that should go through an engineering gate\. What next\? <gstack-qid:plan-devex-next-steps>$/.exec(compact(q.question));
|
||||
if (!match || match[1] !== match[2]) return false;
|
||||
const labels = q.options.map(o => o.label.trim().replace(/\s*\(Recommended\)$/, ''));
|
||||
const expected = ['Run /plan-eng-review next', 'Ready to implement — run /devex-review after shipping', "Skip, I'll handle next steps manually"];
|
||||
const descriptions = [
|
||||
'The demo CI exemption and argument order change are architectural decisions. Eng review validates the approach before implementation and is the required shipping gate.',
|
||||
`Skip eng review and implement the ${match[1]} tasks directly. Run /devex-review on the live SDK to verify the TTHW target was actually hit.`,
|
||||
'Take the plan file and implementation tasks and proceed independently.',
|
||||
];
|
||||
// Consume every offered description in its own navigation role; an appended
|
||||
// remedy or unapproved scope change still requires a later report write.
|
||||
return new Set(labels).size === 3 && labels.every((label, index) => {
|
||||
const role = expected.indexOf(label);
|
||||
return role >= 0 && compact(q.options[index]!.description) === descriptions[role];
|
||||
}) && q.options.some(o => o.label === call.answers?.[q.question]);
|
||||
}
|
||||
|
||||
/** A completed DX recap can offer navigation over already specified tasks. */
|
||||
function specifiedDxTaskNavigation(call: NativePlanQuestionCall): boolean {
|
||||
const q = call.questions[0]!;
|
||||
|
||||
@@ -21,6 +21,19 @@ export interface NativePlanQuestionCall {
|
||||
answeredAt?: string;
|
||||
}
|
||||
|
||||
/** Optional public tool projection for the Autoplan delivery audit; never thinking. */
|
||||
export interface NativePublicToolEvent {
|
||||
sessionId: string;
|
||||
timestamp: string;
|
||||
toolUseId: string;
|
||||
kind: 'use' | 'result';
|
||||
name?: string;
|
||||
input?: Record<string, unknown>;
|
||||
content?: unknown;
|
||||
file?: unknown;
|
||||
isError?: boolean;
|
||||
}
|
||||
|
||||
export interface PlanCountTranscript {
|
||||
status: 'missing' | 'ready' | 'error';
|
||||
calls: NativePlanQuestionCall[];
|
||||
@@ -56,7 +69,9 @@ function validQuestions(value: unknown): value is NativePlanQuestion[] {
|
||||
* Partial final lines remain pending; missing/foreign/sidechain records add
|
||||
* no coverage. Traversal stays inside the owned config's projects directory.
|
||||
*/
|
||||
export function readPlanCountTranscript(configDir: string, cwd: string): PlanCountTranscript {
|
||||
export function readPlanCountTranscript(configDir: string, cwd: string,
|
||||
onPublicToolEvent?: (event: NativePublicToolEvent) => void,
|
||||
): PlanCountTranscript {
|
||||
const calls = new Map<string, NativePlanQuestionCall>();
|
||||
const assistantMessages: PlanCountTranscript['assistantMessages'] = [];
|
||||
const planReadyRequests = new Map<string, NonNullable<PlanCountTranscript['planReadyRequests']>[number]>();
|
||||
@@ -88,6 +103,19 @@ export function readPlanCountTranscript(configDir: string, cwd: string): PlanCou
|
||||
matched = true;
|
||||
for (const block of record.message.content) {
|
||||
if (!object(block)) continue;
|
||||
if (onPublicToolEvent && validTimestamp(record.timestamp)) {
|
||||
if (record.message.role === 'assistant' && block.type === 'tool_use' &&
|
||||
typeof block.id === 'string' && typeof block.name === 'string' && object(block.input)) {
|
||||
onPublicToolEvent({ sessionId: record.sessionId, timestamp: record.timestamp,
|
||||
toolUseId: block.id, kind: 'use', name: block.name, input: block.input });
|
||||
} else if (record.message.role === 'user' && block.type === 'tool_result' &&
|
||||
typeof block.tool_use_id === 'string') {
|
||||
onPublicToolEvent({ sessionId: record.sessionId, timestamp: record.timestamp,
|
||||
toolUseId: block.tool_use_id, kind: 'result', content: block.content,
|
||||
file: record.toolUseResult?.file, isError: block.is_error === true });
|
||||
}
|
||||
}
|
||||
|
||||
if (record.message.role === 'assistant' && block.type === 'text' &&
|
||||
typeof block.text === 'string' && block.text.trim() && validTimestamp(record.timestamp)) {
|
||||
assistantMessages.push({ sessionId: record.sessionId, text: block.text, timestamp: record.timestamp });
|
||||
|
||||
@@ -139,7 +139,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
// Each one tests behavior the SDK harness can't observe (rendered TTY,
|
||||
// numbered-option lists, multi-phase ordering, idempotency state echo).
|
||||
'auq-format-gate': ['plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completeness-section.ts', 'scripts/resolvers/preamble.ts', 'test/helpers/auq-sdk-capture.ts', 'test/helpers/session-runner.ts', 'test/helpers/llm-judge.ts', 'test/skill-e2e-ask-user-question-format-compliance.test.ts'],
|
||||
'plan-ceo-mode-routing': ['plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-native-input.test.ts', 'test/helpers/pty-screen.ts', 'test/pty-screen.test.ts', 'test/pty-screen-session.test.ts', 'test/fixtures/pty-screen/**', 'test/helpers/hermetic-skill-runtime.ts', 'test/hermetic-skill-runtime.test.ts', 'test/helpers/pty-trust-dialog.ts', 'test/pty-trust-dialog.test.ts', 'test/helpers/ceo-mode-option.ts', 'test/ceo-mode-option.test.ts', 'test/pty-option-selection.test.ts', 'test/helpers/plan-count-transcript.ts', 'test/plan-count-transcript.test.ts', 'test/helpers/plan-count-fixture.ts', 'test/plan-count-fixture.test.ts', 'test/plan-count-prerequisite-n.test.ts', 'test/fixtures/ceo-prerequisite-n-call.json', 'test/skill-e2e-plan-ceo-mode-routing.test.ts', 'test/ceo-mode-posture-native.test.ts', 'test/fixtures/ceo-hold-posture-l.json', 'test/ceo-mode-labels-native.test.ts', 'test/fixtures/ceo-mode-labels-l.json', 'test/plan-count-checkbox.test.ts', 'test/fixtures/ceo-checkbox-l.screen.txt', 'test/ceo-mode-prerequisite.test.ts', 'test/fixtures/ceo-mode-prerequisite-o-calls.json', 'test/fixtures/ceo-mode-prerequisite-q-call.json', 'test/plan-count-preview-footer.test.ts', 'test/fixtures/ceo-preview-u-call.json', 'test/fixtures/ceo-preview-u-screen.txt', 'test/ceo-posture-packet.test.ts', 'test/fixtures/design-preview-v-screen.txt', 'test/plan-count-truncated-question.test.ts', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt'],
|
||||
'plan-ceo-mode-routing': ['plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-native-input.test.ts', 'test/helpers/pty-screen.ts', 'test/pty-screen.test.ts', 'test/pty-screen-session.test.ts', 'test/fixtures/pty-screen/**', 'test/helpers/hermetic-skill-runtime.ts', 'test/hermetic-skill-runtime.test.ts', 'test/helpers/pty-trust-dialog.ts', 'test/pty-trust-dialog.test.ts', 'test/helpers/ceo-mode-option.ts', 'test/ceo-mode-option.test.ts', 'test/pty-option-selection.test.ts', 'test/helpers/plan-count-transcript.ts', 'test/plan-count-transcript.test.ts', 'test/helpers/plan-count-fixture.ts', 'test/plan-count-fixture.test.ts', 'test/plan-count-prerequisite-n.test.ts', 'test/fixtures/ceo-prerequisite-n-call.json', 'test/skill-e2e-plan-ceo-mode-routing.test.ts', 'test/ceo-mode-posture-native.test.ts', 'test/fixtures/ceo-hold-posture-l.json', 'test/ceo-mode-labels-native.test.ts', 'test/fixtures/ceo-mode-labels-l.json', 'test/plan-count-checkbox.test.ts', 'test/fixtures/ceo-checkbox-l.screen.txt', 'test/ceo-mode-prerequisite.test.ts', 'test/fixtures/ceo-mode-prerequisite-o-calls.json', 'test/fixtures/ceo-mode-prerequisite-q-call.json', 'test/plan-count-preview-footer.test.ts', 'test/fixtures/ceo-preview-u-call.json', 'test/fixtures/ceo-preview-u-screen.txt', 'test/ceo-posture-packet.test.ts', 'test/fixtures/design-preview-v-screen.txt', 'test/plan-count-truncated-question.test.ts', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt', 'test/fixtures/ceo-mode-preview-aa-screen.txt', 'test/fixtures/ceo-count-mode-preview-aa-screen.txt'],
|
||||
'plan-design-with-ui-scope': ['plan-design-review/**', 'test/fixtures/plans/ui-heavy-feature.md', 'test/helpers/claude-pty-runner.ts', 'test/helpers/hermetic-skill-runtime.ts', 'test/hermetic-skill-runtime.test.ts', 'test/helpers/pty-trust-dialog.ts', 'test/pty-trust-dialog.test.ts', 'test/skill-e2e-plan-design-with-ui.test.ts', 'test/plan-count-truncated-question.test.ts', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt'],
|
||||
'ship-idempotency-pty': ['ship/**', 'bin/gstack-next-version', 'bin/gstack-version-bump', 'scripts/resolvers/sections.ts', 'lib/worktree.ts', 'test/helpers/claude-pty-runner.ts', 'test/helpers/hermetic-skill-runtime.ts', 'test/hermetic-skill-runtime.test.ts', 'test/helpers/pty-trust-dialog.ts', 'test/pty-trust-dialog.test.ts', 'test/skill-e2e-ship-idempotency.test.ts', 'test/plan-count-truncated-question.test.ts', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt'],
|
||||
'tpa-present': ['scripts/resolvers/third-party-actions.ts', 'ship/SKILL.md.tmpl', 'ship/sections/apple-release.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/helpers/session-runner.ts', 'test/skill-e2e-third-party-actions.test.ts'],
|
||||
@@ -148,22 +148,22 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'tpa-absent-darwin': ['scripts/resolvers/third-party-actions.ts', 'ship/SKILL.md.tmpl', 'ship/sections/apple-release.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/helpers/session-runner.ts', 'test/skill-e2e-third-party-actions.test.ts'],
|
||||
'tpa-apple-ban': ['scripts/resolvers/third-party-actions.ts', 'ship/SKILL.md.tmpl', 'ship/sections/apple-release.md.tmpl', 'scripts/gen-skill-docs.ts', 'test/helpers/session-runner.ts', 'test/skill-e2e-third-party-actions.test.ts'],
|
||||
'ship-section-loading': ['ship/**', 'scripts/resolvers/sections.ts', 'scripts/gen-skill-docs.ts', 'test/helpers/auq-sdk-capture.ts', 'test/helpers/session-runner.ts', 'test/session-runner-tools.test.ts', 'test/skill-e2e-ship-section-loading.test.ts'],
|
||||
'plan-ceo-section-loading': ['plan-ceo-review/**', 'test/skill-ceo-section-ordering.test.ts', 'scripts/resolvers/sections.ts', 'scripts/gen-skill-docs.ts', 'test/helpers/auq-sdk-capture.ts', 'test/helpers/session-runner.ts', 'test/session-runner-tools.test.ts', 'test/helpers/ceo-section-loading-fixture.ts', 'test/ceo-section-loading-fixture.test.ts', 'test/skill-e2e-plan-ceo-review-section-loading.test.ts', 'test/fixtures/ceo-section-loading-l-report.md', 'test/fixtures/ceo-section-loading-q-report.md', 'test/fixtures/ceo-section-r-rejected-report.md', 'test/fixtures/ceo-section-s-trace-report.md', 'test/fixtures/ceo-section-u-report.md', 'test/fixtures/ceo-section-y-report.md'],
|
||||
'plan-ceo-section-loading': ['plan-ceo-review/**', 'test/skill-ceo-section-ordering.test.ts', 'scripts/resolvers/sections.ts', 'scripts/gen-skill-docs.ts', 'test/helpers/auq-sdk-capture.ts', 'test/helpers/session-runner.ts', 'test/session-runner-tools.test.ts', 'test/helpers/ceo-section-loading-fixture.ts', 'test/ceo-section-loading-fixture.test.ts', 'test/skill-e2e-plan-ceo-review-section-loading.test.ts', 'test/fixtures/ceo-section-loading-l-report.md', 'test/fixtures/ceo-section-loading-q-report.md', 'test/fixtures/ceo-section-r-rejected-report.md', 'test/fixtures/ceo-section-s-trace-report.md', 'test/fixtures/ceo-section-u-report.md', 'test/fixtures/ceo-section-y-report.md', 'test/fixtures/ceo-section-aa-report.md'],
|
||||
// Data-driven behavioral guard for the 'plan'/'prompt' carves (eng, design,
|
||||
// devex, office-hours + future PR2 carves). One file iterating CARVE_GUARDS;
|
||||
// the selector sets GSTACK_CARVE_SKILL=<name> to scope cost to the changed
|
||||
// skill (D-CODEX A). Touching the registry/helper or sections.ts runs all.
|
||||
'carve-section-loading': ['scripts/resolvers/composition.ts', 'test/autoplan-review-discovery.test.ts', 'test/autoplan-phase-order.test.ts', 'design-html/**', 'design-shotgun/**', 'qa/**', 'browse/**', 'retro/**', 'autoplan/**', 'spec/**', 'setup-gbrain/**', 'review/**', 'codex/**', 'land-and-deploy/**', 'plan-eng-review/**', 'plan-design-review/**', 'plan-devex-review/**', 'office-hours/**', 'document-release/**', 'design-consultation/**', 'cso/**', 'test/helpers/carve-guards.ts', 'scripts/resolvers/sections.ts', 'scripts/resolvers/redact-doc.ts', 'scripts/gen-skill-docs.ts', 'test/helpers/auq-sdk-capture.ts', 'test/helpers/session-runner.ts', 'test/session-runner-tools.test.ts', 'test/carve-section-loading.test.ts', 'bin/gstack-autoplan-snapshot.ts', 'test/autoplan-snapshot.test.ts', 'test/autoplan-init.test.ts', 'test/autoplan-obligations.test.ts', 'test/fixtures/autoplan/t-ceo-omitted-obligations.json', 'test/fixtures/autoplan/u-ceo-original-loss.json', 'test/fixtures/autoplan/v-ceo-dangling-references.json'],
|
||||
'autoplan-chain-pty': ['scripts/resolvers/composition.ts', 'test/autoplan-review-discovery.test.ts', 'test/autoplan-phase-order.test.ts', 'autoplan/**', 'plan-ceo-review/**', 'plan-design-review/**', 'plan-eng-review/**', 'plan-devex-review/**', 'test/fixtures/plans/autoplan-dashboard.md', 'test/autoplan-chain-fixture.test.ts', 'test/helpers/plan-count-fixture.ts', 'test/plan-count-fixture.test.ts', 'test/plan-count-prerequisite-n.test.ts', 'test/fixtures/ceo-prerequisite-n-call.json', 'bin/gstack-config', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-native-input.test.ts', 'test/helpers/pty-screen.ts', 'test/pty-screen.test.ts', 'test/pty-screen-session.test.ts', 'test/fixtures/pty-screen/**', 'test/helpers/hermetic-skill-runtime.ts', 'test/hermetic-skill-runtime.test.ts', 'test/helpers/pty-trust-dialog.ts', 'test/pty-trust-dialog.test.ts', 'test/helpers/autoplan-setup-question.ts', 'test/autoplan-setup-question.test.ts', 'test/helpers/autoplan-phase-observer.ts', 'test/autoplan-phase-observer.test.ts', 'test/helpers/plan-count-transcript.ts', 'test/plan-count-transcript.test.ts', 'test/helpers/plan-count-artifacts.ts', 'test/plan-count-artifacts.test.ts', 'test/skill-e2e-autoplan-chain.test.ts', 'bin/gstack-autoplan-snapshot.ts', 'test/autoplan-snapshot.test.ts', 'test/autoplan-init.test.ts', 'test/autoplan-obligations.test.ts', 'test/fixtures/autoplan/t-ceo-omitted-obligations.json', 'test/plan-count-checkbox.test.ts', 'test/fixtures/ceo-checkbox-l.screen.txt', 'test/helpers/eval-budgets.ts', 'test/autoplan-eval-budget.test.ts', 'test/eval-budgets-policy.test.ts', 'test/eval-detach-timeout-floor.test.ts', 'scripts/test-paid-shards.ts', '.github/workflows/evals-periodic.yml', 'test/fixtures/autoplan-routing-n-screen.txt', 'test/autoplan-routing-o.test.ts', 'test/fixtures/autoplan-routing-o-screen.txt', 'test/autoplan-setup-packet-o.test.ts', 'test/fixtures/autoplan-setup-packet-o-screen.txt', 'test/fixtures/autoplan-setup-packet-o-call.json', 'test/fixtures/autoplan/u-ceo-original-loss.json', 'test/fixtures/autoplan/v-ceo-dangling-references.json', 'test/fixtures/autoplan-setup-z-packet.json', 'test/plan-count-truncated-question.test.ts', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt'],
|
||||
'autoplan-chain-pty': ['scripts/resolvers/composition.ts', 'test/autoplan-review-discovery.test.ts', 'test/autoplan-phase-order.test.ts', 'autoplan/**', 'plan-ceo-review/**', 'plan-design-review/**', 'plan-eng-review/**', 'plan-devex-review/**', 'test/fixtures/plans/autoplan-dashboard.md', 'test/autoplan-chain-fixture.test.ts', 'test/helpers/plan-count-fixture.ts', 'test/plan-count-fixture.test.ts', 'test/plan-count-prerequisite-n.test.ts', 'test/fixtures/ceo-prerequisite-n-call.json', 'bin/gstack-config', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-native-input.test.ts', 'test/helpers/pty-screen.ts', 'test/pty-screen.test.ts', 'test/pty-screen-session.test.ts', 'test/fixtures/pty-screen/**', 'test/helpers/hermetic-skill-runtime.ts', 'test/hermetic-skill-runtime.test.ts', 'test/helpers/pty-trust-dialog.ts', 'test/pty-trust-dialog.test.ts', 'test/helpers/autoplan-setup-question.ts', 'test/autoplan-setup-question.test.ts', 'test/helpers/autoplan-phase-observer.ts', 'test/autoplan-phase-observer.test.ts', 'test/helpers/plan-count-transcript.ts', 'test/plan-count-transcript.test.ts', 'test/helpers/plan-count-artifacts.ts', 'test/plan-count-artifacts.test.ts', 'test/skill-e2e-autoplan-chain.test.ts', 'bin/gstack-autoplan-snapshot.ts', 'test/autoplan-snapshot.test.ts', 'test/autoplan-init.test.ts', 'test/autoplan-obligations.test.ts', 'test/fixtures/autoplan/t-ceo-omitted-obligations.json', 'test/plan-count-checkbox.test.ts', 'test/fixtures/ceo-checkbox-l.screen.txt', 'test/helpers/eval-budgets.ts', 'test/autoplan-eval-budget.test.ts', 'test/eval-budgets-policy.test.ts', 'test/eval-detach-timeout-floor.test.ts', 'scripts/test-paid-shards.ts', '.github/workflows/evals-periodic.yml', 'test/fixtures/autoplan-routing-n-screen.txt', 'test/autoplan-routing-o.test.ts', 'test/fixtures/autoplan-routing-o-screen.txt', 'test/autoplan-setup-packet-o.test.ts', 'test/fixtures/autoplan-setup-packet-o-screen.txt', 'test/fixtures/autoplan-setup-packet-o-call.json', 'test/fixtures/autoplan/u-ceo-original-loss.json', 'test/fixtures/autoplan/v-ceo-dangling-references.json', 'test/fixtures/autoplan-setup-z-packet.json', 'test/plan-count-truncated-question.test.ts', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt', 'test/helpers/autoplan-method-read-audit.ts', 'test/autoplan-method-read-audit.test.ts', 'test/fixtures/autoplan-method-read-aa-events.json'],
|
||||
|
||||
// Per-finding AskUserQuestion count + review-report-at-bottom assertion.
|
||||
// Each test drives its skill end-to-end; touchfiles include preamble +
|
||||
// completion-status resolvers because they affect question cadence and
|
||||
// terminal output (the regression surface this test catches).
|
||||
'plan-ceo-finding-count': ['bin/gstack-config', 'bin/gstack-skill-start', 'bin/gstack-skill-end', 'plan-ceo-review/**', 'test/skill-ceo-section-ordering.test.ts', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-native-input.test.ts', 'test/helpers/pty-screen.ts', 'test/pty-screen.test.ts', 'test/pty-screen-session.test.ts', 'test/fixtures/pty-screen/**', 'test/helpers/hermetic-skill-runtime.ts', 'test/hermetic-skill-runtime.test.ts', 'test/helpers/claude-pty-runner.unit.test.ts', 'test/plan-count-completion.test.ts', 'test/plan-count-dx-handoff.test.ts', 'test/fixtures/devex-handoff-n-call.json', 'test/helpers/ceo-completion-handoff.ts', 'test/ceo-completion-handoff.test.ts', 'test/ceo-count-s-terminals.test.ts', 'test/fixtures/ceo-count-s-paired.json', 'test/fixtures/ceo-completion-handoff-calls.json', 'test/fixtures/ceo-completion-handoff-j-calls.json', 'test/fixtures/ceo-completion-handoff-k-calls.json', 'test/helpers/pty-trust-dialog.ts', 'test/pty-trust-dialog.test.ts', 'test/helpers/plan-count-transcript.ts', 'test/helpers/plan-count-pending-exit.ts', 'test/plan-count-pending-exit.test.ts', 'test/plan-count-transcript.test.ts', 'test/helpers/plan-count-artifacts.ts', 'test/plan-count-artifacts.test.ts', 'test/helpers/eval-store.ts', 'test/helpers/plan-count-fixture.ts', 'test/plan-count-fixture.test.ts', 'test/plan-count-timeout.test.ts', 'test/plan-count-navigation-r.test.ts', 'test/plan-count-prerequisite-n.test.ts', 'test/fixtures/ceo-prerequisite-n-call.json', 'test/skill-e2e-plan-ceo-finding-count.test.ts', 'test/ceo-completion-handoff-l.test.ts', 'test/fixtures/ceo-completion-handoff-l-calls.json', 'test/ceo-completion-handoff-m.test.ts', 'test/fixtures/ceo-completion-handoff-m-call.json', 'test/fixtures/devex-review-l-calls.json', 'test/plan-count-checkbox.test.ts', 'test/fixtures/ceo-checkbox-l.screen.txt', 'test/fixtures/ceo-handoff-n-calls.json', 'test/ceo-completion-handoff-o.test.ts', 'test/fixtures/ceo-completion-handoff-o-call.json', 'test/plan-count-empty-review.test.ts', 'test/fixtures/ceo-count-s-distinct.json', 'test/fixtures/plan-count-design-questionless-report.md', 'test/plan-count-dx-handoff-o.test.ts', 'test/fixtures/devex-handoff-o-call.json', 'test/helpers/ceo-approach-pick.ts', 'test/ceo-approach-pick.test.ts', 'test/fixtures/ceo-approach-q-call.json', 'test/fixtures/ceo-approach-r-call.json', 'test/fixtures/ceo-approach-r-distinct-call.json', 'test/fixtures/ceo-approach-q-paired-call.json', 'test/fixtures/ceo-completion-handoff-q-call.json', 'test/fixtures/ceo-completion-handoff-r-calls.json', 'test/fixtures/ceo-completion-handoff-t-call.json', 'test/helpers/plan-count-file-permission.ts', 'test/plan-count-file-permission.test.ts', 'test/fixtures/plan-count-edit-permission-t.json', 'test/plan-count-preview-footer.test.ts', 'test/fixtures/ceo-preview-u-call.json', 'test/fixtures/ceo-preview-u-screen.txt', 'test/fixtures/ceo-completion-handoff-u-call.json', 'test/fixtures/ceo-completion-handoff-v-call.json', 'test/fixtures/design-preview-v-screen.txt', 'test/plan-count-owned-permission.test.ts', 'test/fixtures/plan-count-owned-permission-v.json', 'test/fixtures/ceo-questionless-w-native.json', 'test/plan-count-ceo-body-finding.test.ts', 'test/fixtures/ceo-count-w-paired.json', 'test/fixtures/ceo-completion-handoff-w-call.json', 'test/fixtures/ceo-approach-y-call.json', 'test/fixtures/ceo-approach-y-screen.txt', 'test/ceo-handoff-y.test.ts', 'test/fixtures/ceo-handoff-y-call.json', 'test/plan-count-truncated-question.test.ts', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt', 'test/fixtures/ceo-handoff-z-call.json'],
|
||||
'plan-ceo-finding-count': ['bin/gstack-config', 'bin/gstack-skill-start', 'bin/gstack-skill-end', 'plan-ceo-review/**', 'test/skill-ceo-section-ordering.test.ts', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-native-input.test.ts', 'test/helpers/pty-screen.ts', 'test/pty-screen.test.ts', 'test/pty-screen-session.test.ts', 'test/fixtures/pty-screen/**', 'test/helpers/hermetic-skill-runtime.ts', 'test/hermetic-skill-runtime.test.ts', 'test/helpers/claude-pty-runner.unit.test.ts', 'test/plan-count-completion.test.ts', 'test/plan-count-dx-handoff.test.ts', 'test/fixtures/devex-handoff-n-call.json', 'test/helpers/ceo-completion-handoff.ts', 'test/ceo-completion-handoff.test.ts', 'test/ceo-count-s-terminals.test.ts', 'test/fixtures/ceo-count-s-paired.json', 'test/fixtures/ceo-completion-handoff-calls.json', 'test/fixtures/ceo-completion-handoff-j-calls.json', 'test/fixtures/ceo-completion-handoff-k-calls.json', 'test/helpers/pty-trust-dialog.ts', 'test/pty-trust-dialog.test.ts', 'test/helpers/plan-count-transcript.ts', 'test/helpers/plan-count-pending-exit.ts', 'test/plan-count-pending-exit.test.ts', 'test/plan-count-transcript.test.ts', 'test/helpers/plan-count-artifacts.ts', 'test/plan-count-artifacts.test.ts', 'test/helpers/eval-store.ts', 'test/helpers/plan-count-fixture.ts', 'test/plan-count-fixture.test.ts', 'test/plan-count-timeout.test.ts', 'test/plan-count-navigation-r.test.ts', 'test/plan-count-prerequisite-n.test.ts', 'test/fixtures/ceo-prerequisite-n-call.json', 'test/skill-e2e-plan-ceo-finding-count.test.ts', 'test/ceo-completion-handoff-l.test.ts', 'test/fixtures/ceo-completion-handoff-l-calls.json', 'test/ceo-completion-handoff-m.test.ts', 'test/fixtures/ceo-completion-handoff-m-call.json', 'test/fixtures/devex-review-l-calls.json', 'test/plan-count-checkbox.test.ts', 'test/fixtures/ceo-checkbox-l.screen.txt', 'test/fixtures/ceo-handoff-n-calls.json', 'test/ceo-completion-handoff-o.test.ts', 'test/fixtures/ceo-completion-handoff-o-call.json', 'test/plan-count-empty-review.test.ts', 'test/fixtures/ceo-count-s-distinct.json', 'test/fixtures/plan-count-design-questionless-report.md', 'test/plan-count-dx-handoff-o.test.ts', 'test/fixtures/devex-handoff-o-call.json', 'test/helpers/ceo-approach-pick.ts', 'test/ceo-approach-pick.test.ts', 'test/fixtures/ceo-approach-q-call.json', 'test/fixtures/ceo-approach-r-call.json', 'test/fixtures/ceo-approach-r-distinct-call.json', 'test/fixtures/ceo-approach-q-paired-call.json', 'test/fixtures/ceo-completion-handoff-q-call.json', 'test/fixtures/ceo-completion-handoff-r-calls.json', 'test/fixtures/ceo-completion-handoff-t-call.json', 'test/helpers/plan-count-file-permission.ts', 'test/plan-count-file-permission.test.ts', 'test/fixtures/plan-count-edit-permission-t.json', 'test/plan-count-preview-footer.test.ts', 'test/fixtures/ceo-preview-u-call.json', 'test/fixtures/ceo-preview-u-screen.txt', 'test/fixtures/ceo-completion-handoff-u-call.json', 'test/fixtures/ceo-completion-handoff-v-call.json', 'test/fixtures/design-preview-v-screen.txt', 'test/plan-count-owned-permission.test.ts', 'test/fixtures/plan-count-owned-permission-v.json', 'test/fixtures/ceo-questionless-w-native.json', 'test/plan-count-ceo-body-finding.test.ts', 'test/fixtures/ceo-count-w-paired.json', 'test/fixtures/ceo-completion-handoff-w-call.json', 'test/fixtures/ceo-approach-y-call.json', 'test/fixtures/ceo-approach-y-screen.txt', 'test/ceo-handoff-y.test.ts', 'test/fixtures/ceo-handoff-y-call.json', 'test/plan-count-truncated-question.test.ts', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt', 'test/fixtures/ceo-handoff-z-call.json', 'test/fixtures/ceo-approach-aa-call.json', 'test/review-handoffs-aa.test.ts', 'test/fixtures/review-handoff-aa-ceo.json', 'test/fixtures/review-handoff-aa-dx.json'],
|
||||
'plan-eng-finding-count': ['bin/gstack-config', 'bin/gstack-skill-start', 'bin/gstack-skill-end', 'plan-eng-review/**', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-native-input.test.ts', 'test/helpers/pty-screen.ts', 'test/pty-screen.test.ts', 'test/pty-screen-session.test.ts', 'test/fixtures/pty-screen/**', 'test/helpers/hermetic-skill-runtime.ts', 'test/hermetic-skill-runtime.test.ts', 'test/helpers/claude-pty-runner.unit.test.ts', 'test/plan-count-completion.test.ts', 'test/plan-count-dx-handoff.test.ts', 'test/fixtures/devex-handoff-n-call.json', 'test/helpers/pty-trust-dialog.ts', 'test/pty-trust-dialog.test.ts', 'test/helpers/plan-count-transcript.ts', 'test/helpers/plan-count-pending-exit.ts', 'test/plan-count-pending-exit.test.ts', 'test/plan-count-transcript.test.ts', 'test/helpers/plan-count-artifacts.ts', 'test/plan-count-artifacts.test.ts', 'test/helpers/eval-store.ts', 'test/helpers/plan-count-fixture.ts', 'test/plan-count-fixture.test.ts', 'test/plan-count-timeout.test.ts', 'test/plan-count-navigation-r.test.ts', 'test/plan-count-prerequisite-n.test.ts', 'test/fixtures/ceo-prerequisite-n-call.json', 'test/skill-e2e-plan-eng-finding-count.test.ts', 'test/fixtures/devex-review-l-calls.json', 'test/plan-count-checkbox.test.ts', 'test/fixtures/ceo-checkbox-l.screen.txt', 'test/plan-count-empty-review.test.ts', 'test/fixtures/ceo-count-s-distinct.json', 'test/fixtures/plan-count-design-questionless-report.md', 'test/plan-count-dx-handoff-o.test.ts', 'test/fixtures/devex-handoff-o-call.json', 'test/eng-devex-s-count.test.ts', 'test/fixtures/eng-devex-s-first-calls.json', 'test/fixtures/eng-devex-s-retry-calls.json', 'test/helpers/plan-count-file-permission.ts', 'test/plan-count-file-permission.test.ts', 'test/fixtures/plan-count-edit-permission-t.json', 'test/eng-first-review-t.test.ts', 'test/fixtures/eng-batching-t-calls.json', 'test/plan-count-preview-footer.test.ts', 'test/fixtures/ceo-preview-u-call.json', 'test/fixtures/ceo-preview-u-screen.txt', 'test/fixtures/design-preview-v-screen.txt', 'test/plan-count-owned-permission.test.ts', 'test/fixtures/plan-count-owned-permission-v.json', 'test/fixtures/ceo-questionless-w-native.json', 'test/eng-scope-y.test.ts', 'test/fixtures/eng-scope-y-calls.json', 'test/plan-count-truncated-question.test.ts', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt', 'test/eng-binding-z.test.ts', 'test/fixtures/eng-binding-z-calls.json', 'test/eng-binding-retry-z.test.ts', 'test/fixtures/eng-binding-retry-z-calls.json'],
|
||||
'plan-design-finding-count': ['bin/gstack-config', 'bin/gstack-skill-start', 'bin/gstack-skill-end', 'plan-design-review/**', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-native-input.test.ts', 'test/helpers/pty-screen.ts', 'test/pty-screen.test.ts', 'test/pty-screen-session.test.ts', 'test/fixtures/pty-screen/**', 'test/helpers/hermetic-skill-runtime.ts', 'test/hermetic-skill-runtime.test.ts', 'test/helpers/claude-pty-runner.unit.test.ts', 'test/plan-count-completion.test.ts', 'test/plan-count-dx-handoff.test.ts', 'test/fixtures/devex-handoff-n-call.json', 'test/helpers/pty-trust-dialog.ts', 'test/pty-trust-dialog.test.ts', 'test/helpers/plan-count-transcript.ts', 'test/helpers/plan-count-pending-exit.ts', 'test/plan-count-pending-exit.test.ts', 'test/plan-count-transcript.test.ts', 'test/helpers/plan-count-artifacts.ts', 'test/plan-count-artifacts.test.ts', 'test/helpers/eval-store.ts', 'test/helpers/plan-count-fixture.ts', 'test/plan-count-fixture.test.ts', 'test/plan-count-timeout.test.ts', 'test/plan-count-navigation-r.test.ts', 'test/plan-count-prerequisite-n.test.ts', 'test/fixtures/ceo-prerequisite-n-call.json', 'test/helpers/design-count-review.ts', 'test/design-count-review.test.ts', 'test/fixtures/design-review-j-calls.json', 'test/helpers/design-count-outside.ts', 'test/design-count-outside.test.ts', 'test/skill-e2e-plan-design-finding-count.test.ts', 'test/fixtures/design-review-l-calls.json', 'test/design-completion-handoff.test.ts', 'test/fixtures/design-handoff-l-calls.json', 'test/fixtures/devex-review-l-calls.json', 'test/plan-count-checkbox.test.ts', 'test/fixtures/ceo-checkbox-l.screen.txt', 'test/fixtures/design-review-n-calls.json', 'test/design-completion-handoff-scored.test.ts', 'test/fixtures/design-handoff-n-calls.json', 'test/fixtures/design-handoff-q-calls.json', 'test/plan-count-empty-review.test.ts', 'test/fixtures/ceo-count-s-distinct.json', 'test/fixtures/plan-count-design-questionless-report.md', 'test/plan-count-dx-handoff-o.test.ts', 'test/fixtures/devex-handoff-o-call.json', 'test/helpers/plan-count-file-permission.ts', 'test/plan-count-file-permission.test.ts', 'test/fixtures/plan-count-edit-permission-t.json', 'test/plan-count-preview-footer.test.ts', 'test/fixtures/ceo-preview-u-call.json', 'test/fixtures/ceo-preview-u-screen.txt', 'test/design-completion-handoff-u.test.ts', 'test/fixtures/design-handoff-u-calls.json', 'test/fixtures/design-preview-v-screen.txt', 'test/plan-count-owned-permission.test.ts', 'test/fixtures/plan-count-owned-permission-v.json', 'test/fixtures/ceo-questionless-w-native.json', 'test/helpers/design-artifact-question.ts', 'test/design-artifact-question.test.ts', 'test/fixtures/design-artifacts-w-calls.json', 'test/fixtures/design-outside-y-calls.json', 'test/fixtures/design-boundaries-y-calls.json', 'test/plan-count-truncated-question.test.ts', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt', 'test/fixtures/design-gap-z-calls.json'],
|
||||
'plan-devex-finding-count': ['bin/gstack-config', 'bin/gstack-skill-start', 'bin/gstack-skill-end', 'plan-devex-review/**', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-native-input.test.ts', 'test/helpers/pty-screen.ts', 'test/pty-screen.test.ts', 'test/pty-screen-session.test.ts', 'test/fixtures/pty-screen/**', 'test/helpers/hermetic-skill-runtime.ts', 'test/hermetic-skill-runtime.test.ts', 'test/helpers/claude-pty-runner.unit.test.ts', 'test/plan-count-completion.test.ts', 'test/plan-count-dx-handoff.test.ts', 'test/fixtures/devex-handoff-n-call.json', 'test/helpers/pty-trust-dialog.ts', 'test/pty-trust-dialog.test.ts', 'test/helpers/plan-count-transcript.ts', 'test/helpers/plan-count-pending-exit.ts', 'test/plan-count-pending-exit.test.ts', 'test/plan-count-transcript.test.ts', 'test/helpers/plan-count-artifacts.ts', 'test/plan-count-artifacts.test.ts', 'test/helpers/eval-store.ts', 'test/helpers/plan-count-fixture.ts', 'test/plan-count-fixture.test.ts', 'test/plan-count-timeout.test.ts', 'test/plan-count-navigation-r.test.ts', 'test/plan-count-prerequisite-n.test.ts', 'test/fixtures/ceo-prerequisite-n-call.json', 'test/helpers/devex-count-fixture.ts', 'test/devex-count-fixture.test.ts', 'test/skill-e2e-plan-devex-finding-count.test.ts', 'test/fixtures/dx-prerequisite-r-call.json', 'test/fixtures/devex-review-l-calls.json', 'test/plan-count-checkbox.test.ts', 'test/fixtures/ceo-checkbox-l.screen.txt', 'test/fixtures/devex-review-n-calls.json', 'test/plan-count-empty-review.test.ts', 'test/fixtures/ceo-count-s-distinct.json', 'test/fixtures/plan-count-design-questionless-report.md', 'test/devex-output-o.test.ts', 'test/fixtures/devex-review-o-calls.json', 'test/fixtures/devex-output-o-retry-call.json', 'test/devex-setup-remedy-o.test.ts', 'test/fixtures/devex-review-o-retry-calls.json', 'test/plan-count-dx-handoff-o.test.ts', 'test/fixtures/devex-handoff-o-call.json', 'test/eng-devex-s-count.test.ts', 'test/fixtures/eng-devex-s-first-calls.json', 'test/fixtures/eng-devex-s-retry-calls.json', 'test/fixtures/devex-review-t-calls.json', 'test/helpers/plan-count-file-permission.ts', 'test/plan-count-file-permission.test.ts', 'test/fixtures/plan-count-edit-permission-t.json', 'test/plan-count-preview-footer.test.ts', 'test/fixtures/ceo-preview-u-call.json', 'test/fixtures/ceo-preview-u-screen.txt', 'test/fixtures/devex-count-u-calls.json', 'test/fixtures/devex-count-u-retry-calls.json', 'test/fixtures/devex-empathy-v-calls.json', 'test/fixtures/devex-handoff-v-call.json', 'test/fixtures/design-preview-v-screen.txt', 'test/plan-count-owned-permission.test.ts', 'test/fixtures/plan-count-owned-permission-v.json', 'test/fixtures/ceo-questionless-w-native.json', 'test/fixtures/devex-count-y-calls.json', 'test/plan-count-truncated-question.test.ts', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt', 'test/fixtures/devex-count-z-calls.json', 'test/fixtures/devex-handoff-z-call.json'],
|
||||
'plan-devex-finding-count': ['bin/gstack-config', 'bin/gstack-skill-start', 'bin/gstack-skill-end', 'plan-devex-review/**', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-native-input.test.ts', 'test/helpers/pty-screen.ts', 'test/pty-screen.test.ts', 'test/pty-screen-session.test.ts', 'test/fixtures/pty-screen/**', 'test/helpers/hermetic-skill-runtime.ts', 'test/hermetic-skill-runtime.test.ts', 'test/helpers/claude-pty-runner.unit.test.ts', 'test/plan-count-completion.test.ts', 'test/plan-count-dx-handoff.test.ts', 'test/fixtures/devex-handoff-n-call.json', 'test/helpers/pty-trust-dialog.ts', 'test/pty-trust-dialog.test.ts', 'test/helpers/plan-count-transcript.ts', 'test/helpers/plan-count-pending-exit.ts', 'test/plan-count-pending-exit.test.ts', 'test/plan-count-transcript.test.ts', 'test/helpers/plan-count-artifacts.ts', 'test/plan-count-artifacts.test.ts', 'test/helpers/eval-store.ts', 'test/helpers/plan-count-fixture.ts', 'test/plan-count-fixture.test.ts', 'test/plan-count-timeout.test.ts', 'test/plan-count-navigation-r.test.ts', 'test/plan-count-prerequisite-n.test.ts', 'test/fixtures/ceo-prerequisite-n-call.json', 'test/helpers/devex-count-fixture.ts', 'test/devex-count-fixture.test.ts', 'test/skill-e2e-plan-devex-finding-count.test.ts', 'test/fixtures/dx-prerequisite-r-call.json', 'test/fixtures/devex-review-l-calls.json', 'test/plan-count-checkbox.test.ts', 'test/fixtures/ceo-checkbox-l.screen.txt', 'test/fixtures/devex-review-n-calls.json', 'test/plan-count-empty-review.test.ts', 'test/fixtures/ceo-count-s-distinct.json', 'test/fixtures/plan-count-design-questionless-report.md', 'test/devex-output-o.test.ts', 'test/fixtures/devex-review-o-calls.json', 'test/fixtures/devex-output-o-retry-call.json', 'test/devex-setup-remedy-o.test.ts', 'test/fixtures/devex-review-o-retry-calls.json', 'test/plan-count-dx-handoff-o.test.ts', 'test/fixtures/devex-handoff-o-call.json', 'test/eng-devex-s-count.test.ts', 'test/fixtures/eng-devex-s-first-calls.json', 'test/fixtures/eng-devex-s-retry-calls.json', 'test/fixtures/devex-review-t-calls.json', 'test/helpers/plan-count-file-permission.ts', 'test/plan-count-file-permission.test.ts', 'test/fixtures/plan-count-edit-permission-t.json', 'test/plan-count-preview-footer.test.ts', 'test/fixtures/ceo-preview-u-call.json', 'test/fixtures/ceo-preview-u-screen.txt', 'test/fixtures/devex-count-u-calls.json', 'test/fixtures/devex-count-u-retry-calls.json', 'test/fixtures/devex-empathy-v-calls.json', 'test/fixtures/devex-handoff-v-call.json', 'test/fixtures/design-preview-v-screen.txt', 'test/plan-count-owned-permission.test.ts', 'test/fixtures/plan-count-owned-permission-v.json', 'test/fixtures/ceo-questionless-w-native.json', 'test/fixtures/devex-count-y-calls.json', 'test/plan-count-truncated-question.test.ts', 'test/fixtures/ceo-approach-z-call.json', 'test/fixtures/ceo-approach-z-screen.txt', 'test/fixtures/devex-count-z-calls.json', 'test/fixtures/devex-handoff-z-call.json', 'test/review-handoffs-aa.test.ts', 'test/fixtures/review-handoff-aa-ceo.json', 'test/fixtures/review-handoff-aa-dx.json'],
|
||||
|
||||
// Gate-tier reviewCount-floor counterparts. Catch the May 2026 transcript
|
||||
// bug (model wrote a plan-mode plan and ExitPlanMode'd without firing any
|
||||
|
||||
Reference in new issue
Block a user