mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-03 18:06:54 +02:00
test: delete tests of dead eval code (A)
- A1: the retired Eng lexical oracle (evaluateEngSeedCoverage, isEngSeedDecisionAUQ), the completion-handoff detector and the retained corpus had no paid caller since v1.87.6; delete their 26 replay files, ~2.6k helper LOC and fixtures, and the dead blocks in 8 mixed files (live hasNativePlanTerminal / batching assertions stay). - A2: dead viewport approvers in autoplan-artifact-permission and their 11 replay files + fixtures; recorder/launcher cases stay. - A3: never-wired oracles and seeders (autoplan-phase-order, eng-finding-fixture, ceo-paired-fixture, design-ui-scope, plan-skill-completion, pty-current-screen, required-reads, transcript-section-logger); plan-seed-submission now decodes through the production createPtyScreen; section manifests name their actual guard. - A4: zero-reference helper exports, plus execGit and invokeAndObserve found by the reachability pass. - 52 fixtures orphaned by the deletions; touchfile and selection-table entries for every deleted path.
This commit is contained in:
1 parent
5ec930d569
commit
5d032ef299
156 files changed
+107
-32055
No files matched your search
@@ -402,12 +402,6 @@ export function verboseSkill(): string {
|
||||
);
|
||||
}
|
||||
|
||||
function execGit(args: string[]): string {
|
||||
const r = spawnSync('git', args, { cwd: ROOT, encoding: 'utf-8', maxBuffer: 64 * 1024 * 1024, timeout: 30_000 });
|
||||
if (r.status !== 0) throw new Error(`git ${args.join(' ')} failed: ${r.stderr}`);
|
||||
return r.stdout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture the real mode-selection tool input, without answering the question.
|
||||
* Print mode retains the existing 12-turn cap; interactive CLI max-turns is
|
||||
|
||||
@@ -97,91 +97,3 @@ export function createAutoplanEditDigest(file: string, removed: string, added: s
|
||||
})};
|
||||
return digest;
|
||||
}
|
||||
|
||||
/** Require complete numbered rows and the marker column of this exact diff. */
|
||||
export function matchesAutoplanDigestRows(rows: string[], before: Buffer, digest: AutoplanEditDigest,
|
||||
allowReplacementReset = false): boolean {
|
||||
if (!validAutoplanEditDigest(digest) || sha(before) !== digest.beforeSHA256) return false;
|
||||
const fullRow = /^( {0,3})([1-9]\d*) ([+ -])(.*)$/;
|
||||
const first = rows.findIndex(row => fullRow.test(row));
|
||||
const leading = first > 0 ? rows.slice(0, first) : [];
|
||||
const chunks: Array<{kind: string; text: string; line: number}> = [];
|
||||
let column: number | undefined, previousLine = 0;
|
||||
let removedStart: number | undefined, replacementReset = false;
|
||||
for (const row of leading.length ? rows.slice(first) : rows) {
|
||||
const full = fullRow.exec(row);
|
||||
if (full) {
|
||||
const line = Number(full[2]), markerColumn = full[1]!.length + full[2]!.length + 1;
|
||||
const kind = full[3]!, previousKind = chunks.at(-1)?.kind;
|
||||
// The complete native replacement panel numbers the old block first,
|
||||
// then restarts additions at that block's first line. Only the caller
|
||||
// that owns this full panel opts in; all row hashes still must match.
|
||||
const reset = allowReplacementReset && !replacementReset && previousKind === '-' && kind === '+' && line === removedStart;
|
||||
if (!Number.isSafeInteger(line) || (line < previousLine && !reset) ||
|
||||
(column !== undefined && column !== markerColumn)) return false;
|
||||
if (allowReplacementReset) {
|
||||
if ((kind === '-' || kind === '+') && kind === previousKind && line !== previousLine + 1) return false;
|
||||
if (kind === '-' && previousKind !== '-') {
|
||||
if (removedStart !== undefined || chunks.some(c => c.kind === '+')) return false;
|
||||
removedStart = line;
|
||||
}
|
||||
if (previousKind === '-' && kind !== '-' && !reset) return false;
|
||||
if (kind === '+' && previousKind !== '+' && removedStart !== undefined && !reset) return false;
|
||||
if (reset) replacementReset = true;
|
||||
}
|
||||
column = markerColumn; previousLine = line;
|
||||
chunks.push({kind, text: full[4]!, line});
|
||||
} else {
|
||||
if (column === undefined || !row.startsWith(' '.repeat(column))) return false;
|
||||
const last = chunks.at(-1), kind = row[column];
|
||||
if (!last || kind !== last.kind) return false;
|
||||
last.text += row.slice(column + 1);
|
||||
}
|
||||
}
|
||||
const originals = new Set(before.toString('utf8').split('\n').map(autoplanEditLineHash));
|
||||
if (allowReplacementReset) {
|
||||
const oldRows = chunks.filter(c => c.kind !== '+').map(c => autoplanEditLineHash(c.text));
|
||||
const newRows = chunks.filter(c => c.kind !== '-').map(c => autoplanEditLineHash(c.text));
|
||||
const starts = (rows: string[], hashes: string[]) => rows.flatMap((_, index) =>
|
||||
hashes.every((hash, offset) => rows[index + offset] === hash) ? [index] : []);
|
||||
const oldStarts = starts(oldRows, digest.oldLineHashes), newStarts = starts(newRows, digest.newLineHashes);
|
||||
if (oldStarts.length !== 1 || newStarts.length !== 1 || oldStarts[0] !== newStarts[0]) return false;
|
||||
const originalLines = before.toString('utf8').split('\n').map(autoplanEditLineHash);
|
||||
const delta = digest.newLineHashes.length - digest.oldLineHashes.length;
|
||||
let oldLine = chunks[0]!.line, newLine = oldLine, added = false;
|
||||
for (const row of chunks) {
|
||||
if (row.kind !== '+') {
|
||||
if (row.line !== oldLine + (added ? delta : 0) || autoplanEditLineHash(row.text) !== originalLines[oldLine - 1]) return false;
|
||||
oldLine++;
|
||||
}
|
||||
if (row.kind !== '-' && row.line !== newLine++) return false;
|
||||
if (row.kind === '+') added = true;
|
||||
}
|
||||
}
|
||||
let authenticatedClip = false;
|
||||
if (leading.length) {
|
||||
const c = digest.clippedAdditions, next = chunks[0];
|
||||
if (!c || c.status !== 'complete' || column === undefined || !next || chunks.length < 2 ||
|
||||
leading.length > MAX_SUFFIX_SCALARS || leading.some(row =>
|
||||
!row.startsWith(' '.repeat(column)) || row[column] !== '+')) return false;
|
||||
const fragment = leading.map(row => row.slice(column + 1)).join('').replace(/\s/g, '');
|
||||
const length = Array.from(fragment).length, record = c.lines.find(row => row.line === next.line - 1);
|
||||
if (!record || !length || length > MAX_SUFFIX_SCALARS || record.nextLineHash !== autoplanEditLineHash(next.text) ||
|
||||
originals.has(record.lineHash) || digest.oldLineHashes.includes(record.lineHash) ||
|
||||
record.suffixHashes[length - 1] !== suffixHash(record.line, record.lineHash, record.nextLineHash, fragment)) return false;
|
||||
const beforeLines = before.toString('utf8').split('\n');
|
||||
if (chunks.some((row, index) => {
|
||||
if (row.line !== next.line + index || row.kind === '-') return true;
|
||||
const relative = row.line - c.startLine, hash = autoplanEditLineHash(row.text);
|
||||
if (relative < digest.newLineHashes.length) return digest.newLineHashes[relative] !== hash;
|
||||
const originalIndex = row.line - (digest.newLineHashes.length - digest.oldLineHashes.length) - 1;
|
||||
return row.kind !== ' ' || originalIndex < 0 || originalIndex >= beforeLines.length ||
|
||||
autoplanEditLineHash(beforeLines[originalIndex]!) !== hash;
|
||||
})) return false;
|
||||
authenticatedClip = true;
|
||||
}
|
||||
return chunks.length >= 2 && (authenticatedClip || chunks.some(c => c.kind === '+' && /\S/.test(c.text) &&
|
||||
!originals.has(autoplanEditLineHash(c.text)) && !digest.oldLineHashes.includes(autoplanEditLineHash(c.text)))) &&
|
||||
chunks.every(c => c.kind === '+' ? digest.newLineHashes.includes(autoplanEditLineHash(c.text)) :
|
||||
originals.has(autoplanEditLineHash(c.text)) && (c.kind !== '-' || digest.oldLineHashes.includes(autoplanEditLineHash(c.text))));
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
/** One-time input for a cropped native Edit of an already-owned review artifact. */
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { validAutoplanEditDigest, readAutoplanDigestFile, matchesAutoplanDigestRows, createAutoplanEditDigest, autoplanEditLineHash } from './autoplan-artifact-digest';
|
||||
import type { PendingAutoplanArtifact } from './autoplan-artifact-recorder';
|
||||
import type { NativePublicToolEvent } from './plan-count-transcript';
|
||||
|
||||
@@ -42,83 +40,6 @@ export function autoplanPermissionProgressKey(viewport: string, events: readonly
|
||||
}
|
||||
}
|
||||
|
||||
/** The native header may remain above the diff; both displayed paths must bind. */
|
||||
function ownedEditDiffRows(rows: string[], file: string, ownedStateRoot?: string): string[] | null {
|
||||
const header = rows.findIndex(row => /^[●⏺] Update\(/.test(row));
|
||||
if (header > 0 && rows.slice(0,header).some(row => row.trim())) {
|
||||
// A completed native tool's diff may remain above the active edit panel.
|
||||
// Only its indented diff output is ignored; competing panels or prose are
|
||||
// not evidence for the current request and cannot be used as a prefix.
|
||||
const prefix = rows.slice(0, header), fullRow = /^ {6}([1-9]\d*) ([+ -])/;
|
||||
const first = prefix.map(row => fullRow.exec(row)).find(Boolean);
|
||||
if (!first) return null;
|
||||
const markerColumn = 6 + first[1]!.length + 1;
|
||||
let kind: string | undefined;
|
||||
for (const row of prefix) {
|
||||
if (!row.trim()) continue;
|
||||
const full = fullRow.exec(row);
|
||||
if (full) {
|
||||
if (!Number.isSafeInteger(Number(full[1])) || 6 + full[1]!.length + 1 !== markerColumn) return null;
|
||||
kind = full[2];
|
||||
} else {
|
||||
const wrappedKind = row[markerColumn];
|
||||
if (!row.startsWith(' '.repeat(markerColumn)) || !['+', ' ', '-'].includes(wrappedKind ?? '') ||
|
||||
(kind !== undefined && wrappedKind !== kind)) return null;
|
||||
kind = wrappedKind;
|
||||
}
|
||||
}
|
||||
rows = rows.slice(header);
|
||||
}
|
||||
// A redraw can repeat the same native tool title above one current panel.
|
||||
// Those homogeneous titles supply no authority: the full panel below must
|
||||
// still bind its path, current request, content, and exact one-time menu.
|
||||
const repeated: string[] = [];
|
||||
let panelAt = 0;
|
||||
for (; panelAt < rows.length; panelAt++) {
|
||||
if (!rows[panelAt]!.trim()) continue;
|
||||
const title = /^[●⏺] Update\(([^\n]+)\)$/.exec(rows[panelAt]!);
|
||||
if (!title) break;
|
||||
repeated.push(title[1]!);
|
||||
}
|
||||
if (repeated.length > 1 && ownedStateRoot && /^[─╌]{8,}$/.test(rows[panelAt] ?? '') &&
|
||||
rows[panelAt + 1]?.trim() === 'Edit file') {
|
||||
const relative = path.relative(ownedStateRoot, file).split(path.sep).join('/');
|
||||
const alias = path.basename(ownedStateRoot) === '.gstack' ? `~/.gstack/${relative}` : undefined;
|
||||
if (repeated.some(title => title !== repeated[0]) || (repeated[0] !== file && repeated[0] !== alias)) return null;
|
||||
rows = rows.slice(panelAt);
|
||||
}
|
||||
if (rows.filter(row => /^[●⏺] Update\(/.test(row)).length > 1) return null;
|
||||
// A viewport can start at the native Edit panel after its tool title has
|
||||
// scrolled away. The remaining displayed path must still bind the complete
|
||||
// owned project/artifact path; the menu and current request are checked below.
|
||||
if (/^[─╌]{8,}$/.test(rows[0] ?? '') && rows[1]?.trim() === 'Edit file') {
|
||||
if (!ownedStateRoot || !/^[─╌]{8,}$/.test(rows[3] ?? '')) return null;
|
||||
const relative = path.relative(ownedStateRoot, file).split(path.sep).join('/');
|
||||
const alias = path.basename(ownedStateRoot) === '.gstack' ? `~/.gstack/${relative}` : undefined;
|
||||
const displayed = rows[2]?.trim() ?? '';
|
||||
if (displayed !== file && displayed !== alias) {
|
||||
const suffix = displayed.startsWith('…') ? displayed.slice(1).split(path.sep).join('/') : '';
|
||||
if ((suffix !== relative && !suffix.endsWith('/' + relative)) || !file.split(path.sep).join('/').endsWith(suffix)) return null;
|
||||
}
|
||||
return rows.slice(4);
|
||||
}
|
||||
const update = /^[●⏺] Update\(([^\n]+)\)$/.exec(rows[0] ?? '');
|
||||
if (!update) return rows; // Existing cropped-only row guards still apply.
|
||||
if (!ownedStateRoot || rows[1]?.trim() !== '' || !/^[─╌]{8,}$/.test(rows[2] ?? '') ||
|
||||
rows[3]?.trim() !== 'Edit file' || !/^[─╌]{8,}$/.test(rows[5] ?? '')) return null;
|
||||
const relative = path.relative(ownedStateRoot,file).split(path.sep).join('/');
|
||||
const alias = path.basename(ownedStateRoot) === '.gstack' ? `~/.gstack/${relative}` : undefined;
|
||||
if (update[1] !== file && update[1] !== alias) return null;
|
||||
const displayed = rows[4]?.trim() ?? '';
|
||||
if (displayed !== file && displayed !== alias) {
|
||||
const suffix = displayed.startsWith('…') ? displayed.slice(1).split(path.sep).join('/') : '';
|
||||
// A truncated prefix must still retain the complete owned project/artifact
|
||||
// path. A basename or sibling-project suffix cannot bind this request.
|
||||
if ((suffix !== relative && !suffix.endsWith('/'+relative)) || !file.split(path.sep).join('/').endsWith(suffix)) return null;
|
||||
}
|
||||
return rows.slice(6);
|
||||
}
|
||||
|
||||
export function ownedAutoplanArtifact(file: string, context: Pick<ArtifactPermissionContext, 'cwd' | 'ownedStateRoot'>): boolean {
|
||||
if (!context.ownedStateRoot || !path.isAbsolute(file) || path.resolve(file) !== file) return false;
|
||||
const project = path.join(context.ownedStateRoot, 'projects', path.basename(context.cwd));
|
||||
@@ -137,281 +58,6 @@ export function ownedAutoplanArtifact(file: string, context: Pick<ArtifactPermis
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
/** Require the whole current cropped diff, exact menu, and requested edit text. */
|
||||
function matchesCroppedEdit(viewport: string, file: string, before: string, removed: string, after: string,
|
||||
ownedStateRoot?: string): boolean {
|
||||
if (viewport.length > MAX_BYTES) return false;
|
||||
const text = viewport.replace(/\r\n?/g, '\n');
|
||||
const menu = /^ {0,3}Do you want to make this edit to ([^\n?]+)\? *\n {0,3}❯ *1\. Yes *\n {0,3}2\. Yes, and switch to accept edits \(auto-approve file edits and common file commands\) for this session(?: \(shift\+tab\))? *\n {0,3}3\. No *\n\s*Esc to cancel [·•] Tab to amend\s*$/m.exec(text);
|
||||
if (!menu || menu.index + menu[0].length !== text.length || menu[1] !== path.basename(file)) return false;
|
||||
const rows = text.slice(0, menu.index).trimEnd().split('\n');
|
||||
if (!/^[╌─]{8,}$/.test(rows.pop() ?? '')) return false;
|
||||
const diffRows = ownedEditDiffRows(rows,file,ownedStateRoot);
|
||||
if (!diffRows) return false;
|
||||
const chunks: Array<{ kind: string; text: string }> = [];
|
||||
let markerColumn: number | undefined;
|
||||
for (const row of diffRows) {
|
||||
const numbered = /^( {0,3})([1-9]\d*) ([+ -])(.*)$/.exec(row);
|
||||
if (numbered) {
|
||||
const line = Number(numbered[2]), column = numbered[1]!.length + numbered[2]!.length + 1;
|
||||
// Match the digest parser: every row owns one marker column, regardless
|
||||
// of line-number width; continuations retain that column and diff kind.
|
||||
if (!Number.isSafeInteger(line) || (markerColumn !== undefined && markerColumn !== column)) return false;
|
||||
markerColumn = column;
|
||||
chunks.push({ kind: numbered[3]!, text: numbered[4]! });
|
||||
} else {
|
||||
const last = chunks.at(-1);
|
||||
if (markerColumn === undefined || !row.startsWith(' '.repeat(markerColumn)) ||
|
||||
!last || row[markerColumn] !== last.kind) return false;
|
||||
last.text += row.slice(markerColumn + 1);
|
||||
}
|
||||
}
|
||||
// The crop itself cannot contain an example introduction, quote, unrelated
|
||||
// prompt, or arbitrary diff: each row must occur in this exact pending edit.
|
||||
const originals = before.split('\n').map(compact);
|
||||
const deletions = removed.split('\n').map(compact);
|
||||
const replacements = after.split('\n').map(compact);
|
||||
const changed = chunks.some(chunk => chunk.kind !== ' ' && compact(chunk.text));
|
||||
const matches = (oldRows: string[], newRows: string[]) => chunks.every(chunk =>
|
||||
(chunk.kind === '+' ? newRows : chunk.kind === '-' ? oldRows : originals).includes(compact(chunk.text)));
|
||||
if (changed && matches(deletions, replacements)) return true;
|
||||
// Edit arguments can start or end inside a line while the native preview
|
||||
// displays the whole line. Reconstruct only those unchanged edge bytes
|
||||
// from the unique current old substring; no viewport text supplies them.
|
||||
const at = before.indexOf(removed), end = at + removed.length;
|
||||
if (!changed || !removed || at < 0 || at !== before.lastIndexOf(removed) || after.length > MAX_BYTES) return false;
|
||||
const prefix = before.slice(before.slice(0, at).lastIndexOf('\n') + 1, at);
|
||||
const newline = before.indexOf('\n', end);
|
||||
const suffix = before.slice(end, newline < 0 ? before.length : newline);
|
||||
if (!prefix && !suffix) return false;
|
||||
const oldRows = (prefix + removed + suffix).split('\n').map(compact);
|
||||
const newRows = (prefix + after + suffix).split('\n').map(compact);
|
||||
return matches(oldRows, newRows) && chunks.some(chunk =>
|
||||
chunk.kind === '+' ? !oldRows.includes(compact(chunk.text)) :
|
||||
chunk.kind === '-' && !newRows.includes(compact(chunk.text)));
|
||||
}
|
||||
|
||||
export function autoplanArtifactPermissionInput(
|
||||
viewport: string, context: ArtifactPermissionContext, seen: ReadonlySet<string>,
|
||||
): { input: '1\r'; signature: string; file: string } | null {
|
||||
const now = context.now ?? Date.now();
|
||||
if (context.transcriptStatus !== 'ready' || !Number.isFinite(context.commandStartedAt) ||
|
||||
context.commandStartedAt > now || context.publicTools.length > 10_000 ||
|
||||
context.publicTools.some(event => !Number.isFinite(Date.parse(event.timestamp)))) return null;
|
||||
const events = context.publicTools.filter(event => Date.parse(event.timestamp) >= context.commandStartedAt);
|
||||
if (!events.length || events.some(event => !event.sessionId || !event.toolUseId ||
|
||||
!Number.isFinite(Date.parse(event.timestamp)) || Date.parse(event.timestamp) > now) ||
|
||||
new Set(events.map(event => event.sessionId)).size !== 1) return null;
|
||||
// Bind the latest file mutation, which must be the sole unresolved Write/Edit.
|
||||
// Claude may publish a queued Bash while its current Edit permission is open;
|
||||
// that unrelated request supplies no file permission authority.
|
||||
const edit = events.filter(event => event.kind === 'use' &&
|
||||
(event.name === 'Write' || event.name === 'Edit')).at(-1);
|
||||
if (!edit || edit.kind !== 'use' || edit.name !== 'Edit' || typeof edit.input?.file_path !== 'string' ||
|
||||
typeof edit.input.old_string !== 'string' || !edit.input.old_string ||
|
||||
typeof edit.input.new_string !== 'string' || edit.input.new_string === edit.input.old_string ||
|
||||
(edit.input.replace_all !== undefined && edit.input.replace_all !== false)) return null;
|
||||
const signature = `${edit.sessionId}:${edit.toolUseId}`;
|
||||
if (seen.has(signature) || !ownedAutoplanArtifact(edit.input.file_path, context)) return null;
|
||||
const uses = new Map<string, NativePublicToolEvent>();
|
||||
const results = new Map<string, NativePublicToolEvent>();
|
||||
let previousTime = context.commandStartedAt;
|
||||
for (const event of events) {
|
||||
const time = Date.parse(event.timestamp);
|
||||
if (time < previousTime) return null;
|
||||
previousTime = time;
|
||||
const map = event.kind === 'use' ? uses : results;
|
||||
if (map.has(event.toolUseId)) return null;
|
||||
map.set(event.toolUseId, event);
|
||||
if (event.kind === 'result' && !uses.has(event.toolUseId)) return null;
|
||||
}
|
||||
const writes = [...uses.values()].filter(event => event.name === 'Write' || event.name === 'Edit');
|
||||
if (results.has(edit.toolUseId) || writes.filter(event => !results.has(event.toolUseId)).length !== 1) return null;
|
||||
if (!writes.some(event => event.toolUseId !== edit.toolUseId &&
|
||||
event.input?.file_path === edit.input!.file_path && results.has(event.toolUseId) &&
|
||||
results.get(event.toolUseId)!.isError === false)) return null;
|
||||
try {
|
||||
const before = fs.readFileSync(edit.input.file_path, 'utf8');
|
||||
if (!before.includes(edit.input.old_string) ||
|
||||
!matchesCroppedEdit(viewport, edit.input.file_path, before, edit.input.old_string, edit.input.new_string,
|
||||
context.ownedStateRoot)) return null;
|
||||
return { input: '1\r', signature, file: edit.input.file_path };
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
/** A previously granted viewport cannot establish a newer unpublished request. */
|
||||
export const autoplanArtifactMenuKey = (viewport: string) =>
|
||||
`menu:${createHash('sha256').update(viewport.replace(/\r\n?/g, '\n')).digest('hex')}`;
|
||||
|
||||
/** Queued native-plan edits remain unstarted; this never grants their input. */
|
||||
function ownedQueuedNativePlan(file: unknown, root: string | undefined, pendingTime: number): file is string {
|
||||
if (!root || typeof file !== 'string' || !path.isAbsolute(root) || path.resolve(root) !== root ||
|
||||
!path.isAbsolute(file) || path.resolve(file) !== file || path.dirname(file) !== root ||
|
||||
!/^[a-z][a-z0-9-]*\.md$/.test(path.basename(file))) return false;
|
||||
try {
|
||||
return fs.lstatSync(root).isDirectory() && fs.lstatSync(file).isFile() &&
|
||||
fs.realpathSync(file) === path.join(fs.realpathSync(root), path.basename(file)) && fs.statSync(file).size <= MAX_BYTES &&
|
||||
Math.floor(fs.statSync(file).mtimeMs) <= pendingTime;
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
/** Bind completed snapshot output and queued native-plan redraw labels before
|
||||
* the full current panel. Arbitrary prose, examples and competing panels stay. */
|
||||
function queuedPlanViewport(viewport: string, queuedPlans: number, current: NativePublicToolEvent,
|
||||
events: NativePublicToolEvent[]): string {
|
||||
if (!queuedPlans) return viewport;
|
||||
const lines = viewport.replace(/\r\n?/g, '\n').split('\n');
|
||||
const title = lines.findIndex(line => /^[●⏺] Update\(/.test(line));
|
||||
if (title < 0 || lines.filter(line => /^[●⏺] Update\(/.test(line)).length !== 1) return viewport;
|
||||
const panel = lines.findIndex((line, i) => i > title && /^[─╌]{8,}$/.test(line));
|
||||
const redraws = lines.slice(title + 1, panel).filter(line => line.trim());
|
||||
if (panel < 0 || redraws.length !== queuedPlans || redraws.some(line => !/^[●⏺] Updated plan$/.test(line))) return viewport;
|
||||
const prefix = lines.slice(0, title);
|
||||
while (prefix.at(-1)?.trim() === '') prefix.pop();
|
||||
if (prefix.some(line => line.trim())) {
|
||||
const prior = events.slice(0, events.indexOf(current));
|
||||
const result = prior.filter(event => event.kind === 'result').at(-1);
|
||||
const use = result && prior.find(event => event.kind === 'use' && event.toolUseId === result.toolUseId);
|
||||
const command = /^ {8}"([^"\n]+)…\)$/.exec(prefix[0] ?? '');
|
||||
const outputEnd = prefix.length - 2;
|
||||
if (!command || !use || use.name !== 'Bash' || use.messageId !== current.messageId ||
|
||||
use.requestId !== current.requestId || typeof use.input?.command !== 'string' ||
|
||||
!use.input.command.includes(command[1]!) || result?.isError !== false || typeof result.content !== 'string' ||
|
||||
!/^ {2}⎿[ \u00a0]+\{$/.test(prefix[1] ?? '') ||
|
||||
!/^ {5}… \+[1-9]\d* lines \(ctrl\+o to expand\)$/.test(prefix[outputEnd] ?? '') ||
|
||||
!/^ {2}⎿[ \u00a0]+Allowed by auto mode classifier$/.test(prefix.at(-1) ?? '') || outputEnd < 3) return viewport;
|
||||
const displayed = ['{', ...prefix.slice(2, outputEnd).map(line => /^ {5}( {2}\S.*)$/.exec(line)?.[1])];
|
||||
if (displayed.some(line => line === undefined) ||
|
||||
result.content.split('\n').slice(0, displayed.length).join('\n') !== displayed.join('\n')) return viewport;
|
||||
}
|
||||
return [lines[title], '', ...lines.slice(panel)].join('\n');
|
||||
}
|
||||
|
||||
/** Native batch redraws are display-only: bind their titles, waiting command,
|
||||
* and one clipped context row to public events before removing the prefix. */
|
||||
function queuedArtifactViewport(viewport: string, current: NativePublicToolEvent,
|
||||
events: NativePublicToolEvent[], queued: ReadonlySet<string>, file: string,
|
||||
pendingTime: number, ownedStateRoot?: string): string {
|
||||
if (!queued.size || !ownedStateRoot) return viewport;
|
||||
const successors = events.filter(e => e.kind === 'use' && queued.has(e.toolUseId));
|
||||
if (successors.some(e => e.input?.file_path !== file)) return viewport;
|
||||
const lines = viewport.replace(/\r\n?/g, '\n').split('\n');
|
||||
const firstTitle = lines.findIndex(line => /^[●⏺] Update\(/.test(line));
|
||||
const panel = lines.findIndex((line, i) => i > firstTitle && /^[─╌]{8,}$/.test(line));
|
||||
if (firstTitle < 1 || panel < 0) return viewport;
|
||||
const prefix = lines.slice(0, firstTitle).filter(line => line.trim());
|
||||
const clipped = prefix.length === 1 && /^ {10}(\S.{15,})$/.exec(prefix[0]!);
|
||||
if (!clipped) return viewport;
|
||||
const relative = path.relative(ownedStateRoot, file).split(path.sep).join('/');
|
||||
const alias = path.basename(ownedStateRoot) === '.gstack' ? `~/.gstack/${relative}` : undefined;
|
||||
const rows = lines.slice(firstTitle, panel).filter(line => line.trim());
|
||||
const titles = rows.slice(0, successors.length + 1);
|
||||
if (titles.length !== successors.length + 1 || titles.some(row => {
|
||||
const title = /^[●⏺] Update\(([^\n]+)\)$/.exec(row);
|
||||
return !title || (title[1] !== file && title[1] !== alias);
|
||||
})) return viewport;
|
||||
const bash = rows.slice(titles.length);
|
||||
if (bash.length < 2 || !/^ {2}⎿[ \u00a0]+Waiting…$/.test(bash.at(-1)!)) return viewport;
|
||||
const parts = bash.slice(0, -1).map((row, i) =>
|
||||
(i === 0 ? /^[●⏺] Bash\((.+)$/ : /^ {6}(.+)$/).exec(row)?.[1]);
|
||||
if (parts.some(part => part === undefined)) return viewport;
|
||||
const rendered = parts.join('');
|
||||
if (!rendered.endsWith('…)')) return viewport;
|
||||
const commandPrefix = compact(rendered.slice(0, -2));
|
||||
const waiting = events.filter(e => e.kind === 'use' && e.name === 'Bash' &&
|
||||
e.messageId === current.messageId && e.requestId === current.requestId &&
|
||||
events.indexOf(e) > Math.max(...successors.map(s => events.indexOf(s))) &&
|
||||
!events.some(result => result.kind === 'result' && result.toolUseId === e.toolUseId) &&
|
||||
typeof e.input?.command === 'string' && compact(e.input.command).startsWith(commandPrefix));
|
||||
if (commandPrefix.length < 32 || waiting.length !== 1) return viewport;
|
||||
const completed = events.filter(e => e.kind === 'result' && e.isError === false &&
|
||||
Date.parse(e.timestamp) <= pendingTime).map(result => ({result, use:events.find(e =>
|
||||
e.kind === 'use' && e.toolUseId === result.toolUseId)})).filter(({use}) =>
|
||||
use?.name === 'Edit' && use.input?.file_path === file &&
|
||||
use.messageId === current.messageId && use.requestId === current.requestId).at(-1);
|
||||
const replacement = completed?.use?.input?.new_string;
|
||||
if (typeof replacement !== 'string' || !replacement) return viewport;
|
||||
const before = fs.readFileSync(file, 'utf8'), at = before.indexOf(replacement);
|
||||
if (at < 0 || before.indexOf(replacement, at + 1) !== -1) return viewport;
|
||||
// Native diffs display at most three unchanged context lines after an edit.
|
||||
// The cropped row must be a suffix of one of those current, unchanged lines.
|
||||
const lineEnd = before.indexOf('\n', at + replacement.length);
|
||||
const context = lineEnd < 0 ? [] : before.slice(lineEnd + 1).split('\n').slice(0, 3);
|
||||
if (!context.some(line => compact(line).endsWith(compact(clipped[1]!)))) return viewport;
|
||||
return lines.slice(panel).join('\n');
|
||||
}
|
||||
|
||||
/** A cropped command caption is display only. Bind the complete wrapped command
|
||||
* to one unstarted successor in the current published batch before discarding it. */
|
||||
function queuedCommandViewport(viewport: string, current: NativePublicToolEvent,
|
||||
events: NativePublicToolEvent[], queued: ReadonlySet<string>, hookSeenIds: readonly string[],
|
||||
viewportCapturedAt: number): string {
|
||||
if (!queued.size) return viewport;
|
||||
const text = viewport.replace(/\r\n?/g, '\n');
|
||||
const panels = [...text.matchAll(/^[─╌]{8,}\n {0,3}Edit file[ \t]*\n/gm)];
|
||||
if (panels.length !== 1 || panels[0]!.index === 0) return viewport;
|
||||
const rows = text.slice(0, panels[0]!.index).split('\n');
|
||||
while (rows.at(-1)?.trim() === '') rows.pop();
|
||||
const parts = rows.map((row, i) =>
|
||||
(i === 0 ? /^ {2}⎿[ \u00a0]+\$ (\S.*)$/ : /^ {5}(\S.*)$/).exec(row)?.[1]);
|
||||
if (!parts.length || parts.some(part => part === undefined)) return viewport;
|
||||
const currentIndex = events.indexOf(current);
|
||||
const waiting = events.filter(e => e.kind === 'use' && e.name === 'Bash' &&
|
||||
e.messageId === current.messageId && e.requestId === current.requestId && events.indexOf(e) > currentIndex &&
|
||||
!events.some(result => result.kind === 'result' && result.toolUseId === e.toolUseId));
|
||||
const command = waiting[0], input = command?.input?.command;
|
||||
if (waiting.length !== 1 || !command || typeof input !== 'string' || !input || input.length > MAX_BYTES ||
|
||||
/[\x00-\x1f\x7f]/.test(input) || hookSeenIds.includes(command.toolUseId) ||
|
||||
Date.parse(command.timestamp) > viewportCapturedAt ||
|
||||
events.some(e => e.kind === 'use' && queued.has(e.toolUseId) && events.indexOf(e) >= events.indexOf(command))) return viewport;
|
||||
// Preserve every displayed character, including spaces inside quoted arguments.
|
||||
// Only whitespace omitted at a renderer soft-wrap boundary may be skipped.
|
||||
let remaining = input;
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (!remaining.startsWith(parts[i]!)) return viewport;
|
||||
remaining = remaining.slice(parts[i]!.length);
|
||||
if (i < parts.length - 1) remaining = remaining.replace(/^[ \t]+/, '');
|
||||
}
|
||||
return remaining === '' ? text.slice(panels[0]!.index) : viewport;
|
||||
}
|
||||
|
||||
/** A native command description can remain above an unpublished Edit panel.
|
||||
* Its text supplies no command identity, completion, or approval authority.
|
||||
* Only the digest-bound pending path may discard this one display prefix. */
|
||||
function pendingCommandDisplayViewport(viewport: string, file: string, ownedStateRoot?: string): string {
|
||||
const text = viewport.replace(/\r\n?/g, '\n');
|
||||
const panels = [...text.matchAll(/^[─╌]{8,}\n {0,3}Edit file[ \t]*\n/gm)];
|
||||
if (panels.length !== 1 || panels[0]!.index === 0) return viewport;
|
||||
const prefix = text.slice(0, panels[0]!.index).split('\n').filter(line => line.trim());
|
||||
// An unpublished batch can leave the current Update title, plan redraws,
|
||||
// and a queued Bash card above the panel. These cards grant no authority:
|
||||
// only the one current Edit's owned path and complete digest below do so.
|
||||
const update = /^[●⏺] Update\(([^\n]+)\)$/.exec(prefix[0] ?? '');
|
||||
if (update && ownedStateRoot) {
|
||||
const relative = path.relative(ownedStateRoot, file).split(path.sep).join('/');
|
||||
const alias = path.basename(ownedStateRoot) === '.gstack' ? `~/.gstack/${relative}` : undefined;
|
||||
const bash = prefix.findIndex(row => /^[●⏺] Bash\(/.test(row));
|
||||
const command = prefix.slice(bash, -1);
|
||||
if ((update[1] === file || update[1] === alias) && bash > 1 &&
|
||||
prefix.slice(1, bash).every(row => /^[●⏺] Updated plan$/.test(row)) &&
|
||||
/^ {2}⎿[ \u00a0]+Waiting…$/.test(prefix.at(-1) ?? '') && command.length > 0 &&
|
||||
command.every((row, i) => (i === 0 ? /^[●⏺] Bash\(\S.*$/ : /^ {6}\S.*$/).test(row)) &&
|
||||
command.at(-1)!.endsWith('…)') &&
|
||||
!command.slice(1).some(row => /^ {6}[●⏺❯☐□>]|^ {6}(?:`{3,}|~{3,})/.test(row)) &&
|
||||
!/(?:Do you want|Would you like|Bash command[^\n]*permission|requested permissions?|allow all edits|always allow access|Esc to cancel|Edit file)/i.test(command.join('\n'))) {
|
||||
return text.slice(panels[0]!.index);
|
||||
}
|
||||
return viewport;
|
||||
}
|
||||
const title = /^[●⏺] ([^\n]+)$/.exec(prefix[0] ?? '')?.[1];
|
||||
if (!title || /^(?:["'`“‘]|(?:source|example|quoted|history|historical|hypothetical|previous|earlier)\b)/i.test(title) ||
|
||||
!/^ {2}⎿[ \u00a0]+\$ \S.*$/.test(prefix[1] ?? '') ||
|
||||
prefix.slice(2).some(line => !/^ {5}\S.*$/.test(line)) ||
|
||||
prefix.slice(1).some(line => /^[ \t]*[●⏺❯☐□>]|^[ \t]*(?:`{3,}|~{3,})/.test(line)) ||
|
||||
/(?:Do you want|Would you like|Bash command[^\n]*permission|requested permissions?|allow all edits|always allow access|Esc to cancel|Edit file)/i.test(prefix.join('\n'))) return viewport;
|
||||
return text.slice(panels[0]!.index);
|
||||
}
|
||||
|
||||
/** Same native-history gate for an unpublished pending Edit, independent of its display. */
|
||||
export function hasPendingAutoplanArtifactHistory(
|
||||
context: ArtifactPermissionContext & { pending?: PendingAutoplanArtifact },
|
||||
@@ -444,151 +90,3 @@ export function hasPendingAutoplanArtifactHistory(
|
||||
Date.parse(results.get(e.toolUseId)!.timestamp) <= pendingTime)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Metadata-only fallback. Added rows are display evidence, never request content. */
|
||||
export function pendingAutoplanArtifactPermissionInput(viewport: string,
|
||||
context: ArtifactPermissionContext & { pending?: PendingAutoplanArtifact; viewportCapturedAt: number },
|
||||
seen: ReadonlySet<string>,
|
||||
): { input: '1\r'; signature: string; file: string } | null {
|
||||
const p = context.pending, now = context.now ?? Date.now();
|
||||
if (p?.editDigest !== undefined && !validAutoplanEditDigest(p.editDigest)) return null;
|
||||
if (!p || !Number.isFinite(now) || context.transcriptStatus !== 'ready' || !Number.isFinite(context.commandStartedAt) ||
|
||||
!Number.isFinite(context.viewportCapturedAt) || context.viewportCapturedAt > now ||
|
||||
context.commandStartedAt > context.viewportCapturedAt || viewport.length > MAX_BYTES ||
|
||||
p.source !== 'pre_tool_use' || p.tool !== 'Edit' || typeof p.file !== 'string' ||
|
||||
!/^[A-Za-z0-9_-]{1,160}$/.test(p.sessionId) || !/^[A-Za-z0-9_-]{1,160}$/.test(p.toolUseId) ||
|
||||
!ownedAutoplanArtifact(p.file, context)) return null;
|
||||
const pendingTime = Date.parse(p.timestamp), signature = `${p.sessionId}:${p.toolUseId}`;
|
||||
if (!Number.isFinite(pendingTime) || pendingTime < context.commandStartedAt || pendingTime > context.viewportCapturedAt ||
|
||||
seen.has(signature) || seen.has(autoplanArtifactMenuKey(viewport)) || context.publicTools.length > 10_000) return null;
|
||||
if (!hasPendingAutoplanArtifactHistory(context)) return null;
|
||||
try {
|
||||
const currentViewport = p.editDigest ? pendingCommandDisplayViewport(viewport, p.file, context.ownedStateRoot) : viewport;
|
||||
const text = currentViewport.replace(/\r\n?/g, '\n');
|
||||
const menu = /^ {0,3}Do you want to make this edit to ([^\n?]+)\? *\n {0,3}❯ *1\. Yes *\n {0,3}2\. Yes, and switch to accept edits \(auto-approve file edits and common file commands\) for this session(?: \(shift\+tab\))? *\n {0,3}3\. No *\n\s*Esc to cancel [·•] Tab to amend\s*$/m.exec(text);
|
||||
if (!menu || menu.index + menu[0].length !== text.length || menu[1] !== path.basename(p.file)) return null;
|
||||
const rows = text.slice(0, menu.index).trimEnd().split('\n');
|
||||
if (!/^[╌─]{8,}$/.test(rows.pop() ?? '')) return null;
|
||||
const diffRows = ownedEditDiffRows(rows,p.file,context.ownedStateRoot);
|
||||
if (!diffRows) return null;
|
||||
if (Math.floor(fs.statSync(p.file).mtimeMs) > pendingTime) return null;
|
||||
if (p.editDigest) {
|
||||
const before = readAutoplanDigestFile(p.file);
|
||||
if (!before || createHash('sha256').update(before).digest('hex') !== p.editDigest.beforeSHA256) return null;
|
||||
if (matchesAutoplanDigestRows(diffRows,before,p.editDigest,currentViewport !== viewport)) return {input:'1\r', signature, file:p.file};
|
||||
if (currentViewport !== viewport) return null; // The new prefix path requires the exact digest, including additions.
|
||||
// Legacy deletion crops below must still honor the recorded request digest.
|
||||
}
|
||||
const originals = fs.readFileSync(p.file, 'utf8').split('\n').map(compact);
|
||||
const chunks: Array<{kind:string; text:string; partial?:boolean}> = [];
|
||||
const fullRow = /^( {0,3})([1-9]\d*) ([+ -])(.*)$/;
|
||||
const first = diffRows.map(row => fullRow.exec(row)).find(Boolean);
|
||||
if (!first) return null;
|
||||
const markerColumn = first[1]!.length + first[2]!.length + 1;
|
||||
let numbered = 0;
|
||||
for (const row of diffRows) {
|
||||
const full = fullRow.exec(row);
|
||||
if (full) {
|
||||
if (!Number.isSafeInteger(Number(full[2])) || full[1]!.length + full[2]!.length + 1 !== markerColumn) return null;
|
||||
numbered++; chunks.push({kind:full[3]!, text:full[4]!});
|
||||
} else {
|
||||
const kind = row[markerColumn], text = row.slice(markerColumn + 1);
|
||||
if (!row.startsWith(' '.repeat(markerColumn)) || !['+', ' ', '-'].includes(kind ?? '')) return null;
|
||||
if (!chunks.length) chunks.push({kind:kind!, text, partial:true});
|
||||
else {
|
||||
const previous = chunks.at(-1)!;
|
||||
if (previous.kind !== kind) return null;
|
||||
previous.text += text;
|
||||
}
|
||||
}
|
||||
}
|
||||
// A leading cropped deletion/context fragment must be an actual suffix.
|
||||
// Complete removed/context rows must occur in the current owned file.
|
||||
if (numbered < 2 || !chunks.some(c => c.kind === '-' && compact(c.text)) ||
|
||||
chunks.some(c => c.kind !== '+' && !originals.some(line => c.partial
|
||||
? line.endsWith(compact(c.text)) : line === compact(c.text)))) return null;
|
||||
const digest = p.editDigest;
|
||||
if (digest && chunks.some(chunk => {
|
||||
const hash = autoplanEditLineHash(chunk.text);
|
||||
if (chunk.kind === '+') return chunk.partial || !digest.newLineHashes.includes(hash);
|
||||
if (chunk.kind !== '-') return false; // Current-file context was checked above.
|
||||
return chunk.partial
|
||||
? !originals.some(line => line.endsWith(compact(chunk.text)) && digest.oldLineHashes.includes(autoplanEditLineHash(line)))
|
||||
: !digest.oldLineHashes.includes(hash);
|
||||
})) return null;
|
||||
return {input:'1\r', signature, file:p.file};
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
/** A native hook identifies the executing request within a published tool batch. */
|
||||
export function publishedAutoplanArtifactPermissionInput(viewport: string,
|
||||
context: ArtifactPermissionContext & { pending?: PendingAutoplanArtifact; viewportCapturedAt: number },
|
||||
seen: ReadonlySet<string>,
|
||||
): { input: '1\r'; signature: string; file: string } | null {
|
||||
const p=context.pending, now=context.now??Date.now();
|
||||
if (!p || p.source!=='pre_tool_use' || p.tool!=='Edit' || !validAutoplanEditDigest(p.editDigest) ||
|
||||
context.transcriptStatus!=='ready' || !Number.isFinite(now) || !Number.isFinite(context.commandStartedAt) ||
|
||||
!Number.isFinite(context.viewportCapturedAt) || context.commandStartedAt>context.viewportCapturedAt ||
|
||||
context.viewportCapturedAt>now || context.publicTools.length>10_000 || viewport.length>MAX_BYTES ||
|
||||
!/^[A-Za-z0-9_-]{1,160}$/.test(p.sessionId) || !/^[A-Za-z0-9_-]{1,160}$/.test(p.toolUseId) ||
|
||||
!Array.isArray(p.hookSeenIds) || !p.hookSeenIds.length || p.hookSeenIds.length>128 ||
|
||||
p.hookSeenIds.some(id=>typeof id!=='string'||!/^[A-Za-z0-9_-]{1,160}$/.test(id)) ||
|
||||
new Set(p.hookSeenIds).size!==p.hookSeenIds.length || !p.hookSeenIds.includes(p.toolUseId) ||
|
||||
seen.has(`${p.sessionId}:${p.toolUseId}`) || seen.has(autoplanArtifactMenuKey(viewport)) ||
|
||||
!ownedAutoplanArtifact(p.file,context)) return null;
|
||||
const pendingTime=Date.parse(p.timestamp);
|
||||
if (!Number.isFinite(pendingTime) || pendingTime<context.commandStartedAt || pendingTime>context.viewportCapturedAt ||
|
||||
context.publicTools.some(e=>!Number.isFinite(Date.parse(e.timestamp)))) return null;
|
||||
const events=context.publicTools.filter(e=>Date.parse(e.timestamp)>=context.commandStartedAt);
|
||||
const uses=new Map<string,NativePublicToolEvent>(), results=new Map<string,NativePublicToolEvent>();
|
||||
let last=context.commandStartedAt;
|
||||
for (const event of events) {
|
||||
const time=Date.parse(event.timestamp), map=event.kind==='use'?uses:results;
|
||||
if (event.sessionId!==p.sessionId || !event.toolUseId || time<last || time>now || map.has(event.toolUseId) ||
|
||||
(event.kind==='result'&&!uses.has(event.toolUseId))) return null;
|
||||
last=time;map.set(event.toolUseId,event);
|
||||
}
|
||||
const current=uses.get(p.toolUseId), input=current?.input;
|
||||
if (!current || current.name!=='Edit' || results.has(p.toolUseId) || Date.parse(current.timestamp)>pendingTime ||
|
||||
!/^msg_[A-Za-z0-9_-]{1,160}$/.test(current.messageId??'') || !/^req_[A-Za-z0-9_-]{1,160}$/.test(current.requestId??'') ||
|
||||
input?.file_path!==p.file || typeof input.old_string!=='string' || !input.old_string ||
|
||||
typeof input.new_string!=='string' || input.new_string===input.old_string ||
|
||||
(input.replace_all!==undefined&&input.replace_all!==false)) return null;
|
||||
const queued=new Set<string>();
|
||||
let queuedPlans = 0;
|
||||
for (const mutation of [...uses.values()].filter(e=>e.name==='Edit'||e.name==='Write')) {
|
||||
const result=results.get(mutation.toolUseId);
|
||||
if (result && (Date.parse(mutation.timestamp)>pendingTime || Date.parse(result.timestamp)>pendingTime)) return null;
|
||||
if (mutation.toolUseId===p.toolUseId || result) continue;
|
||||
// Later publications are queued only when this exact batch owns them and
|
||||
// the recorder has not started them. They never supply current authority.
|
||||
const nativePlan = mutation.input?.file_path !== p.file &&
|
||||
ownedQueuedNativePlan(mutation.input?.file_path, context.ownedNativePlansRoot, pendingTime);
|
||||
if (mutation.name!=='Edit' || (mutation.input?.file_path!==p.file && !nativePlan) ||
|
||||
typeof mutation.input.old_string!=='string' || !mutation.input.old_string ||
|
||||
typeof mutation.input.new_string!=='string' || mutation.input.old_string===mutation.input.new_string ||
|
||||
(mutation.input.replace_all!==undefined && mutation.input.replace_all!==false) ||
|
||||
mutation.messageId!==current.messageId || mutation.requestId!==current.requestId ||
|
||||
Date.parse(mutation.timestamp)>context.viewportCapturedAt ||
|
||||
events.indexOf(mutation)<=events.indexOf(current) || p.hookSeenIds.includes(mutation.toolUseId)) return null;
|
||||
if (nativePlan) {
|
||||
if (![...uses.values()].some(previous => (previous.name === 'Write' || previous.name === 'Edit') &&
|
||||
previous.input?.file_path === mutation.input!.file_path &&
|
||||
results.get(previous.toolUseId)?.isError === false &&
|
||||
Date.parse(results.get(previous.toolUseId)!.timestamp) <= pendingTime)) return null;
|
||||
queuedPlans++;
|
||||
}
|
||||
queued.add(mutation.toolUseId);
|
||||
}
|
||||
try {
|
||||
if (Math.floor(fs.statSync(p.file).mtimeMs)>pendingTime) return null;
|
||||
const actual=createAutoplanEditDigest(p.file,input.old_string,input.new_string), expected=p.editDigest!;
|
||||
if (!actual || actual.beforeSHA256!==expected.beforeSHA256 || actual.requestSHA256!==expected.requestSHA256 ||
|
||||
JSON.stringify(actual.oldLineHashes)!==JSON.stringify(expected.oldLineHashes) ||
|
||||
JSON.stringify(actual.newLineHashes)!==JSON.stringify(expected.newLineHashes)) return null;
|
||||
const commandViewport = queuedCommandViewport(viewport,current,events,queued,p.hookSeenIds,context.viewportCapturedAt);
|
||||
const rendered = queuedArtifactViewport(commandViewport,current,events,queued,p.file,pendingTime,context.ownedStateRoot);
|
||||
return autoplanArtifactPermissionInput(queuedPlanViewport(rendered,queuedPlans,current,events),{...context,
|
||||
publicTools:events.filter(e=>!queued.has(e.toolUseId))},seen);
|
||||
} catch { return null; }
|
||||
}
|
||||
@@ -1,380 +0,0 @@
|
||||
import { readOwnedClaudeTranscript } from './owned-claude-transcript';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { isDeepStrictEqual } from 'node:util';
|
||||
import { currentFilePermissionTarget, nativePermissionKey, reserveNativePermissionGrant, type readPlanSkillQuestions, type NativeFilePermissionRequest, type NativePermissionGrant } from './plan-skill-questions';
|
||||
import type { ClaudePtySession } from './claude-pty-runner';
|
||||
import { readQuestionEvents, readQuestionCompletionEvents, readBashEvents, readBashCompletionEvents, readBashPermissionRequestEvents, type QuestionEventSource,
|
||||
type QuestionEventCall, type QuestionCompletionEventCall, type BashEventCall, type BashCompletionEventCall, type BashPermissionRequestEventCall } from './plan-skill-question-events';
|
||||
|
||||
/** The chain may grant file edits in its fixture and native plan directory.
|
||||
* The shared reservation still requires the exact owned request and menu;
|
||||
* a permission-looking screen alone never authorizes an input. */
|
||||
export function reserveAutoplanFilePermission(
|
||||
native: ReturnType<typeof readPlanSkillQuestions>, visible: string,
|
||||
opts: { cwd: string; planDir: string; granted: Set<string>; requests: Map<string, NativePermissionGrant> },
|
||||
): boolean {
|
||||
if (native.pendingBytes || native.ready || native.calls.some(call => call.result === 'pending')) return false;
|
||||
const pending = native.permissionRequests.filter(request => request.result === 'pending');
|
||||
if (!native.permissionRequestCapture || pending.length !== 1) return false;
|
||||
const request = pending[0]!;
|
||||
const cwd = fs.realpathSync(opts.cwd);
|
||||
if (request.cwd !== cwd) throw new Error('Autoplan file permission cwd differs from its fixture');
|
||||
const file = request.input.file_path;
|
||||
if (typeof file !== 'string' || !path.isAbsolute(file)) throw new Error('Autoplan file permission lacks an absolute path');
|
||||
const normalized = path.normalize(file);
|
||||
const root = [cwd, opts.planDir].find(root => normalized.startsWith(root + path.sep));
|
||||
if (!root) throw new Error('Autoplan file permission is outside its fixture and native plan directory');
|
||||
// Reject symlink escapes, including a not-yet-created file below a link.
|
||||
for (let entry = normalized; entry !== path.dirname(root); entry = path.dirname(entry)) {
|
||||
if (fs.lstatSync(entry, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
||||
throw new Error('Autoplan file permission traverses a symlink');
|
||||
}
|
||||
}
|
||||
return reserveNativePermissionGrant(native, visible, opts.granted, opts.requests);
|
||||
}
|
||||
|
||||
/** Recover only a clipped file identity; the existing reservation remains the
|
||||
* grant authority. The three fresh paints are finite, not a promise that every
|
||||
* possible diff fits. A resize is never a decision or native completion. */
|
||||
export class AutoplanFilePermissionViewport {
|
||||
private owner: NativeFilePermissionRequest | null = null;
|
||||
private paints = 0;
|
||||
inputMark = -1;
|
||||
get active(): boolean { return this.owner !== null; }
|
||||
|
||||
constructor(private readonly opts: {
|
||||
session: Pick<ClaudePtySession, 'resizeQuestionViewport' | 'mark'>;
|
||||
deadlineAt: number;
|
||||
granted: Set<string>;
|
||||
}) {}
|
||||
|
||||
/** Called only after the caller's unchanged native/current-screen bracket.
|
||||
* true means wait for another sample, never type a permission choice. */
|
||||
async advance(native: ReturnType<typeof readPlanSkillQuestions>, frame: { text: string; rawEnd: number }): Promise<boolean> {
|
||||
if (!this.owner) return false;
|
||||
const owner = native.permissionRequests.find(request => request.requestId === this.owner!.requestId);
|
||||
if (!owner || owner.name !== this.owner.name || owner.cwd !== this.owner.cwd
|
||||
|| owner.capturedAtMs !== this.owner.capturedAtMs || !isDeepStrictEqual(owner.input, this.owner.input)
|
||||
|| this.owner.nativeToolId != null && owner.nativeToolId !== this.owner.nativeToolId) {
|
||||
throw new Error('Autoplan expanded file permission changed ownership or input');
|
||||
}
|
||||
// A hook can precede transcript persistence. Once linked, retain that ID.
|
||||
if (this.owner.nativeToolId == null && owner.nativeToolId != null) this.owner.nativeToolId = owner.nativeToolId;
|
||||
if (owner.result === 'error') throw new Error('Autoplan expanded file permission returned an error');
|
||||
if (owner.result === 'completed') {
|
||||
if (!owner.nativeToolId || !Number.isFinite(owner.nativeResultAtMs)) throw new Error('Autoplan expanded file permission lacks its successful native ACK');
|
||||
const restored = await this.opts.session.resizeQuestionViewport!(120, this.opts.deadlineAt);
|
||||
if (restored !== null) { this.inputMark = restored; this.owner = null; }
|
||||
return true;
|
||||
}
|
||||
// A queued Bash cannot own this pinned file repaint. The unchanged exact
|
||||
// current-card reservation below still decides the sole file grant.
|
||||
if (native.permissionRequests.filter(request => request.result === 'pending').length !== 1
|
||||
|| native.permissionTools.some(tool => tool.id !== owner.nativeToolId && tool.name !== 'Bash')) {
|
||||
throw new Error('Ambiguous native permission owner during Autoplan viewport recovery');
|
||||
}
|
||||
if (native.pendingBytes || frame.rawEnd !== this.opts.session.mark() || frame.rawEnd <= this.inputMark
|
||||
|| this.opts.granted.has(`request:${owner.requestId}`)) return true;
|
||||
try { nativePermissionKey(owner, frame.text); return false; }
|
||||
catch (error) {
|
||||
if (!this.clipped(owner, frame.text) || this.paints === 3) throw error;
|
||||
return this.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/** The caller first runs all existing fixture/symlink/owner/grant checks.
|
||||
* A clipped identity can also fail disambiguation against a queued Bash;
|
||||
* neither error authorizes input before the full file card is recovered. */
|
||||
async recover(error: unknown, native: ReturnType<typeof readPlanSkillQuestions>, frame: { text: string; rawEnd: number }): Promise<boolean> {
|
||||
const identityError = error instanceof Error && (error.message === 'Visible permission cannot be bound to its pending native command or file path'
|
||||
|| error.message === 'Ambiguous native permission owner: multiple tools are pending'
|
||||
&& native.permissionTools.some(tool => tool.name === 'Bash'));
|
||||
if (!identityError || this.owner || !this.opts.session.resizeQuestionViewport || frame.rawEnd !== this.opts.session.mark()
|
||||
|| native.pendingBytes || native.ready || native.calls.some(call => call.result === 'pending')) return false;
|
||||
const pending = native.permissionRequests.filter(request => request.result === 'pending');
|
||||
const owner = pending[0];
|
||||
if (!native.permissionRequestCapture || pending.length !== 1 || !owner
|
||||
|| native.permissionTools.some(tool => tool.id !== owner.nativeToolId && tool.name !== 'Bash')
|
||||
|| this.opts.granted.has(`request:${owner.requestId}`) || !this.clipped(owner, frame.text)) return false;
|
||||
this.owner = structuredClone(owner);
|
||||
this.paints = 0;
|
||||
return this.repaint();
|
||||
}
|
||||
|
||||
private clipped(owner: NativeFilePermissionRequest, visible: string): boolean {
|
||||
const target = currentFilePermissionTarget(visible), file = owner.input.file_path;
|
||||
return target !== null && owner.name === (target.operation === 'edit' ? 'Edit' : 'Write')
|
||||
&& typeof file === 'string' && path.isAbsolute(file)
|
||||
&& path.basename(target.filePath) === target.filePath && path.basename(file) === target.filePath
|
||||
&& !/^ (?:Create|Edit|Overwrite) file$/m.test(visible);
|
||||
}
|
||||
|
||||
private async repaint(): Promise<boolean> {
|
||||
const mark = await this.opts.session.resizeQuestionViewport!(this.paints === 0 ? 240 : this.paints === 1 ? 480 : 960, this.opts.deadlineAt);
|
||||
if (mark !== null) { this.inputMark = mark; this.paints++; }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/** The PTY renders Markdown without stars and may position spaces via ANSI.
|
||||
* Keep complete-word bounds and stream order; callers dedupe first observations.
|
||||
*/
|
||||
export function observedAutoplanPhases(visible: string): number[] {
|
||||
return [...visible.matchAll(/\bPhase\s*(\d+(?:\.\d+)?)\s*complete\b/g)]
|
||||
.map(match => Number(match[1]));
|
||||
}
|
||||
|
||||
export interface AutoplanTranscriptObservation {
|
||||
file: string | null;
|
||||
phases: number[];
|
||||
completedLines: number;
|
||||
pendingBytes: number;
|
||||
}
|
||||
|
||||
/** Preserve the owned commands needed to investigate a stopped chain before
|
||||
* its fixture is deleted. This diagnostic cannot change a phase verdict.
|
||||
*/
|
||||
export function retainAutoplanFailure(opts: {
|
||||
configDir: string | null; sessionId: string; observation: unknown;
|
||||
raw: () => string; visible: () => string; evalDir?: string;
|
||||
/** Owned question evidence and the last decoded viewport are bounded; raw history stays hashed. */
|
||||
counting?: { native: ReturnType<typeof readPlanSkillQuestions> | null; dialog: string; events?: QuestionEventSource | null;
|
||||
frame?: { text: string; rawEnd: number; observedAtMs: number; questionSince: number; viewportInputSince: number } | null };
|
||||
}): string | null {
|
||||
try {
|
||||
const raw = opts.raw();
|
||||
const visible = opts.visible();
|
||||
const native = readOwnedClaudeTranscript(opts.configDir, opts.sessionId);
|
||||
const clip = (text: string, limit = 32_768) => ({
|
||||
text: text.slice(0, limit), codeUnits: text.length, truncated: text.length > limit,
|
||||
sha256: createHash('sha256').update(text).digest('hex'),
|
||||
});
|
||||
const signature = (value: unknown) => {
|
||||
const { text: _omitted, ...digest } = clip(JSON.stringify(value) ?? '');
|
||||
return { type: value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value, ...digest };
|
||||
};
|
||||
const safeInput = (name: string, input: any) => ({ ...signature(input),
|
||||
...(['Read', 'Write', 'Edit'].includes(name) && typeof input?.file_path === 'string' ? { filePath: input.file_path.slice(0, 4096) } : {}),
|
||||
});
|
||||
const calls: Array<{ id: string; name: string; input: unknown; timestamp: unknown; cwd: unknown }> = [];
|
||||
const results = new Map<string, boolean>();
|
||||
for (const row of native.rows) {
|
||||
const message = row.message;
|
||||
if (!Array.isArray(message?.content)) continue;
|
||||
for (const block of message.content) {
|
||||
if (row.type === 'user' && message.role === 'user' && block?.type === 'tool_result' && typeof block.tool_use_id === 'string') {
|
||||
results.set(block.tool_use_id, block.is_error === true);
|
||||
}
|
||||
if (row.type === 'assistant' && message.role === 'assistant' && block?.type === 'tool_use'
|
||||
&& typeof block.id === 'string' && typeof block.name === 'string') {
|
||||
calls.push({ id: block.id, name: block.name, input: block.input ?? null, timestamp: row.timestamp, cwd: row.cwd });
|
||||
}
|
||||
}
|
||||
}
|
||||
const pending = calls.filter(call => !results.has(call.id));
|
||||
const queue = native.rows.map((row, index) => ({ row, index })).filter(({ row }) => row.type === 'queue-operation');
|
||||
const state = opts.counting?.native;
|
||||
// The failing reader may not have updated state. Preserve current native
|
||||
// AUQs and revalidated hook inputs as well as the earlier pending snapshot.
|
||||
// These later diagnostic reads never supply an answer or change the error.
|
||||
const observedPending = state?.calls.filter(call => call.result === 'pending') ?? [];
|
||||
let hookQuestions: QuestionEventCall[] = [];
|
||||
let hookCompletions: QuestionCompletionEventCall[] = [];
|
||||
let bashInvocations: BashEventCall[] = [];
|
||||
let bashCompletions: BashCompletionEventCall[] = [];
|
||||
let bashRequests: BashPermissionRequestEventCall[] = [];
|
||||
let hookReadError: ReturnType<typeof clip> | null = null;
|
||||
if (opts.counting?.events) {
|
||||
try { hookQuestions = readQuestionEvents(opts.counting.events, { configDir: opts.configDir,
|
||||
sessionId: opts.sessionId, transcriptFile: native.file });
|
||||
hookCompletions = readQuestionCompletionEvents(opts.counting.events, { configDir: opts.configDir,
|
||||
sessionId: opts.sessionId, transcriptFile: native.file });
|
||||
bashInvocations = readBashEvents(opts.counting.events, { configDir: opts.configDir,
|
||||
sessionId: opts.sessionId, transcriptFile: native.file });
|
||||
bashCompletions = readBashCompletionEvents(opts.counting.events, { configDir: opts.configDir,
|
||||
sessionId: opts.sessionId, transcriptFile: native.file });
|
||||
bashRequests = readBashPermissionRequestEvents(opts.counting.events, { configDir: opts.configDir,
|
||||
sessionId: opts.sessionId, transcriptFile: native.file }); }
|
||||
catch (error) { hookReadError = clip(String(error), 1024); }
|
||||
}
|
||||
const bashCandidateIds = [...new Set([...(state?.permissionTools.filter(tool => tool.name === 'Bash').map(tool => tool.id) ?? []),
|
||||
...bashCompletions.slice().sort((a, b) => b.capturedAtMs - a.capturedAtMs).map(event => event.id),
|
||||
...bashInvocations.slice().sort((a, b) => b.capturedAtMs - a.capturedAtMs).map(event => event.id)])];
|
||||
const bashIds = new Set(bashCandidateIds.slice(0, 16));
|
||||
const selectedBashRequests = bashRequests.filter(request => bashInvocations.some(invoked => bashIds.has(invoked.id)
|
||||
&& invoked.cwd === request.cwd && JSON.stringify(invoked.input) === JSON.stringify(request.input)));
|
||||
const nativeQuestions = calls.filter(call => call.name === 'AskUserQuestion');
|
||||
const candidateIds = [...new Set([...observedPending.slice(-16).map(call => call.id),
|
||||
...hookCompletions.slice().sort((a, b) => b.capturedAtMs - a.capturedAtMs).map(event => event.id),
|
||||
...nativeQuestions.slice().reverse().map(call => call.id), ...hookQuestions.map(call => call.id),
|
||||
...observedPending.map(call => call.id)])];
|
||||
const questionIds = new Set(candidateIds.slice(0, 16));
|
||||
const questionBlocks: unknown[] = [];
|
||||
if (opts.counting) for (const [rowIndex, row] of native.rows.entries()) {
|
||||
const message = row.message;
|
||||
if (!Array.isArray(message?.content)) continue;
|
||||
for (const block of message.content) {
|
||||
const invocation = row.type === 'assistant' && message.role === 'assistant'
|
||||
&& block?.type === 'tool_use' && block.name === 'AskUserQuestion' && questionIds.has(block.id);
|
||||
const result = row.type === 'user' && message.role === 'user'
|
||||
&& block?.type === 'tool_result' && questionIds.has(block.tool_use_id);
|
||||
if (!invocation && !result) continue;
|
||||
questionBlocks.push({ rowIndex, type: row.type, stopReason: message.stop_reason ?? null,
|
||||
timestamp: typeof row.timestamp === 'string' ? clip(row.timestamp, 256) : null,
|
||||
cwd: typeof row.cwd === 'string' ? clip(row.cwd, 4096) : null,
|
||||
blockJson: clip(JSON.stringify(block), 65_536),
|
||||
...(result && row.toolUseResult !== undefined ? { toolUseResultJson: clip(JSON.stringify(row.toolUseResult), 65_536) } : {}) });
|
||||
}
|
||||
}
|
||||
const fileTarget = opts.counting ? currentFilePermissionTarget(opts.counting.dialog) : null;
|
||||
const counting = opts.counting ? {
|
||||
dialog: { ...signature(opts.counting.dialog), currentFileTarget: fileTarget ? { ...fileTarget, filePath: fileTarget.filePath.slice(0, 4096) } : null },
|
||||
decodedFrame: opts.counting.frame ? { source: 'last-sampled-current-screen', ...clip(opts.counting.frame.text, 65_536),
|
||||
rawEnd: opts.counting.frame.rawEnd, observedAtMs: opts.counting.frame.observedAtMs,
|
||||
questionSince: opts.counting.frame.questionSince, viewportInputSince: opts.counting.frame.viewportInputSince } : null,
|
||||
nativeObserved: state !== null, permissionRequestCapture: state?.permissionRequestCapture ?? null,
|
||||
permissionToolCount: state?.permissionTools.length ?? null,
|
||||
permissionTools: state?.permissionTools.slice(-16).map(tool => ({ id: clip(tool.id, 256), name: clip(tool.name, 256),
|
||||
cwd: typeof tool.cwd === 'string' ? clip(tool.cwd, 4096) : null, input: safeInput(tool.name, tool.input) })) ?? [],
|
||||
permissionRequestCount: state?.permissionRequests.length ?? null,
|
||||
permissionRequests: state?.permissionRequests.slice(-16).map(request => ({ requestId: clip(request.requestId, 256),
|
||||
nativeToolId: request.nativeToolId?.slice(0, 256) ?? null, name: request.name, result: request.result,
|
||||
capturedAtMs: request.capturedAtMs, cwd: clip(request.cwd, 4096), input: safeInput(request.name, request.input) })) ?? [],
|
||||
bashEvidence: {
|
||||
candidateCount: bashCandidateIds.length, candidatesOmitted: Math.max(0, bashCandidateIds.length - 16),
|
||||
chronology: 'Later owned diagnostic reads; no grant or outcome credit. Pending IDs first, then latest native resolution and invocation observations. Background resolution is not command completion.',
|
||||
permissionRequestCount: selectedBashRequests.length, permissionRequestsOmitted: Math.max(0, selectedBashRequests.length - 16),
|
||||
permissionRequests: selectedBashRequests.slice(-16).map(event => ({ requestId: clip(event.requestId, 256),
|
||||
capturedAtMs: event.capturedAtMs, cwd: clip(event.cwd, 4096), inputJson: clip(JSON.stringify(event.input), 65_536) })),
|
||||
invocations: bashInvocations.filter(event => bashIds.has(event.id)).map(event => ({ id: clip(event.id, 256),
|
||||
capturedAtMs: event.capturedAtMs, cwd: clip(event.cwd, 4096), inputJson: clip(JSON.stringify(event.input), 65_536) })),
|
||||
resolutions: bashCompletions.filter(event => bashIds.has(event.id)).map(event => ({ id: clip(event.id, 256),
|
||||
hookEventName: event.hookEventName, capturedAtMs: event.capturedAtMs, cwd: clip(event.cwd, 4096),
|
||||
inputJson: clip(JSON.stringify(event.input), 65_536), responseJson: clip(JSON.stringify(event.response), 65_536) })),
|
||||
hookReadError,
|
||||
},
|
||||
questionEvidence: {
|
||||
count: observedPending.length, omitted: Math.max(0, observedPending.length - 16),
|
||||
candidateCount: candidateIds.length, candidatesOmitted: Math.max(0, candidateIds.length - 16),
|
||||
chronology: 'Earlier counting snapshot; later owned transcript and hook reads are not atomic. Pending IDs have priority, then latest completion observations, latest native AUQs, and remaining hooks. Completion observations do not grant diagnostic answer credit; hook order is not execution order.',
|
||||
observed: observedPending.filter(call => questionIds.has(call.id)).map(call => ({ id: clip(call.id, 256),
|
||||
observedResult: call.result, questionsJson: clip(JSON.stringify(call.questions), 65_536),
|
||||
resultAtRetention: results.has(call.id) ? results.get(call.id) ? 'error' : 'completed'
|
||||
: nativeQuestions.some(nativeCall => nativeCall.id === call.id) ? 'pending' : 'absent' })),
|
||||
hookEvents: hookQuestions.filter(event => questionIds.has(event.id)).map(event => ({ id: clip(event.id, 256),
|
||||
toolName: event.toolName, cwd: clip(event.cwd, 4096), inputJson: clip(JSON.stringify(event.input), 65_536) })),
|
||||
hookCompletionEvents: hookCompletions.filter(event => questionIds.has(event.id)).map(event => ({ id: clip(event.id, 256),
|
||||
toolName: event.toolName, capturedAtMs: event.capturedAtMs, cwd: clip(event.cwd, 4096),
|
||||
inputJson: clip(JSON.stringify(event.input), 65_536), responseJson: clip(JSON.stringify(event.response), 65_536) })),
|
||||
hookReadError,
|
||||
nativeBlocks: { count: questionBlocks.length, omitted: Math.max(0, questionBlocks.length - 32), rows: questionBlocks.slice(-32) },
|
||||
},
|
||||
queueOperations: { count: queue.length, omitted: Math.max(0, queue.length - 16), rows: queue.slice(-16).map(({ row, index }) => ({
|
||||
rowIndex: index, operation: typeof row.operation === 'string' ? clip(row.operation, 64) : signature(row.operation),
|
||||
content: signature(row.content), uuid: typeof row.uuid === 'string' ? clip(row.uuid, 256) : null,
|
||||
timestamp: typeof row.timestamp === 'string' ? clip(row.timestamp, 256) : null,
|
||||
})) },
|
||||
} : undefined;
|
||||
const record = {
|
||||
schemaVersion: 1, sessionId: opts.sessionId, capturedAt: new Date().toISOString(),
|
||||
nativeFile: native.file, completedLines: native.completedLines, pendingBytes: native.pendingBytes,
|
||||
observation: clip(JSON.stringify(opts.observation)),
|
||||
calls: calls.slice(-16).map(call => ({ id: clip(call.id, 256), name: clip(call.name, 256),
|
||||
timestamp: clip(String(call.timestamp), 256),
|
||||
...(counting ? { input: safeInput(call.name, call.input), cwd: typeof call.cwd === 'string' ? clip(call.cwd, 4096) : null } : { inputJson: clip(JSON.stringify(call.input)) }),
|
||||
result: results.has(call.id) ? results.get(call.id) ? 'error' : 'completed' : 'pending' })),
|
||||
callCount: calls.length, callsOmitted: Math.max(0, calls.length - 16),
|
||||
pendingIds: pending.slice(-64).map(call => clip(call.id, 256)),
|
||||
pendingCount: pending.length, pendingIdsOmitted: Math.max(0, pending.length - 64),
|
||||
rawTail: { ...(counting ? signature(raw.slice(-65_536)) : clip(raw.slice(-65_536), 65_536)), omittedPrefixCodeUnits: Math.max(0, raw.length - 65_536) }, rawCodeUnits: raw.length,
|
||||
visibleTail: { ...(counting ? signature(visible.slice(-65_536)) : clip(visible.slice(-65_536), 65_536)), omittedPrefixCodeUnits: Math.max(0, visible.length - 65_536) }, visibleCodeUnits: visible.length,
|
||||
...(counting ? { counting } : {}),
|
||||
limits: counting ? 'Diagnostic only. Selected owned AUQ/Bash hook inputs/results and the last sampled decoded viewport are retained with explicit clipping. The viewport keeps its own observation/input epochs; it is not resampled at retention or proof of current ownership. Other native inputs, queue content and raw/flattened history stay hashed. Native thinking and unrelated/foreign results are omitted. Later evidence cannot change the observation, grant input or establish completion.' : 'Diagnostic only. Pending tools are not proof of a permission prompt or a failed command. Native thinking, signatures and tool results are omitted; clipped command inputs remain incomplete evidence.',
|
||||
};
|
||||
const evalDir = opts.evalDir ?? process.env.GSTACK_EVAL_DIR;
|
||||
if (!evalDir) throw new Error('GSTACK_EVAL_DIR is not configured');
|
||||
const category = counting ? 'plan-counting' : 'autoplan-chain';
|
||||
const directory = path.join(evalDir, category);
|
||||
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
||||
const file = path.join(directory, `${opts.sessionId}.json`);
|
||||
const serialized = JSON.stringify(record, null, 2) + '\n';
|
||||
if (Buffer.byteLength(serialized) > 8_388_608) throw new Error('Autoplan diagnostic exceeds its 8 MiB bound');
|
||||
fs.writeFileSync(file, serialized, { flag: 'wx', mode: 0o600 });
|
||||
console.error(`[${category}] failure diagnostic: ${file}`);
|
||||
return file;
|
||||
} catch (error) {
|
||||
try { console.error(`Autoplan failure diagnostic could not be retained: ${String(error).slice(0, 1024)}`); } catch { /* preserve the original outcome */ }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function announcedAutoplanPhases(text: string): number[] {
|
||||
const phases: number[] = [];
|
||||
let fence: string | null = null;
|
||||
for (const line of text.split(/\r?\n/)) {
|
||||
const delimiter = line.match(/^ {0,3}(?:> ?)?(`{3,}|~{3,})/)?.[1];
|
||||
if (delimiter) {
|
||||
if (!fence) fence = delimiter;
|
||||
else if (delimiter[0] === fence[0] && delimiter.length >= fence.length) fence = null;
|
||||
continue;
|
||||
}
|
||||
if (fence) continue;
|
||||
const announcement = line.match(/^ {0,3}(?:> ?)?(?:\*\*)?Phase +(\d+(?:\.\d+)?) +complete\.(?:\*\*)?(?:\s|$)/);
|
||||
if (announcement) phases.push(Number(announcement[1]));
|
||||
}
|
||||
return phases;
|
||||
}
|
||||
|
||||
/** Read only the UUID pinned at launch, directly below a project directory.
|
||||
* Tool payloads, user messages, and sidechain responses cannot announce phases.
|
||||
*/
|
||||
export function readAutoplanTranscript(configDir: string | null, sessionId: string): AutoplanTranscriptObservation {
|
||||
const { file, rows, completedLines, pendingBytes } = readOwnedClaudeTranscript(configDir, sessionId);
|
||||
const phases: number[] = [];
|
||||
for (const row of rows) {
|
||||
if (row.type !== 'assistant' || row.message?.role !== 'assistant') continue;
|
||||
if (!Array.isArray(row.message.content)) continue;
|
||||
for (const block of row.message.content) {
|
||||
if (block?.type !== 'text' || typeof block.text !== 'string') continue;
|
||||
for (const phase of announcedAutoplanPhases(block.text)) {
|
||||
if (!phases.includes(phase)) phases.push(phase);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { file, phases, completedLines, pendingBytes };
|
||||
}
|
||||
|
||||
/** Assistant order is authoritative; rendered previews cannot establish order.
|
||||
* Match its prefix in the PTY stream, ignoring unrelated earlier tool previews.
|
||||
*/
|
||||
export function corroboratedAutoplanPhases(assistantPhases: readonly number[], visible: string): number[] {
|
||||
let count = 0;
|
||||
for (const phase of observedAutoplanPhases(visible)) {
|
||||
if (phase === assistantPhases[count]) count++;
|
||||
}
|
||||
return assistantPhases.slice(0, count);
|
||||
}
|
||||
|
||||
/** Validate first-observed completion markers in stream order. Poll timestamps
|
||||
* cannot establish order: several phases may first appear in the same batch.
|
||||
*/
|
||||
export function validateAutoplanPhaseOrder(phases: readonly number[]): void {
|
||||
const observed = phases.join(' -> ') || '(none)';
|
||||
if (!phases.includes(1) || !phases.includes(3)) {
|
||||
throw new Error(`Autoplan requires CEO (1) and Eng (3) completion; observed: ${observed}`);
|
||||
}
|
||||
if (phases.at(-1) !== 3) {
|
||||
throw new Error(`Autoplan Eng (3) must complete last; observed: ${observed}`);
|
||||
}
|
||||
const expected = [1, 2, 2.5, 3];
|
||||
let previous = -1;
|
||||
for (const phase of phases) {
|
||||
const position = expected.indexOf(phase);
|
||||
if (position <= previous) {
|
||||
throw new Error(`Autoplan completion order must be CEO (1), optional Design (2), optional DX (2.5), Eng (3); observed: ${observed}`);
|
||||
}
|
||||
previous = position;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import path from 'node:path';
|
||||
|
||||
/** Rebase recorded Unix paths without inserting raw backslashes into JSON. */
|
||||
export function capturedPathRebaser(replacements: Array<[string, string]>) {
|
||||
const text = (value: string) => replacements.reduce((value, [before, after]) =>
|
||||
value.replaceAll(before, after.split(path.sep).join('/')), value);
|
||||
// Translate separators without resolving traversal, redundant separators, or
|
||||
// relative spelling that an ownership rejection control must still observe.
|
||||
const file = (value: string) => text(value).split('/').join(path.sep);
|
||||
const pathKeys = new Set(['cwd', 'config', 'stateRoot', 'file', 'file_path', 'transcriptPath']);
|
||||
const json = <T>(value: T): T => JSON.parse(JSON.stringify(value), (key, value) =>
|
||||
typeof value === 'string' ? (pathKeys.has(key) ? file(value) : text(value)) : value);
|
||||
return { text, file, json };
|
||||
}
|
||||
@@ -54,20 +54,6 @@ export function seedCeoPaymentProject(projectDir: string, plan: string): void {
|
||||
git(['update-ref', 'refs/remotes/origin/main', 'HEAD']);
|
||||
}
|
||||
|
||||
/** Materialized documentation for the revised synthetic DX baseline. The SDK
|
||||
* implementation is deliberately absent; this does not run or install it. */
|
||||
export function seedDevexReviewProject(projectDir: string, plan: string): void {
|
||||
seedPlanReviewProject(projectDir, plan, 'plan-devex-review');
|
||||
const fixture = path.resolve(import.meta.dir, '../fixtures/devex-existing-sdk');
|
||||
const files = ['README.md', 'docs/getting-started.md', 'docs/feedback.md', 'docs/reference-v1.md'];
|
||||
fs.mkdirSync(path.join(projectDir, 'docs'));
|
||||
for (const file of files) fs.copyFileSync(path.join(fixture, file), path.join(projectDir, file));
|
||||
const git = (args: string[]) => execFileSync('git', args, { cwd: projectDir, stdio: 'pipe', timeout: 10_000 });
|
||||
git(['add', ...files]);
|
||||
git(['-c', 'user.name=Finding fixture', '-c', 'user.email=fixture@gstack.test', 'commit', '-m', 'Seed synthetic SDK documentation']);
|
||||
git(['update-ref', 'refs/remotes/origin/main', 'HEAD']);
|
||||
}
|
||||
|
||||
export function seedPlanReviewProject(projectDir: string, plan: string, skill: 'plan-ceo-review' | 'plan-eng-review' | 'plan-design-review' | 'plan-devex-review', design?: string): void {
|
||||
if (!fs.lstatSync(projectDir).isDirectory() || fs.readdirSync(projectDir).length !== 0) {
|
||||
throw new Error('Plan review fixture requires a fresh private directory');
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { seedCeoFindingProject } from './ceo-finding-fixture';
|
||||
|
||||
/** Give the two-test review an existing function to inspect before its first turn. */
|
||||
export function seedCeoPairedProject(projectDir: string, plan: string): void {
|
||||
seedCeoFindingProject(projectDir, plan);
|
||||
const fixture = path.resolve(import.meta.dir, '../fixtures/paired-payment');
|
||||
fs.mkdirSync(path.join(projectDir, 'src'));
|
||||
for (const [source, target] of [['README.md', 'README.md'], ['src/payment.ts', 'src/payment.ts'],
|
||||
['contract.test.ts.fixture', 'contract.test.ts']]) {
|
||||
fs.copyFileSync(path.join(fixture, source!), path.join(projectDir, target!));
|
||||
}
|
||||
const git = (args: string[]) => execFileSync('git', args, { cwd: projectDir, stdio: 'pipe', timeout: 10_000 });
|
||||
git(['add', 'README.md', 'src/payment.ts', 'contract.test.ts']);
|
||||
git(['-c', 'user.name=Finding fixture', '-c', 'user.email=fixture@gstack.test', 'commit', '-m', 'Seed existing payment contracts']);
|
||||
git(['update-ref', 'refs/remotes/origin/main', 'HEAD']);
|
||||
}
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
import { resolveEvalModel } from '../../lib/eval-model';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { stripVTControlCharacters, isDeepStrictEqual } from 'node:util';
|
||||
import { hermeticChildEnv, hermeticSkillsConfigDir, isHermeticEnabled } from './hermetic-env';
|
||||
@@ -229,11 +228,6 @@ export async function selectPtyNumberedOption(
|
||||
session.send('\r');
|
||||
}
|
||||
|
||||
/** Detect a complete, recognized workspace-trust menu. */
|
||||
export function isTrustDialogVisible(visible: string): boolean {
|
||||
return trustDialogInput(visible) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect plan-mode's native "ready to execute" confirmation. Tests both the
|
||||
* spaced and whitespace-collapsed forms because stripAnsi removes cursor-
|
||||
@@ -4344,58 +4338,6 @@ export async function launchClaudePty(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* High-level: invoke a slash command and observe the response. Used by the
|
||||
* 5 plan-mode tests so each only has ~10 LOC of orchestration.
|
||||
*
|
||||
* The `expectations` object names the patterns the caller cares about.
|
||||
* Returns which one matched first (or throws on timeout).
|
||||
*
|
||||
* @example
|
||||
* const session = await launchClaudePty();
|
||||
* const result = await invokeAndObserve(session, '/plan-ceo-review', {
|
||||
* askUserQuestion: /❯\s*1\./,
|
||||
* planReady: /ready to execute/i,
|
||||
* silentWrite: /⏺\s*Write\(/,
|
||||
* silentEdit: /⏺\s*Edit\(/,
|
||||
* exitedPlanMode: /Exiting plan mode/i,
|
||||
* });
|
||||
* await session.close();
|
||||
*/
|
||||
export async function invokeAndObserve(
|
||||
session: ClaudePtySession,
|
||||
slashCommand: string,
|
||||
expectations: Record<string, RegExp | string>,
|
||||
opts?: { boot_grace_ms?: number; timeoutMs?: number },
|
||||
): Promise<{ matched: string; rawPattern: RegExp | string; visibleAtMatch: string }> {
|
||||
// Brief grace period so the trust-dialog auto-press has time to clear and
|
||||
// claude is back at the input prompt before we type the command.
|
||||
const boot = opts?.boot_grace_ms ?? 6000;
|
||||
await Bun.sleep(boot);
|
||||
|
||||
// Mark buffer position. All pattern matching scopes to text AFTER this point,
|
||||
// so the trust-dialog residue and boot banner numbered options don't cause
|
||||
// false positives.
|
||||
const sinceMark = session.mark();
|
||||
|
||||
// Type and submit.
|
||||
session.send(slashCommand + '\r');
|
||||
|
||||
const patterns = Object.entries(expectations);
|
||||
const result = await session.waitForAny(
|
||||
patterns.map(([, p]) => p),
|
||||
{ timeoutMs: opts?.timeoutMs ?? 240_000, since: sinceMark },
|
||||
);
|
||||
// Map back to the named key.
|
||||
const idx = patterns.findIndex(([, p]) => p === result.matched);
|
||||
const [name, rawPattern] = patterns[idx]!;
|
||||
return {
|
||||
matched: name,
|
||||
rawPattern,
|
||||
visibleAtMatch: session.visibleText(),
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// High-level skill-mode test contract
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -4823,9 +4765,6 @@ export async function runPlanSkillObservation(opts: {
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
/** Legacy caller outer finalization allowance; native counting reserves cleanup internally. */
|
||||
export const PLAN_SKILL_COUNT_FINALIZE_MS = 10_000;
|
||||
|
||||
// runPlanSkillCounting — drives a plan-* skill end-to-end through Step 0 then
|
||||
// counts completed review-phase AskUserQuestion calls. The actual
|
||||
// product asserted by the per-finding-count tests.
|
||||
|
||||
@@ -297,6 +297,12 @@ describe('isPermissionDialogVisible', () => {
|
||||
// post-merge follow-up. Flip this assertion once the regex tightens.
|
||||
expect(isPermissionDialogVisible(sample)).toBe(true);
|
||||
});
|
||||
|
||||
test('matches the captured Autoplan settings-overwrite card as a numbered permission dialog', () => {
|
||||
const captured = JSON.parse(readFileSync(new URL('../fixtures/autoplan-settings-overwrite.json', import.meta.url), 'utf8'));
|
||||
expect(isNumberedOptionListVisible(captured.frame.text)).toBe(true);
|
||||
expect(isPermissionDialogVisible(captured.frame.text)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isNumberedOptionListVisible', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** Private evaluator inputs. Never copy this module or its JSON output into producer snapshots. */
|
||||
import { createHash } from 'node:crypto';
|
||||
import { CORPUS_VERSION, type EvalFamily, type EvalStack } from '../fixtures/cso-eval/materialize';
|
||||
import { CORPUS_VERSION, type EvalFamily } from '../fixtures/cso-eval/materialize';
|
||||
|
||||
export interface OracleRequest { method: 'GET' | 'POST'; path: string; body?: string; headers?: Record<string, string> }
|
||||
export interface OracleResponse { status: number; body: string; headers?: Record<string, string> }
|
||||
@@ -100,9 +100,3 @@ export function judgeRepair(family: EvalFamily, evidence: PrivateEvidence): { re
|
||||
&& patched.existingTestsPassed && evidence.immutableVerifier && evidence.independentRootCauseReview && evidence.featurePreserved && !evidence.boundaryMocks;
|
||||
return { reproduced, correctRepair, evidenceHash: createHash('sha256').update(JSON.stringify({ version: oracle.version, family, evidence })).digest('hex') };
|
||||
}
|
||||
export function runtimeStart(stack: EvalStack): { executable: string; args: string[]; port: 8000; environment: Record<string, string> } {
|
||||
return stack === 'node' ? { executable: '/usr/local/bin/node', args: ['app.mjs'], port: 8000, environment: {} }
|
||||
: stack === 'bun' ? { executable: '/usr/local/bin/bun', args: ['--no-install', 'app.ts'], port: 8000, environment: {} }
|
||||
: stack === 'python' ? { executable: '/usr/local/bin/python', args: ['-I', 'app.py'], port: 8000, environment: {} }
|
||||
: { executable: '/usr/local/bin/ruby', args: ['bin/rails', 'server', '-e', 'test', '-b', '127.0.0.1', '-p', '8000'], port: 8000, environment: { RAILS_ENV: 'test', RACK_ENV: 'test' } };
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import type { AskUserQuestionFingerprint } from './claude-pty-runner';
|
||||
import { isDesignCountFirstReview } from './design-count-review';
|
||||
|
||||
export function isDesignUIScopeReview(fp: AskUserQuestionFingerprint): boolean {
|
||||
const call = fp.nativeCall;
|
||||
if (!call?.answered || call.failed || !Array.isArray(call.unansweredQuestionIndices) ||
|
||||
call.unansweredQuestionIndices.length || !call.questions.length ||
|
||||
fp.signature !== `${call.sessionId}:${call.toolUseId}`) return false;
|
||||
if (call.questions.some(q => q.multiSelect || q.options.length < 2 ||
|
||||
new Set(q.options.map(option => option.label)).size !== q.options.length ||
|
||||
!q.options.some(option => option.label === call.answers?.[q.question]))) return false;
|
||||
if (isDesignCountFirstReview(fp)) return true;
|
||||
const workflow = /\b(?:review(?:s|ers?)?|scope|setup|learnings|routing|mockups?|permissions?|codex|claude|outside)\b/i;
|
||||
const ui = /\b(?:dashboard|hierarchy|panels?|layout|headers?|buttons?|navigation|notifications?|activity|actions?|spacing|colou?rs?|fonts?|typography|loading|errors?|focus|contrast|keyboard|mobile|responsive|toasts?|modals?|empty)\b/i;
|
||||
return call.questions.some(q => {
|
||||
if (/^(?:scope|focus|learnings|routing|next steps?|outside(?: design)? voices)$/i.test(q.header.trim())) return false;
|
||||
const issue = /^(?:D\d+\s*[—–:-]\s*)?Issue ([1-9]\d*)\s*[:—–-]\s*([^\n]+\?)$/i.exec(q.question.split('\n')[0]!.trim());
|
||||
if (!issue || workflow.test(issue[2]!) || !ui.test(issue[2]!) ||
|
||||
!q.options.some(option => ui.test(`${option.label} ${option.description ?? ''}`))) return false;
|
||||
const context = /^Project\/branch\/task:([^\n]*)/mi.exec(q.question)?.[1] ?? '';
|
||||
const namedPlans = context.match(/\b[\w.-]+\.md\b/gi) ?? [];
|
||||
if ((namedPlans.length && !namedPlans.some(plan => /^PLAN\.md$/i.test(plan))) ||
|
||||
/\b(?:before|prior to)\s+Pass\b/i.test(context)) return false;
|
||||
const choice = new RegExp(`^${issue[1]}[A-Z](?:[).:—–-]\\s*|\\s+)\\S`);
|
||||
return q.options.every(option => choice.test(option.label) && !workflow.test(option.label));
|
||||
});
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import '../../lib/conductor-env-shim';
|
||||
import { describe, test, beforeAll, afterAll, expect } from 'bun:test';
|
||||
import { describe, test, afterAll, expect } from 'bun:test';
|
||||
import type { SkillTestResult } from './session-runner';
|
||||
import { EvalCollector, judgePassed } from './eval-store';
|
||||
import type { EvalTestEntry } from './eval-store';
|
||||
@@ -14,7 +14,6 @@ import { judgeRecommendation, type RecommendationScore } from './llm-judge';
|
||||
import { selectTests, detectBaseBranch, getChangedFiles, E2E_TOUCHFILES, E2E_TIERS, GLOBAL_TOUCHFILES } from './touchfiles';
|
||||
import { WorktreeManager } from '../../lib/worktree';
|
||||
import type { HarvestResult } from '../../lib/worktree';
|
||||
import { spawnSync } from 'child_process';
|
||||
import { preflightAnthropicApi } from './anthropic-preflight';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
@@ -400,24 +399,6 @@ export function harvestAndCleanup(testName: string): HarvestResult | null {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience: describe block with automatic worktree isolation + harvest.
|
||||
* Any test file can use this to get real repo context instead of a tmpdir.
|
||||
* Note: tests with planted-bug fixtures should NOT use this — they need their fixture repos.
|
||||
*/
|
||||
export function describeWithWorktree(
|
||||
name: string,
|
||||
testNames: string[],
|
||||
fn: (getWorktreePath: () => string) => void,
|
||||
) {
|
||||
describeIfSelected(name, testNames, () => {
|
||||
let worktreePath: string;
|
||||
beforeAll(() => { worktreePath = createTestWorktree(name); });
|
||||
afterAll(() => { harvestAndCleanup(name); });
|
||||
fn(() => worktreePath);
|
||||
});
|
||||
}
|
||||
|
||||
export { judgePassed } from './eval-store';
|
||||
export { EvalCollector } from './eval-store';
|
||||
export type { EvalTestEntry } from './eval-store';
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,60 +0,0 @@
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { seedPlanReviewProject } from './ceo-finding-fixture';
|
||||
|
||||
/** Supply the existing behavior that the plan says it will rewrite. The new
|
||||
* AuthBroker/SessionMint/cache implementation remains proposed and absent. */
|
||||
export function seedEngFindingProject(projectDir: string, plan: string): string {
|
||||
const input = plan + '\n\n## Existing behavior and boundaries\n' + [
|
||||
'Read `src/legacy-auth.ts`: this is the existing flow, not the proposed refactor.',
|
||||
'The HTTP admission middleware, IDP policy client, and session service keep their',
|
||||
'documented contracts. The five policy names identify the existing independent',
|
||||
'read-only verdict calls made by this function.',
|
||||
'For this synthetic fixture, the unchanged platform admission limit and IDP quota',
|
||||
'reserve concurrency and rate capacity for five policy calls per admitted request,',
|
||||
'including denied requests. This refactor does not require a capacity rollout.',
|
||||
'Preserve the return value, all three public failure codes, and which outcome',
|
||||
'wins: evaluate failures in the existing POLICIES order, not response-arrival order.',
|
||||
'No session lifecycle, HTTP protocol, schema, or deployment change is proposed.',
|
||||
'Those adapter implementations belong to the existing platform outside this',
|
||||
'fixture; their interface here defines the boundary of this refactor.',
|
||||
'The existing deployment system restores the prior build artifact for rollback;',
|
||||
'there is no per-function rollout flag in this module or deployment change here.',
|
||||
'This is a Bun TypeScript package. Use its existing `bun test` command for new',
|
||||
'tests; choosing a different runner or adding a toolchain is outside the refactor.',
|
||||
'',
|
||||
'## Proposed class roles',
|
||||
'The four classes are a proposed organization of operations and values already',
|
||||
'present in `legacyAuthFlow`, not additional product features or implemented code:',
|
||||
'- AuthBroker coordinates the policy checks and the existing session call.',
|
||||
'- SessionMint delegates to `platform.issueSession(identity)`; the platform still',
|
||||
' owns session IDs, expiry, revocation, and storage.',
|
||||
'- TokenStore is a request-local holder for the existing Identity and returned',
|
||||
' Session values; it adds no persistence, token issuance, or renewal.',
|
||||
'- RequestPolicy wraps one existing POLICIES entry and its `checkPolicy` call;',
|
||||
' it adds no policy, configuration, or precedence rule.',
|
||||
'This class arrangement is still a proposal to review, not an accepted design.',
|
||||
'The separately proposed shared mutable AuthCache remains in the plan.',
|
||||
'',
|
||||
'## Undecided internal failure interface',
|
||||
// Newly authored synthetic input; this is not evidence about prior reviews.
|
||||
'The proposed validateAndDispatch catches would swallow failures. The refactor',
|
||||
'has not chosen how those failures reach the existing public auth boundary:',
|
||||
'typed exception propagation or a discriminated result handled exhaustively',
|
||||
'before that boundary.',
|
||||
'Both must preserve the public failure codes, causes and POLICIES-order precedence.',
|
||||
'This internal choice fits the accepted single-function/module or class organization;',
|
||||
'it does not require a new helper or adapter. It is independent of sequential or',
|
||||
'parallel IDP dispatch and the algorithm used to settle the policy outcomes.',
|
||||
].join('\n') + '\n';
|
||||
seedPlanReviewProject(projectDir, input, 'plan-eng-review');
|
||||
fs.mkdirSync(path.join(projectDir, 'src'));
|
||||
fs.copyFileSync(path.resolve(import.meta.dir, '../fixtures/eng-existing-auth/legacy-auth.ts'), path.join(projectDir, 'src/legacy-auth.ts'));
|
||||
fs.copyFileSync(path.resolve(import.meta.dir, '../fixtures/eng-existing-auth/package.json'), path.join(projectDir, 'package.json'));
|
||||
const git = (...args: string[]) => execFileSync('git', args, { cwd: projectDir, stdio: 'pipe', timeout: 10_000 });
|
||||
git('add', 'src/legacy-auth.ts', 'package.json');
|
||||
git('-c', 'user.name=Finding fixture', '-c', 'user.email=fixture@gstack.test', 'commit', '-m', 'Supply existing auth behavior');
|
||||
git('update-ref', 'refs/remotes/origin/main', 'HEAD');
|
||||
return input;
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/** A recorded legacy oracle can survive removal of the implementation it sampled. */
|
||||
export function hasRetainedLegacyCorpus(
|
||||
current: ReadonlyArray<{ title: string; body: string[] }>, snapshot: string,
|
||||
): boolean {
|
||||
const flat = (s: string) => s.replace(/\s+/g, ' ').trim();
|
||||
const quoted = (s: string) => s.replace(/"[^"\n]*"|“[^”\n]*”|(?<![\w])'[^'\n]*'(?![\w])|‘[^’\n]*’/g, '');
|
||||
const source = (s: string) => /(?:^|\n)\s*(?:source(?: excerpt| material)?|quoted(?: source)?|historical(?: example| assessment)?|if approved|once approved|when approved|pending approval|assuming approval|provided approval)\s*[,.:—-]/i.test(quoted(s));
|
||||
const status = '(?:withdrawn|rejected|declined|cancelled|canceled|superseded|deferred|optional|proposed|not current|no longer current|not required|no longer required|conditional on approval)';
|
||||
const owner = '(?:(?:this|the) (?:(?:legacy|recorded|baseline) )?(?:(?:regression|parity) )?(?:suite|test|requirement|verification|oracle|corpus))';
|
||||
const scalar = (s: string, id: string) => quoted(s.replace(new RegExp(
|
||||
`((?:^|[.!?;]\\s+|\\n)\\s*(?:Correction:\\s*)?(?:${id}(?: verification)?|${owner}) (?:is|are|was|were|has been|have been) )["“'‘](${status})["”'’]`, 'gim'), '$1$2'));
|
||||
const inactive = (s: string, id: string) => source(s) || new RegExp(
|
||||
`\\b(?:${id}(?: verification)?|${owner}) (?:is|are|was|were|has been|have been) ${status}\\b|\\b(?:only if|unless|pending) (?:user )?approv`, 'i').test(scalar(s, id));
|
||||
const negated = (s: string) => /\b(?:do not|don't|never|skip|omit|defer|cancel) (?:add|write|implement|record|capture|pin|run|assert|retain|keep)\b/i.test(quoted(s));
|
||||
const owned = (s: string) => !source(s) && !negated(s) && snapshot.includes(flat(s));
|
||||
|
||||
for (const declaration of current) {
|
||||
if (!/^CRITICAL regression test \([^)]*\bmandatory\b[^)]*\)$/i.test(declaration.title)
|
||||
|| /\b(?:not|never|no longer) mandatory\b/i.test(declaration.title)) continue;
|
||||
const body = declaration.body.join('\n').trim();
|
||||
const add = /^Add ([A-Za-z][\w/.-]*\.test\.[jt]s):$/m.exec(body);
|
||||
if (!add || !owned(body) || inactive(body, 'T[1-9]\\d*')) continue;
|
||||
const bullets = body.split(/\n(?=- )/).slice(1).map(flat);
|
||||
const capture = bullets.findIndex(s => /^- (?:Record|Capture|Pin) a corpus of .+ with the legacy decision for each\.$/i.test(s));
|
||||
const parity = bullets.findIndex(s => /^- Run the (?:same )?corpus through the new [A-Za-z][\w]* path and assert identical allow\/deny and reason code for every entry\.$/i.test(s));
|
||||
if (capture < 0 || parity <= capture || !bullets.some(s => /^- This test is also the shadow-mode oracle; it stays after legacy deletion, re-pointed at the recorded decisions\.$/i.test(s))) continue;
|
||||
// Retention makes the old behavior the oracle. Building independent new
|
||||
// modules before recording it does not itself rewrite that old behavior.
|
||||
const retention = current.filter(s => s.title === 'What already exists').flatMap(s => s.body.join('\n').split(/\n(?=- )/))
|
||||
.find(s => /^- legacyAuthFlow\(\): retained behind the per-tenant flag as the shadow oracle and regression baseline until deletion\.$/i.test(flat(s)) && owned(s) && !inactive(s, 'T[1-9]\\d*'));
|
||||
if (!retention) continue;
|
||||
for (const section of current.filter(s => s.title === 'Implementation Tasks')) {
|
||||
const taskBody = section.body.join('\n').trim();
|
||||
const tasks = taskBody.split(/\n(?=- )/);
|
||||
const rows = tasks.map(body => ({ body, match: /^- (?:\[[ xX]\] )?(T[1-9]\d*)(?: \([^\n)]*\))? [—–-] (.+)(?:\n|$)/.exec(body) })).filter(row => row.match);
|
||||
if (rows.length !== new Set(rows.map(row => row.match![1])).size) continue;
|
||||
for (const row of rows) {
|
||||
const id = row.match![1]!, title = row.match![2]!;
|
||||
const action = row.body.replace(/^ - Surfaced by:.*$/gm, '');
|
||||
const preceding = taskBody.slice(0, taskBody.indexOf(row.body)).trim().split('\n').at(-1) ?? '';
|
||||
const files = [...row.body.matchAll(/^ - Files: ([^\n]+)$/gm)];
|
||||
const verifies = [...row.body.matchAll(/^ - Verify: ([^\n]+)$/gm)];
|
||||
if (!/^[A-Za-z][\w -]* [—–-] CRITICAL: recorded-corpus parity test legacy vs [A-Za-z][\w]*$/i.test(title)
|
||||
|| files.length !== 1 || verifies.length !== 1 || !snapshot.includes(flat(row.body)) || source(action) || negated(action) || source(preceding)
|
||||
|| inactive(action, id) || !files[0]![1]!.split(',').map(s => s.trim()).includes(add[1]!)) continue;
|
||||
if (!/^100% decision \+ reason-code parity across the corpus$/i.test(verifies[0]![1]!)) continue;
|
||||
const cancelled = current.some(s => {
|
||||
if (/\b(?:history|historical|source|quoted|example)\b/i.test(s.title)) return false;
|
||||
const raw = s.body.join('\n'), value = scalar(raw, id);
|
||||
const named = /^(.*?)\b(?:regression|characterization|parity)\s+(?:suite|test)/i.exec(s.title)?.[1]?.trim();
|
||||
const foreign = Boolean(named && !/^(?:(?:critical|recorded|required|current|final|updated)\s*)*(?:legacy(?:AuthFlow\(\))?)?[\s:—-]*$/i.test(named));
|
||||
if (new RegExp(`^\\s*\\|\\s*${id}\\s*\\|\\s*["“'‘]?${status}["”'’]?\\s*\\|`, 'im').test(raw)) return true;
|
||||
return value.split(/\n|[.!?;]\s+/).some(line => {
|
||||
if (source(line) || /^\s*(?:if|unless|assuming|provided)\b/i.test(line)) return false;
|
||||
if (foreign && !new RegExp(`\\b${id}\\b|legacyAuthFlow|\\blegacy (?:regression|parity) (?:suite|test|corpus|baseline)\\b`, 'i').test(line)) return false;
|
||||
return inactive(line, id) || new RegExp(`^\\s*(?:Correction:\\s*)?legacyAuthFlow\\(\\) (?:is|was|has been|will be) (?:modified|changed|rewritten|refactored|removed|deleted) before ${id}\\b`, 'i').test(line)
|
||||
|| /^\s*(?:Correction:\s*)?legacyAuthFlow\(\) (?:is|was|has been|will be) (?:modified|changed|rewritten|refactored|removed|deleted) before (?:the )?(?:(?:legacy|recorded|regression) )?(?:corpus|baseline)(?: is)? (?:recorded|captured|pinned)\b/i.test(line)
|
||||
|| /^\s*(?:the|this) (?:(?:legacy|regression) )?(?:corpus|baseline) is (?:recorded|captured|pinned) (?:only )?after legacyAuthFlow\(\) is (?:modified|changed|rewritten|refactored|removed|deleted)\b/i.test(line)
|
||||
|| /\b(?:the|this) legacy (?:regression baseline|shadow oracle) (?:is|was|has been) (?:changed|rewritten|removed|deleted|replaced)\b/i.test(line);
|
||||
});
|
||||
});
|
||||
if (!cancelled) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,4 @@
|
||||
import { execFileSync, spawnSync } from 'node:child_process';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import type { NativeQuestion } from './plan-skill-questions';
|
||||
@@ -54,16 +54,6 @@ export const DESIGN_BOARD_ACTOR_PROTOCOL = [
|
||||
'decisions, visual verification, and the remaining review still apply.',
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
/** Declare the fixture user's interface before launch, without changing its plan. */
|
||||
export function seedDesignBoardActorProtocol(cwd: string): void {
|
||||
fs.appendFileSync(path.join(cwd, 'CLAUDE.md'), `\n${DESIGN_BOARD_ACTOR_PROTOCOL}`);
|
||||
const git = (args: string[]) => execFileSync('git', args, { cwd, stdio: 'pipe', timeout: 10_000 });
|
||||
git(['add', 'CLAUDE.md']);
|
||||
git(['-c', 'user.name=Finding fixture', '-c', 'user.email=fixture@gstack.test', 'commit', '-m', 'Declare Design board fixture actor interface']);
|
||||
git(['update-ref', 'refs/remotes/origin/main', 'HEAD']);
|
||||
}
|
||||
|
||||
// The native picker is synchronous. Keep identity checks and the real board
|
||||
// submission together in a bounded child; the counting driver still owns the
|
||||
// subsequent terminal input and requires its native acknowledgment.
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
/** Ground truth for the existing plan fixtures, independent of review phase. */
|
||||
import type { NativeQuestion } from './plan-skill-questions';
|
||||
|
||||
export const CEO_FINDINGS = [
|
||||
{ id: 'dispatcher', description: 'Decide whether PaymentService should reuse the existing WebhookDispatcher instead of bypassing it for namespace separation.' },
|
||||
{ id: 'sql', description: 'Decide a safe parameterized user lookup instead of interpolating untrusted request.params.userId into raw SQL.' },
|
||||
{ id: 'email', description: 'Decide the failure/recovery contract for notification email after the payment transaction commits; the proposed inline email leg has no catch, outbox, or retry.' },
|
||||
{ id: 'tests', description: 'Decide regression coverage for the new PaymentService path; existing platform tests do not cover this new handler and no new tests are planned.' },
|
||||
{ id: 'queries', description: 'Decide how to eliminate or justify the per-order lookup loop instead of batching the webhook order reads.' },
|
||||
];
|
||||
|
||||
export const CEO_PAIRED_FINDINGS = [
|
||||
{ id: 'receipt-test', description: 'Independently decide happy-path processPayment test coverage asserting the correct receipt after a successful Stripe charge.' },
|
||||
{ id: 'failure-test', description: 'Independently decide Stripe 502/timeout coverage asserting one retry with backoff and then clean failure.' },
|
||||
@@ -21,15 +12,6 @@ export const CEO_SCOPE_CANDIDATES = [
|
||||
{ id: 'E4', description: 'The whole Telegram bot API integration: include, defer, or cut it.' },
|
||||
{ id: 'E5', description: 'The whole Mattermost REST plugin integration: include, defer, or cut it.' },
|
||||
];
|
||||
|
||||
export const DESIGN_FINDINGS = [
|
||||
{ id: 'primary-action', description: 'Decide how Save becomes the primary action instead of sharing equal visual weight with Reset, Cancel, and Export.' },
|
||||
{ id: 'spacing', description: 'Decide a consistent vertical section rhythm instead of mixed 16px, 24px, and 32px gaps.' },
|
||||
{ id: 'contrast', description: 'Decide accessible error-message contrast instead of the proposed approximately 3:1 red-on-pink treatment.' },
|
||||
{ id: 'typography', description: 'Decide a coherent form-label type hierarchy instead of inconsistent 14px, 16px, and 18px labels.' },
|
||||
{ id: 'save-feedback', description: 'Decide progress feedback for the 2–5 second Save action instead of leaving the interface apparently frozen.' },
|
||||
];
|
||||
|
||||
export const DEVEX_FINDINGS = [
|
||||
{ id: 'persona', description: 'Decide a specific target developer persona instead of shipping for everyone.' },
|
||||
{ id: 'first-run-benchmark', description: 'Decide measurement/benchmarking of time to hello world instead of leaving first-run duration unknown.' },
|
||||
@@ -37,15 +19,6 @@ export const DEVEX_FINDINGS = [
|
||||
{ id: 'aha', description: 'Decide a concrete interactive demo or aha moment in the getting-started flow instead of documentation alone.' },
|
||||
{ id: 'peer-comparison', description: 'Produce grounded comparative analysis of peer SDK developer experiences and its implications for this plan instead of ignoring existing solutions.' },
|
||||
];
|
||||
|
||||
export const ENG_FINDINGS = [
|
||||
{ id: 'shared-cache', description: 'Decide ownership/isolation of AuthCache instead of two services mutating one module-level global cache.' },
|
||||
{ id: 'swallowed-errors', description: 'Decide explicit handling of the swallowed error classes in validateAndDispatch rather than keeping three nested catch blocks that hide failures.' },
|
||||
{ id: 'legacy-regression', description: 'Decide regression tests for the rewritten legacyAuthFlow behavior.' },
|
||||
{ id: 'parallel-idp', description: 'Decide parallelizing the five independent IDP validation calls instead of running them sequentially.' },
|
||||
{ id: 'complexity', description: 'Decide whether to reduce or justify the 12-file/four-new-class scope and complexity.' },
|
||||
];
|
||||
|
||||
export const ENG_BATCHING_FINDINGS = [
|
||||
{ id: 'retry-library', description: 'Decide reuse of existing job-library retry hooks instead of a custom inline backoff scheduler per worker.' },
|
||||
{ id: 'retry-duplication', description: 'Decide consolidation of the copied retry envelope across five workers.' },
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import { readOwnedClaudeTranscript } from './owned-claude-transcript';
|
||||
|
||||
/** A report preview is not completion. Require the latest owned conversation
|
||||
* turn to finish without tools, then corroborate its own completion text.
|
||||
*/
|
||||
export function readPlanSkillCompletion(configDir: string | null, sessionId: string, visible: string): string | null {
|
||||
const transcript = readOwnedClaudeTranscript(configDir, sessionId);
|
||||
if (transcript.pendingBytes) return null;
|
||||
const queued = new Map<string | undefined, number>();
|
||||
let queuedCount = 0;
|
||||
let ambiguousDequeues = false;
|
||||
let latest: { id: string | null; text: string[]; stop: unknown; tools: boolean } | null = null;
|
||||
for (const row of transcript.rows) {
|
||||
if (row.type === 'queue-operation') {
|
||||
latest = null;
|
||||
const hasContent = Object.hasOwn(row, 'content');
|
||||
if (!['enqueue', 'dequeue', 'remove', 'popOne', 'popAll'].includes(row.operation)
|
||||
|| hasContent && (typeof row.content !== 'string' || row.operation === 'dequeue')) {
|
||||
throw new Error('Unsupported queue operation in owned Claude transcript');
|
||||
}
|
||||
const count = queued.get(row.content) ?? 0;
|
||||
if (row.operation === 'enqueue') {
|
||||
if (ambiguousDequeues) throw new Error('Unsupported mixed queue history after anonymous dequeue in owned Claude transcript');
|
||||
queued.set(row.content, count + 1);
|
||||
queuedCount++;
|
||||
} else {
|
||||
// Claude 2.1.263 emits one removal per actual item, even for popAll.
|
||||
// Dequeue omits identity: payload counts remain upper bounds until
|
||||
// the logged queue drains. New enqueues during unresolved ambiguity
|
||||
// fail closed; this reader cannot reconstruct every producer stream.
|
||||
if (!queuedCount) throw new Error('Queue removal lacks its enqueue in owned Claude transcript');
|
||||
if (row.operation === 'dequeue') ambiguousDequeues = queued.size > 1;
|
||||
else {
|
||||
if (!count) throw new Error('Queue removal does not match a queued payload in owned Claude transcript');
|
||||
if (count > 1) queued.set(row.content, count - 1);
|
||||
else queued.delete(row.content);
|
||||
}
|
||||
if (--queuedCount === 0) { queued.clear(); ambiguousDequeues = false; }
|
||||
else if (queued.size === 1) {
|
||||
// The remaining payload is now unique, even if it had duplicates.
|
||||
queued.set(queued.keys().next().value, queuedCount);
|
||||
ambiguousDequeues = false;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (row.type === 'user' || (row.type === 'attachment' && typeof row.attachment?.prompt === 'string')) {
|
||||
latest = null;
|
||||
continue;
|
||||
}
|
||||
if (row.type !== 'assistant' || row.message?.role !== 'assistant') continue;
|
||||
const message = row.message;
|
||||
const id = typeof message.id === 'string' ? message.id : null;
|
||||
if (!latest || !id || latest.id !== id) latest = { id, text: [], stop: null, tools: false };
|
||||
latest.stop = message.stop_reason;
|
||||
if (!Array.isArray(message.content)) continue;
|
||||
for (const block of message.content) {
|
||||
if (block?.type === 'text' && typeof block.text === 'string') latest.text.push(block.text);
|
||||
if (block?.type === 'tool_use') latest.tools = true;
|
||||
}
|
||||
}
|
||||
if (queuedCount || !latest || latest.stop !== 'end_turn' || latest.tools) return null;
|
||||
let fence: string | null = null;
|
||||
const compact = (value: string) => value.replace(/[\s*#]/g, '').toLowerCase();
|
||||
for (const line of latest.text.join('\n').split(/\r?\n/)) {
|
||||
const delimiter = line.match(/^ {0,3}(`{3,}|~{3,})/)?.[1];
|
||||
if (delimiter) {
|
||||
if (!fence) fence = delimiter;
|
||||
else if (delimiter[0] === fence[0] && delimiter.length >= fence.length) fence = null;
|
||||
continue;
|
||||
}
|
||||
if (fence || /^(?: {4}| {0,3}\t)|^\s*>/.test(line)) continue;
|
||||
const text = line.replace(/^ {0,3}(?:#{1,6}\s+)?/, '').replace(/\*\*/g, '').trim();
|
||||
const marker = /^(?:GSTACK REVIEW REPORT|Completion Summary)$/i.test(text)
|
||||
|| /^VERDICT:\s*\S/.test(text)
|
||||
|| /^Status:\s*(?:clean|issues_open)\b/i.test(text)
|
||||
|| /^(?:STATUS:\s*)?DONE(?:_WITH_CONCERNS)?(?:\s|[—:.-]|$)/.test(text);
|
||||
if (marker && compact(visible).includes(compact(text))) return text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
import type { Terminal } from 'xterm';
|
||||
|
||||
let TerminalClass: typeof Terminal | undefined;
|
||||
|
||||
function terminalClass(): typeof Terminal {
|
||||
if (TerminalClass) return TerminalClass;
|
||||
// xterm 5.3 detects Node by navigator's absence. Bun provides navigator
|
||||
// without a DOM, so select the package's Node path during synchronous load.
|
||||
// No await or fake document/window: restore the exact descriptors even if
|
||||
// loading fails. Its public buffer API works without Terminal.open().
|
||||
const navigator = Object.getOwnPropertyDescriptor(globalThis, 'navigator');
|
||||
const self = Object.getOwnPropertyDescriptor(globalThis, 'self');
|
||||
try {
|
||||
Object.defineProperty(globalThis, 'navigator', { configurable: true, value: undefined });
|
||||
if (typeof globalThis.self === 'undefined') Object.defineProperty(globalThis, 'self', { configurable: true, value: globalThis });
|
||||
TerminalClass = (require('xterm') as { Terminal: typeof Terminal }).Terminal;
|
||||
return TerminalClass;
|
||||
} finally {
|
||||
if (navigator) Object.defineProperty(globalThis, 'navigator', navigator);
|
||||
else delete (globalThis as any).navigator;
|
||||
if (self) Object.defineProperty(globalThis, 'self', self);
|
||||
else delete (globalThis as any).self;
|
||||
}
|
||||
}
|
||||
|
||||
export interface PtyScreenSnapshot {
|
||||
/** Absolute bytes fed through this snapshot's write barrier, not raw JS string indices. */
|
||||
inputOffset: number;
|
||||
cols: number;
|
||||
rows: number;
|
||||
bufferType: 'normal' | 'alternate';
|
||||
lines: Array<{ text: string; wrapped: boolean }>;
|
||||
text: string;
|
||||
/** Contiguous dim/inverse text cells, captured at the same write barrier. */
|
||||
styledText: Array<{ row: number; start: number; text: string; dim: boolean; inverse: boolean }>;
|
||||
}
|
||||
|
||||
/** Test-only current-screen projection. Never writes input to a PTY. */
|
||||
export class PtyCurrentScreen {
|
||||
private terminal: Terminal | null = null;
|
||||
private offset = 0;
|
||||
private closed: Error | null = null;
|
||||
private pending = new Set<(error: Error) => void>();
|
||||
private readonly cols: number;
|
||||
private readonly rows: number;
|
||||
private readonly flushTimeoutMs: number;
|
||||
|
||||
constructor(options: { cols?: number; rows?: number; flushTimeoutMs?: number } = {}) {
|
||||
this.cols = options.cols ?? 120;
|
||||
this.rows = options.rows ?? 40;
|
||||
this.flushTimeoutMs = options.flushTimeoutMs ?? 1000;
|
||||
if (![this.cols, this.rows].every(value => Number.isSafeInteger(value) && value > 0)) throw new Error('Screen dimensions must be positive integers');
|
||||
if (!Number.isFinite(this.flushTimeoutMs) || this.flushTimeoutMs <= 0) throw new Error('Screen flush timeout must be finite and positive');
|
||||
}
|
||||
|
||||
get inputOffset(): number { return this.offset; }
|
||||
|
||||
private getTerminal(): Terminal {
|
||||
if (this.closed) throw this.closed;
|
||||
if (!this.terminal) this.terminal = new (terminalClass())({ cols: this.cols, rows: this.rows, scrollback: 0 });
|
||||
return this.terminal;
|
||||
}
|
||||
|
||||
/** Byte chunks preserve decoder state across split UTF-8 characters.
|
||||
* Strings are encoded once; offsets always count UTF-8 bytes. */
|
||||
feed(data: string | Uint8Array): number {
|
||||
const terminal = this.getTerminal();
|
||||
const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : new Uint8Array(data);
|
||||
const offset = this.offset + bytes.byteLength;
|
||||
if (!Number.isSafeInteger(offset)) throw new Error('Screen input offset exceeded the safe integer range');
|
||||
try { terminal.write(bytes); }
|
||||
catch (cause) {
|
||||
this.close(new Error('Screen input could not be decoded'));
|
||||
throw cause;
|
||||
}
|
||||
this.offset = offset;
|
||||
return offset;
|
||||
}
|
||||
|
||||
/** Capture inside the callback, before later queued writes can change the
|
||||
* screen. The returned offset lets callers compare with their input epoch;
|
||||
* it does not itself establish native question ownership or acknowledgement. */
|
||||
snapshot(): Promise<PtyScreenSnapshot> {
|
||||
const terminal = this.getTerminal();
|
||||
const inputOffset = this.offset;
|
||||
return new Promise((resolve, reject) => {
|
||||
let done = false;
|
||||
const finish = (error?: Error, value?: PtyScreenSnapshot) => {
|
||||
if (done) return;
|
||||
done = true;
|
||||
clearTimeout(timer);
|
||||
this.pending.delete(fail);
|
||||
if (error) reject(error);
|
||||
else resolve(value!);
|
||||
};
|
||||
const fail = (error: Error) => finish(error);
|
||||
const timer = setTimeout(() => this.close(new Error(`Screen flush did not complete within ${this.flushTimeoutMs}ms`)), this.flushTimeoutMs);
|
||||
this.pending.add(fail);
|
||||
try {
|
||||
terminal.write('', () => {
|
||||
if (done || this.closed) return;
|
||||
try {
|
||||
const buffer = terminal.buffer.active;
|
||||
const styledText: PtyScreenSnapshot['styledText'] = [];
|
||||
const lines = Array.from({ length: terminal.rows }, (_, row) => {
|
||||
const line = buffer.getLine(buffer.baseY + row);
|
||||
let start = 0, previous = '';
|
||||
for (let col = 0; col <= terminal.cols; col++) {
|
||||
const cell = col < terminal.cols ? line?.getCell(col) : undefined;
|
||||
const key = cell && (cell.getChars() || cell.getWidth() === 0)
|
||||
? `${Number(!!cell.isDim())}${Number(!!cell.isInverse())}` : '';
|
||||
if (key === previous) continue;
|
||||
if (previous && previous !== '00') styledText.push({ row, start,
|
||||
text: line!.translateToString(false, start, col), dim: previous[0] === '1', inverse: previous[1] === '1' });
|
||||
start = col; previous = key;
|
||||
}
|
||||
return { text: line?.translateToString(true) ?? '', wrapped: line?.isWrapped ?? false };
|
||||
});
|
||||
finish(undefined, { inputOffset, cols: terminal.cols, rows: terminal.rows,
|
||||
bufferType: buffer.type, lines, text: lines.map(line => line.text).join('\n'), styledText });
|
||||
} catch (cause) {
|
||||
this.close(cause instanceof Error ? cause : new Error(String(cause)));
|
||||
}
|
||||
});
|
||||
} catch (cause) {
|
||||
this.close(cause instanceof Error ? cause : new Error(String(cause)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** The caller must flush before coordinating this with the actual PTY.
|
||||
* Resizing changes geometry only; it contributes no output or input epoch. */
|
||||
resize(cols: number, rows: number): void {
|
||||
if (![cols, rows].every(value => Number.isSafeInteger(value) && value > 0)) throw new Error('Screen dimensions must be positive integers');
|
||||
if (this.pending.size) throw new Error('Cannot resize while a screen snapshot is pending');
|
||||
try { this.getTerminal().resize(cols, rows); }
|
||||
catch (cause) { this.close(cause instanceof Error ? cause : new Error(String(cause))); throw cause; }
|
||||
}
|
||||
|
||||
private close(error: Error): void {
|
||||
if (this.closed) return;
|
||||
this.closed = error;
|
||||
for (const fail of [...this.pending]) fail(error);
|
||||
this.terminal?.dispose();
|
||||
}
|
||||
|
||||
dispose(): void { this.close(new Error('Screen projection is disposed')); }
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* requiredReads enforcement (v2 plan T9, mitigation layer 5 — the only CI-failing
|
||||
* layer against silent section-skip).
|
||||
*
|
||||
* Given a /ship run's tool calls and the set of section files the run's SITUATION
|
||||
* required, assert the agent actually Read each one. The required set comes from
|
||||
* the TEST FIXTURE (which situation it set up), NOT from the manifest — the
|
||||
* manifest is passive (CM2). This keeps "when is a section required" in exactly
|
||||
* one machine-checkable place: the eval fixtures.
|
||||
*
|
||||
* Builds on extractSectionReads from transcript-section-logger so section-path
|
||||
* matching (the `/sections/<file>.md` segment, host-layout agnostic) lives in one
|
||||
* place.
|
||||
*/
|
||||
|
||||
import { extractSectionReads, type TranscriptResultLike } from './transcript-section-logger';
|
||||
|
||||
export interface RequiredReadsResult {
|
||||
required: string[];
|
||||
read: string[];
|
||||
missing: string[];
|
||||
ok: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param result the skill run (anything with toolCalls)
|
||||
* @param requiredFiles section basenames the situation required, e.g.
|
||||
* ['version-bump.md','changelog.md'] (or with a sections/
|
||||
* prefix — normalized to basename here)
|
||||
*/
|
||||
export function assertRequiredReads(
|
||||
result: TranscriptResultLike,
|
||||
requiredFiles: string[],
|
||||
): RequiredReadsResult {
|
||||
const read = extractSectionReads(result);
|
||||
const readSet = new Set(read);
|
||||
const required = requiredFiles.map(f => f.replace(/^.*\//, '')); // tolerate sections/<f>
|
||||
const missing = required.filter(f => !readSet.has(f));
|
||||
return { required, read, missing, ok: missing.length === 0 };
|
||||
}
|
||||
@@ -33,18 +33,6 @@ export function gitArgvIn(repoDir: string, args: string[], timeout = 5000) {
|
||||
return spawnSync('git', [...GIT_HERMETIC_ARGS, ...args], { cwd: repoDir, timeout });
|
||||
}
|
||||
|
||||
/** Create a scratch repo (mkdtemp) with an initial commit; caller cleans up. */
|
||||
export function makeScratchRepo(prefix: string, files: Record<string, string> = { 'src.txt': 'v1\n' }): string {
|
||||
const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
gitIn(repoDir, 'init -q -b main');
|
||||
for (const [name, content] of Object.entries(files)) {
|
||||
fs.writeFileSync(path.join(repoDir, name), content);
|
||||
}
|
||||
gitIn(repoDir, `add ${Object.keys(files).join(' ')}`);
|
||||
gitIn(repoDir, 'commit -q -m init');
|
||||
return repoDir;
|
||||
}
|
||||
|
||||
/** Recursively find files with a given suffix under a directory. */
|
||||
export function findFilesBySuffix(root: string, suffix: string): string[] {
|
||||
const found: string[] = [];
|
||||
|
||||
@@ -193,7 +193,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'test/fixtures/design-scope-declaration-ak.json',
|
||||
"test/eng-option-b-scope-al.test.ts",
|
||||
"test/fixtures/eng-option-b-scope-al.json",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/tasks-section.ts'
|
||||
'test/pty-workspace-trust.test.ts', '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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/tasks-section.ts'
|
||||
],
|
||||
'plan-eng-review-plan-mode': [
|
||||
'lib/claude-public-transcript.ts',
|
||||
@@ -217,7 +217,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
"test/fixtures/eng-option-b-scope-al.json",
|
||||
|
||||
"test/review-entry-and-design-clarity-au.test.ts", "scripts/resolvers/preamble/generate-preamble-bash.ts",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'test/helpers/plan-seed-submission.ts', 'test/plan-seed-submission.test.ts', 'test/fixtures/plan-seed-cli.ts', 'test/helpers/owned-claude-transcript.ts', 'lib/fs-atomic.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/testing.ts', 'test/helpers/plan-mode-evidence.ts', 'test/plan-mode-evidence.test.ts', 'lib/redact-engine.ts', 'lib/redact-patterns.ts', 'test/plan-review-cases.test.ts'
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'test/helpers/plan-seed-submission.ts', 'test/plan-seed-submission.test.ts', 'test/fixtures/plan-seed-cli.ts', 'test/helpers/owned-claude-transcript.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/testing.ts', 'test/helpers/plan-mode-evidence.ts', 'test/plan-mode-evidence.test.ts', 'lib/redact-engine.ts', 'lib/redact-patterns.ts', 'test/plan-review-cases.test.ts'
|
||||
],
|
||||
'plan-design-review-plan-mode': ['test/session-runner-stream-lifecycle.test.ts',
|
||||
'lib/claude-public-transcript.ts',
|
||||
@@ -244,7 +244,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
"test/design-scope-entry-aq.test.ts",
|
||||
|
||||
"test/review-entry-and-design-clarity-au.test.ts", "scripts/resolvers/preamble/generate-preamble-bash.ts",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'test/helpers/plan-seed-submission.ts', 'test/plan-seed-submission.test.ts', 'test/fixtures/plan-seed-cli.ts', 'test/helpers/owned-claude-transcript.ts', 'lib/fs-atomic.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/plan-mode-evidence.ts', 'test/plan-mode-evidence.test.ts', 'lib/redact-engine.ts', 'lib/redact-patterns.ts'
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'test/helpers/plan-seed-submission.ts', 'test/plan-seed-submission.test.ts', 'test/fixtures/plan-seed-cli.ts', 'test/helpers/owned-claude-transcript.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/plan-mode-evidence.ts', 'test/plan-mode-evidence.test.ts', 'lib/redact-engine.ts', 'lib/redact-patterns.ts'
|
||||
],
|
||||
'plan-devex-review-plan-mode': [
|
||||
'test/auto-decide-recommendation-scope.test.ts',
|
||||
@@ -260,7 +260,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'test/fixtures/design-scope-declaration-ak.json',
|
||||
"test/eng-option-b-scope-al.test.ts",
|
||||
"test/fixtures/eng-option-b-scope-al.json",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts'
|
||||
'test/pty-workspace-trust.test.ts', '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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts'
|
||||
],
|
||||
// Covers ceo (preamble misfire) + eng/design (scope-gate bypass must not
|
||||
// fire outside plan mode) + the named-target exception case. 4 PTY runs;
|
||||
@@ -289,7 +289,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
"test/design-scope-entry-aq.test.ts",
|
||||
|
||||
"test/review-entry-and-design-clarity-au.test.ts", "scripts/resolvers/preamble/generate-preamble-bash.ts",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'test/helpers/plan-seed-submission.ts', 'test/plan-seed-submission.test.ts', 'test/fixtures/plan-seed-cli.ts', 'test/helpers/owned-claude-transcript.ts', 'lib/fs-atomic.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/testing.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/review.ts', 'test/plan-review-cases.test.ts', 'scripts/resolvers/tasks-section.ts'
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'test/helpers/plan-seed-submission.ts', 'test/plan-seed-submission.test.ts', 'test/fixtures/plan-seed-cli.ts', 'test/helpers/owned-claude-transcript.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/testing.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/review.ts', 'test/plan-review-cases.test.ts', 'scripts/resolvers/tasks-section.ts'
|
||||
],
|
||||
|
||||
// v1.21+ AskUserQuestion-blocked regression tests — Conductor launches
|
||||
@@ -306,7 +306,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'test/auto-decide-target-identity.test.ts',
|
||||
'test/fixtures/auto-decide-target-361c.json',
|
||||
'test/native-auto-decide.test.ts', 'test/native-auto-decide-pty.test.ts', 'test/fixtures/native-auto-decide-ag.json', 'test/helpers/native-auto-decide.ts', 'test/auto-decide-current-declaration.test.ts', 'test/fixtures/auto-decide-current-declaration-6aef.json', 'test/auto-decide-explanatory-mode.test.ts', '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/plan-count-design-ui-recovery.test.ts', 'test/fixtures/design-ui-boxed-question.json', '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-office-hours-auto-mode.test.ts', 'test/plan-count-truncated-border.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/plan-count-truncated-question.test.ts', 'test/plan-count-clipped-elision.test.ts', '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/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts'
|
||||
'test/pty-workspace-trust.test.ts', '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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts'
|
||||
],
|
||||
'office-hours-phase4-fork': ['test/session-runner-stream-lifecycle.test.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', 'test/office-hours-phase4-caller.test.ts'],
|
||||
'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.ts'],
|
||||
@@ -328,7 +328,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'test/auto-decide-saved-ai.test.ts', 'test/fixtures/auto-decide-saved-ai.json', 'test/fixtures/auto-decide-retry-ai.json','bin/gstack-skill-start', 'bin/gstack-skill-end', 'bin/gstack-session-kind', 'scripts/resolvers/question-tuning.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-preamble-bash.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'plan-ceo-review/**', 'bin/gstack-question-preference', 'bin/gstack-config', 'bin/gstack-slug', 'hosts/claude/hooks/question-preference-hook.ts', 'hosts/claude/hooks/spawned-directive.ts', 'lib/is-conductor.ts', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-design-ui-recovery.test.ts', 'test/fixtures/design-ui-boxed-question.json', '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-auto-decide-preserved.test.ts', 'test/plan-count-truncated-border.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/plan-count-truncated-question.test.ts', 'test/plan-count-clipped-elision.test.ts', '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/native-auto-decide.ts', 'test/auto-decide-current-declaration.test.ts', 'test/fixtures/auto-decide-current-declaration-6aef.json', 'test/auto-decide-explanatory-mode.test.ts', 'test/fixtures/auto-decide-explanatory-mode-043a.json', 'test/fixtures/auto-decide-explanatory-mode-749df.json', 'test/auto-decide-structured.test.ts', 'test/fixtures/auto-decide-structured-77.json', 'test/helpers/auto-decision-state.ts', 'test/auto-decision-state.test.ts', 'test/fixtures/auto-decide-state-cab3.json', 'bin/gstack-question-log', 'test/native-auto-decide.test.ts', 'test/native-auto-decide-pty.test.ts', 'test/helpers/fake-plan-seed.ts', 'test/helpers/plan-seed-submission.ts', 'test/plan-seed-submission.test.ts', 'test/fixtures/plan-seed-cli.ts', 'test/fixtures/native-auto-decide-ag.json', 'test/eng-seeded-completion-ai.test.ts', 'test/fixtures/eng-seeded-completion-ai.json', 'test/helpers/plan-count-pending-exit.ts', 'test/plan-count-pending-exit.test.ts', 'test/helpers/pty-screen.ts', 'test/pty-screen.test.ts', 'test/pty-screen-session.test.ts', 'test/fixtures/pty-screen/**',
|
||||
"test/ceo-mode-preference-al.test.ts",
|
||||
'test/helpers/plan-count-fixture.ts', 'test/plan-count-fixture.test.ts',
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/auto-decide-fixture.test.ts', 'test/fixtures/auto-decide-mode-selector-749df.json', 'lib/redact-engine.ts', 'lib/redact-patterns.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/helpers/owned-claude-transcript.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.ts', 'scripts/resolvers/tasks-section.ts'
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/auto-decide-fixture.test.ts', 'test/fixtures/auto-decide-mode-selector-749df.json', 'lib/redact-engine.ts', 'lib/redact-patterns.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/helpers/owned-claude-transcript.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/tasks-section.ts'
|
||||
],
|
||||
|
||||
// Conductor → prose decision brief (Conductor signal makes prose the default;
|
||||
@@ -349,7 +349,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'test/conductor-prose-observation-ao.test.ts', 'test/fixtures/conductor-prose-ao.json',
|
||||
'test/auto-decide-saved-ai.test.ts', 'test/fixtures/auto-decide-saved-ai.json', 'test/fixtures/auto-decide-retry-ai.json','bin/gstack-skill-start', 'bin/gstack-skill-end', 'bin/gstack-session-kind', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-preamble-bash.ts', 'scripts/resolvers/preamble.ts', 'plan-eng-review/**', 'hosts/claude/hooks/question-preference-hook.ts', 'hosts/claude/hooks/spawned-directive.ts', 'lib/is-conductor.ts', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-design-ui-recovery.test.ts', 'test/fixtures/design-ui-boxed-question.json', '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-conductor-prose.test.ts', 'test/plan-count-truncated-border.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/plan-count-truncated-question.test.ts', 'test/plan-count-clipped-elision.test.ts', '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/native-auto-decide.ts', 'test/auto-decide-current-declaration.test.ts', 'test/fixtures/auto-decide-current-declaration-6aef.json', 'test/auto-decide-explanatory-mode.test.ts', 'test/fixtures/auto-decide-explanatory-mode-043a.json', 'test/fixtures/auto-decide-explanatory-mode-749df.json', 'test/auto-decide-structured.test.ts', 'test/fixtures/auto-decide-structured-77.json', 'test/helpers/auto-decision-state.ts', 'test/auto-decision-state.test.ts', 'test/fixtures/auto-decide-state-cab3.json', 'bin/gstack-question-log', 'bin/gstack-question-preference', 'test/native-auto-decide.test.ts', 'test/native-auto-decide-pty.test.ts', 'test/helpers/fake-plan-seed.ts', 'test/fixtures/native-auto-decide-ag.json', 'test/eng-seeded-completion-ai.test.ts', 'test/fixtures/eng-seeded-completion-ai.json', 'test/helpers/plan-count-pending-exit.ts', 'test/plan-count-pending-exit.test.ts', 'test/helpers/pty-screen.ts', 'test/pty-screen.test.ts', 'test/pty-screen-session.test.ts', 'test/fixtures/pty-screen/**',
|
||||
"test/review-entry-and-design-clarity-au.test.ts", "scripts/resolvers/preamble/generate-completion-status.ts",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'test/helpers/plan-seed-submission.ts', 'test/plan-seed-submission.test.ts', 'test/fixtures/plan-seed-cli.ts', 'test/helpers/owned-claude-transcript.ts', 'lib/fs-atomic.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/testing.ts', 'scripts/resolvers/review.ts', 'test/plan-review-cases.test.ts'
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'test/helpers/plan-seed-submission.ts', 'test/plan-seed-submission.test.ts', 'test/fixtures/plan-seed-cli.ts', 'test/helpers/owned-claude-transcript.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/testing.ts', 'scripts/resolvers/review.ts', 'test/plan-review-cases.test.ts'
|
||||
],
|
||||
|
||||
// Native question capture and interactive workflow probes.
|
||||
@@ -376,7 +376,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
|
||||
"test/ceo-mode-colon-at.test.ts", "test/fixtures/ceo-mode-colon-at.json",'test/pty-screen-unicode-ap.test.ts', 'plan-ceo-review/**', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble.ts', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-design-ui-recovery.test.ts', 'test/fixtures/design-ui-boxed-question.json', '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-expansion-disposition.test.ts', 'test/fixtures/ceo-expansion-disposition-77.json', 'test/ceo-mode-option.test.ts', 'test/pty-option-selection.test.ts', 'test/helpers/plan-count-transcript.ts', 'test/autoplan-public-narration.test.ts', 'test/fixtures/autoplan-public-narration-ad.json', '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/fixtures/eng-prerequisite-77.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-border.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/plan-count-truncated-question.test.ts', 'test/plan-count-clipped-elision.test.ts', '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/ceo-mode-preview-aa-screen.txt', 'test/fixtures/ceo-count-mode-preview-aa-screen.txt', 'test/ceo-expansion-auq.test.ts', 'test/fixtures/ceo-expansion-auq-ac.json', 'test/helpers/plan-count-pending-question.ts', 'test/autoplan-pending-question.test.ts', 'test/plan-pending-question-pty.test.ts', 'test/ceo-barless-submit.test.ts', 'test/fixtures/ceo-barless-submit-ac.json', 'test/pending-question-completion.test.ts', 'test/fixtures/pending-question-completion-ad.json', 'test/ceo-mode-posture-ad.test.ts', 'test/fixtures/ceo-mode-posture-ad.json', 'test/ceo-mode-full-ad.test.ts', 'test/fixtures/ceo-mode-full-ad.json', 'test/ceo-prerequisite-ad-v2.test.ts', 'test/fixtures/ceo-prerequisite-ad-v2.json', 'test/ceo-hold-posture-ag.test.ts', 'test/fixtures/ceo-hold-posture-ag.json',
|
||||
"test/ceo-hold-commitment-ar.test.ts", "test/fixtures/ceo-hold-commitment-ar.json",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/ceo-mode-routing-fixture.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/helpers/claude-pty-runner.unit.test.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/owned-claude-transcript.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.ts', 'scripts/resolvers/tasks-section.ts',
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/ceo-mode-routing-fixture.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/helpers/claude-pty-runner.unit.test.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/owned-claude-transcript.ts', 'scripts/resolvers/tasks-section.ts',
|
||||
'test/fixtures/ceo-expansion-pacing-77.json',
|
||||
],
|
||||
'plan-design-with-ui-scope': [
|
||||
@@ -385,17 +385,16 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'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/fixtures/eng-prerequisite-77.json',
|
||||
'test/helpers/plan-count-transcript.ts', 'test/plan-count-cross-cwd-ancestry.test.ts', 'test/fixtures/plan-count-cross-cwd-ancestry-0bcd.json', 'test/plan-count-session-cwd.test.ts',
|
||||
'test/helpers/design-ui-scope.ts', 'test/design-ui-scope.test.ts', 'test/fixtures/plan-design-ui-scope.json',
|
||||
"test/plan-scope-recovery-av.test.ts",
|
||||
"test/fixtures/plan-scope-recovery-av.json",
|
||||
"test/fixtures/design-scope-checkpoint-at.json",'plan-design-review/**', 'test/fixtures/plans/ui-heavy-feature.md', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-design-ui-recovery.test.ts', 'test/fixtures/design-ui-boxed-question.json', '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-border.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/plan-count-truncated-question.test.ts', 'test/plan-count-clipped-elision.test.ts', '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/design-scope-entry-aq.test.ts",
|
||||
|
||||
"test/review-entry-and-design-clarity-au.test.ts", "scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/plan-design-with-ui-fixture.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/helpers/plan-review-cases.ts', 'test/helpers/plan-review-board-feedback.ts', 'test/plan-review-board-feedback.test.ts', 'test/fixtures/design-board-questions.json', 'test/fixtures/design-outside-voices-question.json', 'design/src/daemon-state.ts', 'design/src/daemon.ts', 'design/test/daemon-tests-fixtures.ts', 'design/src/daemon-client.ts', 'test/helpers/owned-claude-transcript.ts', 'test/helpers/plan-skill-completion.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'bin/gstack-paths', 'bin/gstack-slug', 'scripts/resolvers/design.ts'
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/plan-design-with-ui-fixture.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/helpers/plan-review-cases.ts', 'test/helpers/plan-review-board-feedback.ts', 'test/plan-review-board-feedback.test.ts', 'test/fixtures/design-board-questions.json', 'test/fixtures/design-outside-voices-question.json', 'design/src/daemon-state.ts', 'design/src/daemon.ts', 'design/test/daemon-tests-fixtures.ts', 'design/src/daemon-client.ts', 'test/helpers/owned-claude-transcript.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'bin/gstack-paths', 'bin/gstack-slug', 'scripts/resolvers/design.ts'
|
||||
],
|
||||
'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/plan-count-design-ui-recovery.test.ts', 'test/fixtures/design-ui-boxed-question.json', '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-border.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/plan-count-truncated-question.test.ts', 'test/plan-count-clipped-elision.test.ts', '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/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/testing.ts'
|
||||
'test/pty-workspace-trust.test.ts', '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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/testing.ts'
|
||||
],
|
||||
'tpa-present': ['test/session-runner-stream-lifecycle.test.ts', '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', 'test/helpers/third-party-actions.ts',
|
||||
'test/third-party-actions-recording.test.ts', 'lib/eval-model.ts'
|
||||
@@ -453,29 +452,20 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
"test/fixtures/autoplan-cropped-gate-av.json",
|
||||
"test/plan-scope-recovery-av.test.ts",
|
||||
"test/fixtures/plan-scope-recovery-av.json",
|
||||
"test/autoplan-cropped-command-av.test.ts",
|
||||
"test/fixtures/autoplan-cropped-command-av.json",
|
||||
"test/autoplan-overwrite-progress-ax.test.ts",
|
||||
"test/fixtures/autoplan-overwrite-progress-ax.json",
|
||||
"test/fixtures/design-scope-checkpoint-at.json",
|
||||
"test/autoplan-rendered-batch-at.test.ts", "test/fixtures/autoplan-rendered-batch-at.json",'test/pty-screen-unicode-ap.test.ts', 'test/autoplan-routing-label-ap.test.ts', 'test/fixtures/autoplan-routing-label-ap.json', 'test/eng-scope-entry-ap.test.ts', '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', 'test/fixtures/eng-prerequisite-77.json', 'bin/gstack-config', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-design-ui-recovery.test.ts', 'test/fixtures/design-ui-boxed-question.json', '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/autoplan-phase-dash-ao.test.ts', 'test/fixtures/autoplan-phase-dash-ao.json', 'test/helpers/plan-count-transcript.ts', 'test/autoplan-public-narration.test.ts', 'test/fixtures/autoplan-public-narration-ad.json', '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-border.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/plan-count-truncated-question.test.ts', 'test/plan-count-clipped-elision.test.ts', '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/autoplan-method-read-audit.ts', 'test/autoplan-method-read-audit.test.ts', 'test/fixtures/autoplan-method-read-aa-events.json', 'test/fixtures/autoplan-phase-entry-cf74.json', 'test/fixtures/autoplan-phase-entry-alias-f359.json', 'test/autoplan-routing-manual-skills.test.ts', 'test/fixtures/autoplan-routing-manual-skills-ac.json', 'test/helpers/plan-count-pending-question.ts', 'test/autoplan-pending-question.test.ts', 'test/plan-pending-question-pty.test.ts', 'test/pending-question-completion.test.ts', 'test/fixtures/pending-question-completion-ad.json', 'test/fixtures/autoplan-setup-ad-v2-packet.json', 'test/helpers/autoplan-artifact-permission.ts', 'test/autoplan-artifact-permission.test.ts', 'test/fixtures/autoplan-artifact-permission-ad-v3.json', 'scripts/resolvers/testing.ts', 'test/helpers/autoplan-artifact-recorder.ts', 'test/autoplan-artifact-recorder.test.ts', 'test/autoplan-artifact-windows-argv.test.ts', 'test/autoplan-pending-artifact.test.ts', 'test/fixtures/autoplan-pending-artifact-ae.json', 'test/autoplan-edit-header-ag.test.ts', 'test/fixtures/autoplan-edit-header-ag.json', 'test/autoplan-edit-prefix-ai.test.ts', 'test/fixtures/autoplan-edit-prefix-ai.json', 'test/autoplan-edit-panel-aj.test.ts', 'test/fixtures/autoplan-edit-panel-aj.json',
|
||||
'test/autoplan-repeated-header-ak.test.ts',
|
||||
'test/fixtures/autoplan-repeated-header-ak.json',
|
||||
'test/pty-screen-unicode-ap.test.ts', 'test/autoplan-routing-label-ap.test.ts', 'test/fixtures/autoplan-routing-label-ap.json', 'test/eng-scope-entry-ap.test.ts', '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', 'test/fixtures/eng-prerequisite-77.json', 'bin/gstack-config', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-design-ui-recovery.test.ts', 'test/fixtures/design-ui-boxed-question.json', '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/autoplan-phase-dash-ao.test.ts', 'test/fixtures/autoplan-phase-dash-ao.json', 'test/helpers/plan-count-transcript.ts', 'test/autoplan-public-narration.test.ts', 'test/fixtures/autoplan-public-narration-ad.json', '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-border.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/plan-count-truncated-question.test.ts', 'test/plan-count-clipped-elision.test.ts', '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/autoplan-method-read-audit.ts', 'test/autoplan-method-read-audit.test.ts', 'test/fixtures/autoplan-method-read-aa-events.json', 'test/fixtures/autoplan-phase-entry-cf74.json', 'test/fixtures/autoplan-phase-entry-alias-f359.json', 'test/autoplan-routing-manual-skills.test.ts', 'test/fixtures/autoplan-routing-manual-skills-ac.json', 'test/helpers/plan-count-pending-question.ts', 'test/autoplan-pending-question.test.ts', 'test/plan-pending-question-pty.test.ts', 'test/pending-question-completion.test.ts', 'test/fixtures/pending-question-completion-ad.json', 'test/fixtures/autoplan-setup-ad-v2-packet.json', 'test/helpers/autoplan-artifact-permission.ts', 'scripts/resolvers/testing.ts', 'test/helpers/autoplan-artifact-recorder.ts', 'test/autoplan-artifact-recorder.test.ts', 'test/autoplan-artifact-windows-argv.test.ts', 'test/autoplan-pending-artifact.test.ts', 'test/fixtures/autoplan-pending-artifact-ae.json',
|
||||
"test/helpers/autoplan-artifact-digest.ts",
|
||||
"test/autoplan-edit-digests-al.test.ts",
|
||||
"test/fixtures/autoplan-edit-digests-al.json",
|
||||
"test/autoplan-edit-queue-am.test.ts",
|
||||
"test/fixtures/autoplan-edit-queue-am.json",
|
||||
"test/autoplan-edit-edges-an.test.ts",
|
||||
"test/fixtures/autoplan-edit-edges-an.json",
|
||||
'test/autoplan-final-gate-ao.test.ts',
|
||||
'test/fixtures/autoplan-final-gate-ao.json',
|
||||
"test/autoplan-clipped-suffix-aq.test.ts", "test/fixtures/autoplan-clipped-suffix-aq.json", "test/design-scope-entry-aq.test.ts",
|
||||
"test/helpers/autoplan-preconfigured-fixture.ts", "test/autoplan-preconfigured-onboarding-ar.test.ts",
|
||||
"test/autoplan-artifact-stall-as.test.ts", "test/fixtures/autoplan-artifact-stall-as.json",
|
||||
|
||||
"test/review-entry-and-design-clarity-au.test.ts", "scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts", "test/autoplan-with-result-au.test.ts", "test/fixtures/autoplan-with-result-au.json", "test/autoplan-command-prefix-au.test.ts", "test/fixtures/autoplan-command-prefix-au.json",
|
||||
'scripts/resolvers/design-doc-discovery.ts', 'bin/gstack-skill-start', 'bin/gstack-paths', 'test/gstack-paths.test.ts', 'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/helpers/autoplan-phase-order.ts', 'test/autoplan-fixture.test.ts', 'test/fixtures/autoplan-caller.fixture.test.ts', 'test/autoplan-phase-observation.test.ts', 'test/helpers/owned-claude-transcript.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/review.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'test/autoplan-permission-viewport.test.ts', 'test/plan-review-cases.test.ts', 'scripts/resolvers/tasks-section.ts', 'scripts/resolvers/design.ts', 'test/fixtures/autoplan-settings-overwrite.json'
|
||||
"test/review-entry-and-design-clarity-au.test.ts", "scripts/resolvers/preamble/generate-preamble-bash.ts", "scripts/resolvers/preamble/generate-completion-status.ts", "test/autoplan-with-result-au.test.ts", "test/fixtures/autoplan-with-result-au.json",
|
||||
'scripts/resolvers/design-doc-discovery.ts', 'bin/gstack-skill-start', 'bin/gstack-paths', 'test/gstack-paths.test.ts', 'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/autoplan-fixture.test.ts', 'test/fixtures/autoplan-caller.fixture.test.ts', 'test/helpers/owned-claude-transcript.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/review.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'test/plan-review-cases.test.ts', 'scripts/resolvers/tasks-section.ts', 'scripts/resolvers/design.ts', 'test/fixtures/autoplan-settings-overwrite.json'
|
||||
],
|
||||
|
||||
// Per-finding AskUserQuestion count + review-report-at-bottom assertion.
|
||||
@@ -527,7 +517,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'test/fixtures/ceo-test-subject-ao.json',
|
||||
"test/ceo-sequence-aq.test.ts", "test/fixtures/ceo-sequence-aq.json", "test/ceo-section-ordering-aq.test.ts", "test/fixtures/ceo-section-ordering-aq.json",
|
||||
"test/ceo-transaction-contract-ar.test.ts", "test/fixtures/ceo-transaction-contract-ar.json", "test/ceo-section-declarative-ar.test.ts", "test/fixtures/ceo-section-declarative-ar.json",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'test/helpers/autoplan-phase-order.ts', 'test/autoplan-phase-observation.test.ts', 'lib/fs-atomic.ts', 'test/helpers/owned-claude-transcript.ts', 'test/helpers/plan-skill-completion.ts', 'test/plan-skill-completion.test.ts', 'test/eval-budgets-policy.test.ts', 'test/fixtures/webfetch-permission.json', 'test/plan-skill-webfetch-permission.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/skill-e2e-plan-ceo-finding-count.test.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.ts', 'test/helpers/plan-review-decisions.ts', 'test/plan-review-decisions.test.ts', 'test/helpers/plan-review-cases.ts', 'test/plan-review-cases.test.ts', 'test/helpers/llm-judge.ts', 'lib/eval-model.ts', 'test/skill-e2e-plan-decision-classification.test.ts', 'test/fixtures/plan-decision-classification.ts', 'test/plan-review-calibration.test.ts', 'test/helpers/ceo-paired-fixture.ts', 'test/ceo-paired-payment-fixture.test.ts', 'test/fixtures/ceo-paired-option-values.json', 'test/fixtures/paired-payment/**', 'test/fixtures/ceo-existing-payment/**', 'scripts/resolvers/review.ts', 'scripts/resolvers/tasks-section.ts'
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/helpers/owned-claude-transcript.ts', 'test/eval-budgets-policy.test.ts', 'test/fixtures/webfetch-permission.json', 'test/plan-skill-webfetch-permission.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/skill-e2e-plan-ceo-finding-count.test.ts', 'test/helpers/plan-review-decisions.ts', 'test/plan-review-decisions.test.ts', 'test/helpers/plan-review-cases.ts', 'test/plan-review-cases.test.ts', 'test/helpers/llm-judge.ts', 'lib/eval-model.ts', 'test/skill-e2e-plan-decision-classification.test.ts', 'test/fixtures/plan-decision-classification.ts', 'test/plan-review-calibration.test.ts', 'test/fixtures/ceo-paired-option-values.json', 'test/fixtures/ceo-existing-payment/**', 'scripts/resolvers/review.ts', 'scripts/resolvers/tasks-section.ts'
|
||||
],
|
||||
'plan-eng-finding-count': [
|
||||
'test/helpers/eng-count-question-policy.ts', 'test/eng-count-question-policy.test.ts', 'test/fixtures/eng-count-actor-491.json',
|
||||
@@ -538,23 +528,10 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'test/fixtures/eng-omitted-select-361c.json',
|
||||
|
||||
'test/eng-semantic-terminal.test.ts', 'test/fixtures/eng-fb10-count-public.json', 'test/fixtures/ceo-report-permission-fb10.json',
|
||||
'test/eng-error-flow-seed.test.ts', 'test/fixtures/eng-69193-count-public.json', 'test/fixtures/eng-e366-count-public.json',
|
||||
'test/eng-initial-selector-043a.test.ts',
|
||||
'test/fixtures/eng-initial-selector-043a.json',
|
||||
'test/fixtures/eng-count-c6fc-public.json',
|
||||
'test/fixtures/eng-cdd-regression-task.json',
|
||||
'test/eng-resolution-block-position.test.ts',
|
||||
'test/eng-count-owned-outcomes.test.ts', 'test/fixtures/eng-count-owned-outcomes-f359.json',
|
||||
'test/eng-task-pause-navigation-f359.test.ts', 'test/fixtures/eng-task-pause-navigation-f359.json',
|
||||
'test/fixtures/eng-current-choice-cab3.json', 'test/fixtures/eng-completed-navigation-cab3.json',
|
||||
'test/eng-native-seed-contract.test.ts', 'test/fixtures/eng-native-seed-contract-6f.json',
|
||||
'test/fixtures/eng-native-packets-b955.json',
|
||||
'test/fixtures/eng-structure-choice-90f.json',
|
||||
'test/fixtures/eng-idp-choice-90f.json',
|
||||
'test/fixtures/eng-legacy-declaration-90f.json',
|
||||
'test/fixtures/eng-completed-navigation-cab3.json',
|
||||
'test/review-count-markdown.test.ts', 'test/fixtures/review-count-markdown-6f.json',
|
||||
'test/eng-current-native-seeds.test.ts',
|
||||
'test/fixtures/eng-current-native-seeds-6714.json',
|
||||
'test/plan-count-long-edit.test.ts', 'test/fixtures/plan-count-long-edit-0bcd.json', 'test/plan-count-cropped-wrap.test.ts',
|
||||
'test/fixtures/plan-count-cropped-wrap-6714.json',
|
||||
'test/eng-batching-saved-ledger.test.ts',
|
||||
@@ -568,51 +545,31 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'test/helpers/autoplan-artifact-permission.ts',
|
||||
'test/helpers/autoplan-artifact-digest.ts',
|
||||
'test/autoplan-artifact-recorder.test.ts', 'test/autoplan-artifact-windows-argv.test.ts',
|
||||
'test/autoplan-artifact-permission.test.ts',
|
||||
'test/autoplan-edit-digests-al.test.ts',
|
||||
'test/fixtures/autoplan-edit-digests-al.json',
|
||||
'test/eng-finding-retry-budget.test.ts',
|
||||
'test/eng-published-navigation.test.ts', 'test/fixtures/eng-published-navigation.json',
|
||||
'test/fixtures/eng-current-ledger-seeds.json',
|
||||
'scripts/resolvers/learnings.ts',
|
||||
"test/plan-count-cross-cwd-ancestry.test.ts", "test/fixtures/plan-count-cross-cwd-ancestry-0bcd.json", "test/plan-count-session-cwd.test.ts",
|
||||
"test/eng-scheduled-regression.test.ts",
|
||||
"test/eng-owned-explanation.test.ts", "test/fixtures/eng-owned-explanation.json",
|
||||
|
||||
"test/eng-architecture-cache-av.test.ts",
|
||||
"test/fixtures/eng-architecture-cache-av-calls.json",
|
||||
"test/eng-paired-regression-av.test.ts",
|
||||
"test/fixtures/eng-paired-regression-av.md",
|
||||
"test/eng-owned-seeds-av.test.ts",
|
||||
"test/fixtures/eng-owned-seeds-av.json",
|
||||
"test/plan-scope-recovery-av.test.ts",
|
||||
"test/fixtures/plan-scope-recovery-av.json",
|
||||
"test/eng-retry-coverage-at.test.ts", "test/fixtures/eng-retry-coverage-at.json", "test/fixtures/eng-retry-baseline-at.md",
|
||||
"test/eng-blocking-baseline-at.test.ts", "test/fixtures/eng-blocking-baseline-at.md",'test/pty-screen-unicode-ap.test.ts', 'test/design-crop-gutter-ap.test.ts', 'test/fixtures/design-crop-gutter-ap.json', 'test/helpers/dx-selected-navigation.ts', 'test/dx-selected-navigation-ap.test.ts', 'test/fixtures/dx-selected-navigation-ap.json', 'test/eng-scope-entry-ap.test.ts', 'test/dx-manual-handoff-ao.test.ts', 'test/fixtures/dx-manual-handoff-ao.json', '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-design-ui-recovery.test.ts', 'test/fixtures/design-ui-boxed-question.json', '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/autoplan-public-narration.test.ts', 'test/fixtures/autoplan-public-narration-ad.json', '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-collection-completion.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/fixtures/eng-prerequisite-77.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-edit-cropped-permission.test.ts', 'test/fixtures/plan-edit-cropped-permission-1579.json', '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-border.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/plan-count-truncated-question.test.ts', 'test/plan-count-clipped-elision.test.ts', '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/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', 'test/plan-count-permission-ac.test.ts', 'test/fixtures/plan-count-permission-ac.json', 'test/fixtures/plan-count-permission-ad.json', 'test/fixtures/plan-count-permission-ae.json', 'test/fixtures/plan-count-permission-target-ad-v2.json', 'test/helpers/eng-completion-handoff.ts', 'test/eng-count-ad-v2.test.ts', 'test/fixtures/eng-count-ad-v2.json', 'test/helpers/eng-seeded-coverage.ts', 'test/fixtures/eng-neutral-seed-749df.json', 'test/fixtures/eng-paired-suite-749df.json', 'test/fixtures/eng-a689-count-public.json', 'test/fixtures/eng-6aef-count-public.json', 'test/fixtures/eng-a689-retry-public.json', 'test/eng-seeded-coverage.test.ts', 'test/eng-seeded-packet-ae.test.ts', 'test/fixtures/eng-seeded-packet-ae.json', 'test/eng-first-category-af.test.ts', 'test/fixtures/eng-first-category-af.json', 'test/eng-regression-pinning-ag.test.ts', 'test/fixtures/eng-regression-pinning-ag.json', 'test/fixtures/plan-count-permission-ah.json', 'test/eng-next-handoff-ah.test.ts', 'test/fixtures/eng-next-handoff-ah.json', 'test/eng-declared-regression-ai.test.ts', 'test/fixtures/eng-declared-regression-ai.json', 'test/eng-snapshot-adapter-aj.test.ts', 'test/fixtures/eng-snapshot-adapter-aj.json',
|
||||
'test/eng-declared-suite-ak.test.ts',
|
||||
'test/fixtures/eng-declared-suite-ak.json',
|
||||
'test/pty-screen-unicode-ap.test.ts', 'test/design-crop-gutter-ap.test.ts', 'test/fixtures/design-crop-gutter-ap.json', 'test/helpers/dx-selected-navigation.ts', 'test/dx-selected-navigation-ap.test.ts', 'test/fixtures/dx-selected-navigation-ap.json', 'test/eng-scope-entry-ap.test.ts', 'test/dx-manual-handoff-ao.test.ts', 'test/fixtures/dx-manual-handoff-ao.json', '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-design-ui-recovery.test.ts', 'test/fixtures/design-ui-boxed-question.json', '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/autoplan-public-narration.test.ts', 'test/fixtures/autoplan-public-narration-ad.json', '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-collection-completion.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/fixtures/eng-prerequisite-77.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-edit-cropped-permission.test.ts', 'test/fixtures/plan-edit-cropped-permission-1579.json', '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-border.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/plan-count-truncated-question.test.ts', 'test/plan-count-clipped-elision.test.ts', '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/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', 'test/plan-count-permission-ac.test.ts', 'test/fixtures/plan-count-permission-ac.json', 'test/fixtures/plan-count-permission-ad.json', 'test/fixtures/plan-count-permission-ae.json', 'test/fixtures/plan-count-permission-target-ad-v2.json', 'test/eng-count-ad-v2.test.ts', 'test/fixtures/eng-count-ad-v2.json', 'test/helpers/eng-seeded-coverage.ts', 'test/fixtures/eng-a689-retry-public.json', 'test/eng-seeded-coverage.test.ts', 'test/eng-first-category-af.test.ts', 'test/fixtures/eng-first-category-af.json', 'test/fixtures/plan-count-permission-ah.json', 'test/eng-next-handoff-ah.test.ts', 'test/fixtures/eng-next-handoff-ah.json',
|
||||
'test/plan-count-crop-ak.test.ts',
|
||||
'test/fixtures/plan-count-crop-ak.json',
|
||||
'test/plan-count-quoted-frame-ak.test.ts',
|
||||
'test/fixtures/plan-count-quoted-frame-ak.json',
|
||||
"test/eng-golden-master-al.test.ts",
|
||||
"test/fixtures/eng-golden-master-al.json",
|
||||
"test/eng-cache-brief-am.test.ts",
|
||||
"test/fixtures/eng-cache-brief-am.json",
|
||||
"test/eng-legacy-contract-am.test.ts",
|
||||
"test/fixtures/eng-legacy-contract-am.json",
|
||||
"test/eng-retry-contract-am.test.ts",
|
||||
"test/fixtures/eng-retry-contract-am.json",
|
||||
"test/eng-cache-owner-an.test.ts",
|
||||
"test/fixtures/eng-cache-owner-an.json",
|
||||
"test/eng-golden-parity-an.test.ts",
|
||||
"test/fixtures/eng-golden-parity-an.json",
|
||||
"test/eng-injected-export-aq.test.ts", "test/fixtures/eng-injected-export-aq.json", "test/eng-staged-regression-aq.test.ts", "test/fixtures/eng-staged-regression-aq.md", "test/eng-library-hooks-aq.test.ts", "test/fixtures/eng-library-hooks-aq.json",
|
||||
"test/eng-before-rewrite-ar.test.ts", "test/fixtures/eng-before-rewrite-ar.md",
|
||||
"test/eng-declarative-as.test.ts", "test/fixtures/eng-declarative-as.json", "test/eng-mandatory-baseline-as.test.ts", "test/fixtures/eng-mandatory-baseline-as.md", "test/helpers/eng-cache-writer-decision.ts", "test/eng-cache-writes-as.test.ts", "test/fixtures/eng-cache-writes-as.json", "test/eng-retry-coverage-as.test.ts", "test/fixtures/eng-retry-coverage-as.json", "test/fixtures/eng-retry-baseline-as.md",
|
||||
"test/eng-injected-export-aq.test.ts", "test/fixtures/eng-injected-export-aq.json", "test/eng-library-hooks-aq.test.ts", "test/fixtures/eng-library-hooks-aq.json",
|
||||
"test/eng-declarative-as.test.ts", "test/fixtures/eng-declarative-as.json", "test/helpers/eng-cache-writer-decision.ts", "test/eng-cache-writes-as.test.ts", "test/fixtures/eng-cache-writes-as.json",
|
||||
|
||||
"test/eng-required-parity-au.test.ts", "test/fixtures/eng-required-parity-au.md", "test/helpers/eng-retained-corpus.ts", "test/eng-retained-corpus-au.test.ts", "test/fixtures/eng-retained-corpus-au.md", "test/eng-annotated-cache-au.test.ts", "test/fixtures/eng-annotated-cache-au.json", "test/review-entry-and-design-clarity-au.test.ts", "scripts/resolvers/preamble/generate-preamble-bash.ts",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'test/helpers/autoplan-phase-order.ts', 'test/autoplan-phase-observation.test.ts', 'lib/fs-atomic.ts', 'test/helpers/owned-claude-transcript.ts', 'test/helpers/plan-skill-completion.ts', 'test/plan-skill-completion.test.ts', 'test/eval-budgets-policy.test.ts', 'test/fixtures/webfetch-permission.json', 'test/plan-skill-webfetch-permission.test.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.ts', 'test/helpers/plan-review-decisions.ts', 'test/plan-review-decisions.test.ts', 'test/helpers/plan-review-cases.ts', 'test/plan-review-cases.test.ts', 'test/helpers/llm-judge.ts', 'lib/eval-model.ts', 'test/skill-e2e-plan-decision-classification.test.ts', 'test/fixtures/plan-decision-classification.ts', 'test/plan-review-calibration.test.ts', 'scripts/resolvers/testing.ts', 'test/helpers/eng-finding-fixture.ts', 'test/eng-finding-fixture.test.ts', 'test/fixtures/eng-existing-auth/**', 'scripts/resolvers/review.ts'
|
||||
"test/eng-annotated-cache-au.test.ts", "test/fixtures/eng-annotated-cache-au.json", "test/review-entry-and-design-clarity-au.test.ts", "scripts/resolvers/preamble/generate-preamble-bash.ts",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/helpers/owned-claude-transcript.ts', 'test/eval-budgets-policy.test.ts', 'test/fixtures/webfetch-permission.json', 'test/plan-skill-webfetch-permission.test.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/helpers/plan-review-decisions.ts', 'test/plan-review-decisions.test.ts', 'test/helpers/plan-review-cases.ts', 'test/plan-review-cases.test.ts', 'test/helpers/llm-judge.ts', 'lib/eval-model.ts', 'test/skill-e2e-plan-decision-classification.test.ts', 'test/fixtures/plan-decision-classification.ts', 'test/plan-review-calibration.test.ts', 'scripts/resolvers/testing.ts', 'test/eng-finding-fixture.test.ts', 'scripts/resolvers/review.ts'
|
||||
],
|
||||
'plan-design-finding-count': [
|
||||
'test/helpers/design-count-fixture.ts', 'test/design-count-fixture.test.ts', 'test/fixtures/design-count-sep20-calls.json', 'test/fixtures/design-count-sep21-first-call.json', 'test/fixtures/design-count-sep21-confirm-first-call.json',
|
||||
@@ -663,7 +620,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
"test/design-primary-group-as.test.ts", "test/fixtures/design-primary-group-as-calls.json",
|
||||
|
||||
"test/review-entry-and-design-clarity-au.test.ts", "scripts/resolvers/preamble/generate-preamble-bash.ts",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'test/helpers/autoplan-phase-order.ts', 'test/autoplan-phase-observation.test.ts', 'lib/fs-atomic.ts', 'test/helpers/owned-claude-transcript.ts', 'test/helpers/plan-skill-completion.ts', 'test/plan-skill-completion.test.ts', 'test/eval-budgets-policy.test.ts', 'test/fixtures/webfetch-permission.json', 'test/plan-skill-webfetch-permission.test.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.ts', 'test/helpers/plan-review-decisions.ts', 'test/plan-review-decisions.test.ts', 'test/helpers/plan-review-cases.ts', 'test/helpers/plan-review-board-feedback.ts', 'test/plan-review-board-feedback.test.ts', 'test/fixtures/design-board-questions.json', 'design/src/daemon-state.ts', 'design/src/daemon.ts', 'design/test/daemon-tests-fixtures.ts', 'design/src/daemon-client.ts', 'test/plan-review-cases.test.ts', 'test/helpers/llm-judge.ts', 'lib/eval-model.ts', 'test/skill-e2e-plan-decision-classification.test.ts', 'test/fixtures/plan-decision-classification.ts', 'test/plan-review-calibration.test.ts', 'test/design-finding-fixture.test.ts', 'scripts/resolvers/review.ts', 'bin/gstack-paths', 'bin/gstack-slug', 'scripts/resolvers/design.ts'
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/helpers/owned-claude-transcript.ts', 'test/eval-budgets-policy.test.ts', 'test/fixtures/webfetch-permission.json', 'test/plan-skill-webfetch-permission.test.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/helpers/plan-review-decisions.ts', 'test/plan-review-decisions.test.ts', 'test/helpers/plan-review-cases.ts', 'test/helpers/plan-review-board-feedback.ts', 'test/plan-review-board-feedback.test.ts', 'test/fixtures/design-board-questions.json', 'design/src/daemon-state.ts', 'design/src/daemon.ts', 'design/test/daemon-tests-fixtures.ts', 'design/src/daemon-client.ts', 'test/plan-review-cases.test.ts', 'test/helpers/llm-judge.ts', 'lib/eval-model.ts', 'test/skill-e2e-plan-decision-classification.test.ts', 'test/fixtures/plan-decision-classification.ts', 'test/plan-review-calibration.test.ts', 'test/design-finding-fixture.test.ts', 'scripts/resolvers/review.ts', 'bin/gstack-paths', 'bin/gstack-slug', 'scripts/resolvers/design.ts'
|
||||
],
|
||||
'plan-devex-finding-count': [
|
||||
'test/fixtures/devex-seed-sep21-calls.json',
|
||||
@@ -694,7 +651,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
"test/fixtures/dx-declarative-choices-am.json",
|
||||
"test/dx-declarative-stage-ar.test.ts", "test/fixtures/dx-declarative-stage-ar.json",
|
||||
"test/dx-asserted-defect-as.test.ts", "test/fixtures/dx-asserted-defect-as.json", "test/fixtures/dx-asserted-defect-as-retry.json",
|
||||
'bin/gstack-decision-log', 'lib/gstack-decision.ts', 'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'test/helpers/autoplan-phase-order.ts', 'test/autoplan-phase-observation.test.ts', 'lib/fs-atomic.ts', 'test/devex-finding-fixture.test.ts', 'test/fixtures/devex-checkpoint-todos.json', 'test/helpers/owned-claude-transcript.ts', 'test/helpers/plan-skill-completion.ts', 'test/plan-skill-completion.test.ts', 'test/eval-budgets-policy.test.ts', 'test/fixtures/webfetch-permission.json', 'test/plan-skill-webfetch-permission.test.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.ts', 'test/helpers/plan-review-decisions.ts', 'test/plan-review-decisions.test.ts', 'test/helpers/plan-review-cases.ts', 'test/plan-review-cases.test.ts', 'test/helpers/llm-judge.ts', 'lib/eval-model.ts', 'test/skill-e2e-plan-decision-classification.test.ts', 'test/fixtures/plan-decision-classification.ts', 'test/plan-review-calibration.test.ts', 'scripts/resolvers/review.ts', 'test/fixtures/devex-existing-sdk/README.md', 'test/fixtures/devex-existing-sdk/docs/getting-started.md', 'test/fixtures/devex-existing-sdk/docs/feedback.md', 'test/fixtures/devex-existing-sdk/docs/reference-v1.md'
|
||||
'bin/gstack-decision-log', 'lib/gstack-decision.ts', 'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/devex-finding-fixture.test.ts', 'test/fixtures/devex-checkpoint-todos.json', 'test/helpers/owned-claude-transcript.ts', 'test/eval-budgets-policy.test.ts', 'test/fixtures/webfetch-permission.json', 'test/plan-skill-webfetch-permission.test.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/helpers/plan-review-decisions.ts', 'test/plan-review-decisions.test.ts', 'test/helpers/plan-review-cases.ts', 'test/plan-review-cases.test.ts', 'test/helpers/llm-judge.ts', 'lib/eval-model.ts', 'test/skill-e2e-plan-decision-classification.test.ts', 'test/fixtures/plan-decision-classification.ts', 'test/plan-review-calibration.test.ts', 'scripts/resolvers/review.ts', 'test/fixtures/devex-existing-sdk/README.md', 'test/fixtures/devex-existing-sdk/docs/getting-started.md', 'test/fixtures/devex-existing-sdk/docs/feedback.md', 'test/fixtures/devex-existing-sdk/docs/reference-v1.md'
|
||||
],
|
||||
|
||||
// Gate-tier reviewCount-floor counterparts. Catch the May 2026 transcript
|
||||
@@ -717,7 +674,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
"test/eng-injected-export-aq.test.ts", "test/fixtures/eng-injected-export-aq.json", "test/eng-library-hooks-aq.test.ts", "test/fixtures/eng-library-hooks-aq.json",
|
||||
|
||||
"test/review-entry-and-design-clarity-au.test.ts", "scripts/resolvers/preamble/generate-preamble-bash.ts",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/testing.ts', 'test/plan-review-cases.test.ts'
|
||||
'test/pty-workspace-trust.test.ts', '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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/testing.ts', 'test/plan-review-cases.test.ts'
|
||||
],
|
||||
'plan-ceo-finding-floor': [
|
||||
'lib/claude-public-transcript.ts', 'test/plan-create-prepublication.test.ts', 'test/fixtures/plan-create-prepublication-491.json', 'test/plan-create-combined-permission.test.ts', 'test/fixtures/plan-create-combined-permission-70b.json', 'test/fixtures/plan-floor-quote-70b.json',
|
||||
@@ -730,7 +687,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'test/fixtures/ceo-report-permission-fb10.json', 'test/plan-floor-permission.test.ts', 'test/fixtures/plan-floor-permission-fb10.json', 'test/helpers/plan-count-file-permission.ts', 'test/plan-edit-cropped-permission.test.ts', 'test/fixtures/plan-edit-cropped-permission-1579.json', 'test/plan-count-file-permission.test.ts', 'test/fixtures/plan-count-permission-target-ad-v2.json', 'test/design-crop-gutter-ap.test.ts', 'test/fixtures/design-crop-gutter-ap.json', 'test/plan-count-crop-ak.test.ts', 'test/fixtures/plan-count-crop-ak.json', 'test/plan-count-long-edit.test.ts', 'test/fixtures/plan-count-long-edit-0bcd.json', 'test/plan-count-cropped-wrap.test.ts', 'test/fixtures/plan-count-cropped-wrap-6714.json','test/paid-retry-supervision.test.ts', 'bin/gstack-skill-start', 'bin/gstack-skill-end', 'plan-ceo-review/**', 'scripts/resolvers/preamble.ts', 'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/preamble/generate-completion-status.ts', 'scripts/resolvers/review.ts', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-design-ui-recovery.test.ts', 'test/fixtures/design-ui-boxed-question.json', '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/fixtures/forcing-finding-seeds.ts', 'test/skill-e2e-plan-ceo-finding-floor.test.ts', 'test/plan-count-truncated-border.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/plan-count-truncated-question.test.ts', 'test/plan-count-clipped-elision.test.ts', '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-floor-target.ts', 'test/plan-floor-target.test.ts', 'test/helpers/plan-count-fixture.ts', 'test/plan-count-fixture.test.ts', 'test/helpers/plan-count-artifacts.ts', 'test/plan-count-artifacts.test.ts',
|
||||
"test/ceo-section-ordering-aq.test.ts", "test/fixtures/ceo-section-ordering-aq.json",
|
||||
"test/ceo-transaction-contract-ar.test.ts", "test/fixtures/ceo-transaction-contract-ar.json", "test/ceo-section-declarative-ar.test.ts", "test/fixtures/ceo-section-declarative-ar.json",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/tasks-section.ts'
|
||||
'test/pty-workspace-trust.test.ts', '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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'scripts/resolvers/tasks-section.ts'
|
||||
],
|
||||
'plan-design-finding-floor': [
|
||||
'test/paid-retry-supervision.test.ts',
|
||||
@@ -748,7 +705,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
"test/design-scope-entry-aq.test.ts",
|
||||
|
||||
"test/review-entry-and-design-clarity-au.test.ts", "scripts/resolvers/preamble/generate-preamble-bash.ts",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/plan-design-floor-fixture.test.ts'
|
||||
'test/pty-workspace-trust.test.ts', '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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/plan-design-floor-fixture.test.ts'
|
||||
],
|
||||
'plan-devex-finding-floor': [
|
||||
'test/plan-floor-dx-actor.test.ts', 'test/fixtures/plan-floor-dx-custom-491.json', 'test/fixtures/plan-floor-dx-editor-hint.json',
|
||||
@@ -761,7 +718,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'test/fixtures/plan-floor-routing-361c.json',
|
||||
|
||||
'test/fixtures/ceo-report-permission-fb10.json', 'test/plan-floor-permission.test.ts', 'test/fixtures/plan-floor-permission-fb10.json', 'test/helpers/plan-count-file-permission.ts', 'test/plan-edit-cropped-permission.test.ts', 'test/fixtures/plan-edit-cropped-permission-1579.json', 'test/plan-count-file-permission.test.ts', 'test/fixtures/plan-count-permission-target-ad-v2.json', 'test/design-crop-gutter-ap.test.ts', 'test/fixtures/design-crop-gutter-ap.json', 'test/plan-count-crop-ak.test.ts', 'test/fixtures/plan-count-crop-ak.json', 'test/plan-count-long-edit.test.ts', 'test/fixtures/plan-count-long-edit-0bcd.json', 'test/plan-count-cropped-wrap.test.ts', 'test/fixtures/plan-count-cropped-wrap-6714.json','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', 'scripts/resolvers/review.ts', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-design-ui-recovery.test.ts', 'test/fixtures/design-ui-boxed-question.json', '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/fixtures/forcing-finding-seeds.ts', 'test/skill-e2e-plan-devex-finding-floor.test.ts', 'test/plan-count-truncated-border.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/plan-count-truncated-question.test.ts', 'test/plan-count-clipped-elision.test.ts', '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-floor-target.ts', 'test/plan-floor-target.test.ts', 'test/helpers/plan-count-fixture.ts', 'test/plan-count-fixture.test.ts', 'test/helpers/plan-count-artifacts.ts', 'test/plan-count-artifacts.test.ts',
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts'
|
||||
'test/pty-workspace-trust.test.ts', '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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts'
|
||||
],
|
||||
|
||||
// Multi-finding batching regression — periodic tier complement to the
|
||||
@@ -777,9 +734,6 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'test/fixtures/eng-omitted-select-361c.json',
|
||||
|
||||
'test/fixtures/ceo-report-permission-fb10.json',
|
||||
'test/eng-error-flow-seed.test.ts', 'test/fixtures/eng-69193-count-public.json', 'test/fixtures/eng-e366-count-public.json',
|
||||
'test/eng-initial-selector-043a.test.ts',
|
||||
'test/fixtures/eng-initial-selector-043a.json',
|
||||
'test/fixtures/eng-batching-prefixed-ledger-f359.json',
|
||||
'test/eng-batching-current-ledger.test.ts', 'test/fixtures/eng-batching-saved-ledger-b176.json',
|
||||
'test/review-count-markdown.test.ts', 'test/fixtures/review-count-markdown-6f.json',
|
||||
@@ -791,14 +745,14 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'test/eng-batching-native-replay.test.ts', 'test/fixtures/eng-batching-native-8525.json',
|
||||
'test/eng-batching-saved-ledger.test.ts', 'test/fixtures/eng-batching-saved-ledger-dacc.json',
|
||||
'scripts/resolvers/learnings.ts',
|
||||
'test/helpers/eng-seeded-coverage.ts', 'test/fixtures/eng-neutral-seed-749df.json', 'test/fixtures/eng-paired-suite-749df.json', 'test/fixtures/eng-a689-count-public.json', 'test/fixtures/eng-6aef-count-public.json', 'test/fixtures/eng-a689-retry-public.json', 'test/eng-seeded-coverage.test.ts',
|
||||
'test/helpers/eng-seeded-coverage.ts', 'test/fixtures/eng-a689-retry-public.json', 'test/eng-seeded-coverage.test.ts',
|
||||
'test/eng-resolution-block-position.test.ts',
|
||||
"test/plan-count-cross-cwd-ancestry.test.ts", "test/fixtures/plan-count-cross-cwd-ancestry-0bcd.json", "test/plan-count-session-cwd.test.ts",
|
||||
|
||||
"test/plan-scope-recovery-av.test.ts",
|
||||
"test/fixtures/plan-scope-recovery-av.json",
|
||||
"test/batching-permission-at.test.ts", "test/fixtures/batching-permission-at.json",
|
||||
"test/eng-declared-retry-at.test.ts", "test/fixtures/eng-declared-retry-at.json",'test/pty-screen-unicode-ap.test.ts', 'test/design-crop-gutter-ap.test.ts', 'test/fixtures/design-crop-gutter-ap.json', 'test/eng-scope-entry-ap.test.ts', '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', 'scripts/resolvers/review.ts', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-design-ui-recovery.test.ts', 'test/fixtures/design-ui-boxed-question.json', '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/helpers/pty-trust-dialog.ts', 'test/pty-trust-dialog.test.ts', 'test/helpers/plan-count-transcript.ts', 'test/autoplan-public-narration.test.ts', 'test/fixtures/autoplan-public-narration-ad.json', '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-collection-completion.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/fixtures/eng-prerequisite-77.json', 'test/fixtures/forcing-finding-seeds.ts', 'test/skill-e2e-plan-eng-multi-finding-batching.test.ts', 'test/plan-count-checkbox.test.ts', 'test/fixtures/ceo-checkbox-l.screen.txt', '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-edit-cropped-permission.test.ts', 'test/fixtures/plan-edit-cropped-permission-1579.json', '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-border.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/plan-count-truncated-question.test.ts', 'test/plan-count-clipped-elision.test.ts', '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/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', 'test/plan-count-permission-ac.test.ts', 'test/fixtures/plan-count-permission-ac.json', 'test/fixtures/plan-count-permission-ad.json', 'test/fixtures/plan-count-permission-ae.json', 'test/fixtures/plan-count-permission-target-ad-v2.json', 'test/eng-count-ad-v2.test.ts', 'test/fixtures/eng-count-ad-v2.json', 'test/eng-first-category-af.test.ts', 'test/fixtures/eng-first-category-af.json', 'test/eng-regression-pinning-ag.test.ts', 'test/fixtures/eng-regression-pinning-ag.json', 'test/fixtures/plan-count-permission-ah.json', 'test/eng-declared-regression-ai.test.ts', 'test/fixtures/eng-declared-regression-ai.json',
|
||||
"test/eng-declared-retry-at.test.ts", "test/fixtures/eng-declared-retry-at.json",'test/pty-screen-unicode-ap.test.ts', 'test/design-crop-gutter-ap.test.ts', 'test/fixtures/design-crop-gutter-ap.json', 'test/eng-scope-entry-ap.test.ts', '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', 'scripts/resolvers/review.ts', 'test/helpers/claude-pty-runner.ts', 'test/plan-count-design-ui-recovery.test.ts', 'test/fixtures/design-ui-boxed-question.json', '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/helpers/pty-trust-dialog.ts', 'test/pty-trust-dialog.test.ts', 'test/helpers/plan-count-transcript.ts', 'test/autoplan-public-narration.test.ts', 'test/fixtures/autoplan-public-narration-ad.json', '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-collection-completion.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/fixtures/eng-prerequisite-77.json', 'test/fixtures/forcing-finding-seeds.ts', 'test/skill-e2e-plan-eng-multi-finding-batching.test.ts', 'test/plan-count-checkbox.test.ts', 'test/fixtures/ceo-checkbox-l.screen.txt', '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-edit-cropped-permission.test.ts', 'test/fixtures/plan-edit-cropped-permission-1579.json', '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-border.test.ts', 'test/fixtures/eng-d2-truncated-border-0bcd.json', 'test/plan-count-truncated-question.test.ts', 'test/plan-count-clipped-elision.test.ts', '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/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', 'test/plan-count-permission-ac.test.ts', 'test/fixtures/plan-count-permission-ac.json', 'test/fixtures/plan-count-permission-ad.json', 'test/fixtures/plan-count-permission-ae.json', 'test/fixtures/plan-count-permission-target-ad-v2.json', 'test/eng-count-ad-v2.test.ts', 'test/fixtures/eng-count-ad-v2.json', 'test/eng-first-category-af.test.ts', 'test/fixtures/eng-first-category-af.json', 'test/fixtures/plan-count-permission-ah.json',
|
||||
'test/plan-count-crop-ak.test.ts',
|
||||
'test/fixtures/plan-count-crop-ak.json',
|
||||
'test/plan-count-quoted-frame-ak.test.ts',
|
||||
@@ -807,7 +761,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
"test/eng-declarative-as.test.ts", "test/fixtures/eng-declarative-as.json", "test/helpers/eng-cache-writer-decision.ts", "test/eng-cache-writes-as.test.ts", "test/fixtures/eng-cache-writes-as.json",
|
||||
|
||||
"test/review-entry-and-design-clarity-au.test.ts", "scripts/resolvers/preamble/generate-preamble-bash.ts",
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'test/helpers/autoplan-phase-order.ts', 'test/autoplan-phase-observation.test.ts', 'lib/fs-atomic.ts', 'test/helpers/owned-claude-transcript.ts', 'test/helpers/plan-skill-completion.ts', 'test/plan-skill-completion.test.ts', 'test/eval-budgets-policy.test.ts', 'test/fixtures/webfetch-permission.json', 'test/plan-skill-webfetch-permission.test.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.ts', 'test/helpers/plan-review-decisions.ts', 'test/plan-review-decisions.test.ts', 'test/helpers/plan-review-cases.ts', 'test/plan-review-cases.test.ts', 'test/helpers/llm-judge.ts', 'lib/eval-model.ts', 'test/skill-e2e-plan-decision-classification.test.ts', 'test/fixtures/plan-decision-classification.ts', 'test/plan-review-calibration.test.ts', 'scripts/resolvers/testing.ts', 'test/fixtures/eng-file-permission-repaint.json'
|
||||
'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/helpers/owned-claude-transcript.ts', 'test/eval-budgets-policy.test.ts', 'test/fixtures/webfetch-permission.json', 'test/plan-skill-webfetch-permission.test.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/helpers/plan-review-decisions.ts', 'test/plan-review-decisions.test.ts', 'test/helpers/plan-review-cases.ts', 'test/plan-review-cases.test.ts', 'test/helpers/llm-judge.ts', 'lib/eval-model.ts', 'test/skill-e2e-plan-decision-classification.test.ts', 'test/fixtures/plan-decision-classification.ts', 'test/plan-review-calibration.test.ts', 'scripts/resolvers/testing.ts', 'test/fixtures/eng-file-permission-repaint.json'
|
||||
],
|
||||
'plan-ceo-split-overflow': [
|
||||
'lib/claude-public-transcript.ts', 'test/plan-create-prepublication.test.ts', 'test/fixtures/plan-create-prepublication-491.json', 'test/plan-create-combined-permission.test.ts', 'test/fixtures/plan-create-combined-permission-70b.json',
|
||||
@@ -828,7 +782,7 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
||||
'test/fixtures/plan-count-crop-ak.json',
|
||||
'test/plan-count-quoted-frame-ak.test.ts',
|
||||
'test/fixtures/plan-count-quoted-frame-ak.json',
|
||||
'docs/askuserquestion-split.md', 'test/resolver-ask-user-format.test.ts', 'bin/gstack-slug', 'test/helpers/hermetic-env.test.ts', 'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'test/helpers/autoplan-phase-order.ts', 'test/autoplan-phase-observation.test.ts', 'lib/fs-atomic.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/helpers/owned-claude-transcript.ts', 'test/helpers/plan-skill-completion.ts', 'test/plan-skill-completion.test.ts', 'test/eval-budgets-policy.test.ts', 'test/fixtures/webfetch-permission.json', 'test/plan-skill-webfetch-permission.test.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/pty-current-screen.ts', 'test/pty-current-screen.test.ts', 'test/fixtures/native-viewport.ts', 'test/helpers/plan-review-decisions.ts', 'test/plan-review-decisions.test.ts', 'test/helpers/plan-review-cases.ts', 'test/plan-review-cases.test.ts', 'test/helpers/llm-judge.ts', 'lib/eval-model.ts', 'test/skill-e2e-plan-decision-classification.test.ts', 'test/fixtures/plan-decision-classification.ts', 'test/plan-review-calibration.test.ts', 'test/helpers/ceo-split-question-policy.ts', 'test/fixtures/ceo-split-actor-6aef.json', 'test/helpers/ceo-mode-option.ts', 'test/ceo-mode-option.test.ts', 'test/ceo-split-collection.test.ts', 'test/fixtures/ceo-split-collection-0bcd.json', 'test/ceo-split-question-policy.test.ts', 'scripts/resolvers/review.ts', 'scripts/resolvers/tasks-section.ts'
|
||||
'docs/askuserquestion-split.md', 'test/resolver-ask-user-format.test.ts', 'bin/gstack-slug', 'test/helpers/hermetic-env.test.ts', 'test/pty-workspace-trust.test.ts', 'test/fixtures/pty-companion-cli.ts', 'lib/fs-atomic.ts', 'test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/helpers/owned-claude-transcript.ts', 'test/eval-budgets-policy.test.ts', 'test/fixtures/webfetch-permission.json', 'test/plan-skill-webfetch-permission.test.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/plan-skill-read-permission.test.ts', 'test/fixtures/read-permission.json', 'test/pty-numbered-option-indent-native.test.ts', 'test/fixtures/ceo-split-e5-numbered-description-491.json', 'test/plan-skill-questions.test.ts', 'test/helpers/plan-skill-question-events.ts', 'test/plan-skill-question-events.test.ts', 'test/helpers/plan-skill-question-hook-scope.ts', 'test/helpers/skill-census.ts', 'test/plan-skill-question-hook-scope.test.ts', 'test/helpers/plan-review-decisions.ts', 'test/plan-review-decisions.test.ts', 'test/helpers/plan-review-cases.ts', 'test/plan-review-cases.test.ts', 'test/helpers/llm-judge.ts', 'lib/eval-model.ts', 'test/skill-e2e-plan-decision-classification.test.ts', 'test/fixtures/plan-decision-classification.ts', 'test/plan-review-calibration.test.ts', 'test/helpers/ceo-split-question-policy.ts', 'test/fixtures/ceo-split-actor-6aef.json', 'test/helpers/ceo-mode-option.ts', 'test/ceo-mode-option.test.ts', 'test/ceo-split-collection.test.ts', 'test/fixtures/ceo-split-collection-0bcd.json', 'test/ceo-split-question-policy.test.ts', 'scripts/resolvers/review.ts', 'scripts/resolvers/tasks-section.ts'
|
||||
],
|
||||
'brain-privacy-gate': ['bin/gstack-skill-start', 'bin/gstack-skill-end', 'scripts/resolvers/preamble/generate-brain-sync-block.ts', 'scripts/resolvers/preamble.ts', 'bin/gstack-brain-sync', 'bin/gstack-artifacts-init', 'bin/gstack-config', 'test/helpers/agent-sdk-runner.ts', 'test/skill-e2e-brain-privacy-gate.test.ts',
|
||||
'test/agent-sdk-runner.test.ts'
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
/**
|
||||
* Transcript section logger (v2 plan T10).
|
||||
*
|
||||
* Two jobs, both pure analysis over a SkillTestResult / NDJSON transcript:
|
||||
*
|
||||
* 1. extractSectionReads() — which `sections/*.md` files a run actually Read.
|
||||
* Used by the sectioned world (post-carve) to verify the agent opened the
|
||||
* chapters its situation required.
|
||||
*
|
||||
* 2. extractShipActions() — an observable ACTION fingerprint of a /ship run
|
||||
* (ran tests, bumped VERSION, wrote CHANGELOG, created PR, ...). This works
|
||||
* on BOTH the monolith and the sectioned skill, which is the whole point:
|
||||
* capture a baseline on the current monolith ship FIRST, then assert the
|
||||
* sectioned ship still performs the same actions. A section-read check alone
|
||||
* can't catch "agent read the chapter but skipped the step"; the action
|
||||
* fingerprint can.
|
||||
*
|
||||
* Why baseline-first (Codex outside-voice critique on the T9 plan): a logger
|
||||
* shipped in the same PR as the carve is post-failure telemetry unless it has a
|
||||
* pre-carve reference. captureShipBaseline() records the monolith's action
|
||||
* fingerprint so compareShipActions() can flag a regression introduced by the
|
||||
* carve.
|
||||
*
|
||||
* Pure functions, no I/O except the explicit read/write baseline helpers. The
|
||||
* unit tests drive these with synthetic transcripts — no paid run needed to
|
||||
* validate the logic.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
|
||||
/** Minimal shape we need from SkillTestResult — kept structural so callers can
|
||||
* pass a full SkillTestResult or a hand-built fixture in unit tests. */
|
||||
export interface ToolCallLike {
|
||||
tool: string;
|
||||
input: unknown;
|
||||
output?: string;
|
||||
}
|
||||
export interface TranscriptResultLike {
|
||||
toolCalls: ToolCallLike[];
|
||||
output?: string;
|
||||
}
|
||||
|
||||
/** Pull the file_path off a tool-call input, tolerating unknown shapes. */
|
||||
function readFilePath(input: unknown): string | null {
|
||||
if (input && typeof input === 'object') {
|
||||
const fp = (input as Record<string, unknown>).file_path;
|
||||
if (typeof fp === 'string') return fp;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Pull the command string off a Bash tool-call input. */
|
||||
function bashCommand(input: unknown): string | null {
|
||||
if (input && typeof input === 'object') {
|
||||
const cmd = (input as Record<string, unknown>).command;
|
||||
if (typeof cmd === 'string') return cmd;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every `sections/<name>.md` file the run Read, normalized to the section
|
||||
* basename (e.g. "version-bump.md"). Deduped, in first-Read order. Matching is
|
||||
* on the path segment `/sections/<file>.md` so it works regardless of whether
|
||||
* the host resolved a relative, absolute, or prefixed install path.
|
||||
*/
|
||||
export function extractSectionReads(result: TranscriptResultLike): string[] {
|
||||
const seen = new Set<string>();
|
||||
const ordered: string[] = [];
|
||||
for (const call of result.toolCalls) {
|
||||
if (call.tool !== 'Read') continue;
|
||||
const fp = readFilePath(call.input);
|
||||
if (!fp) continue;
|
||||
const m = fp.match(/(?:^|\/)sections\/([A-Za-z0-9._-]+\.md)$/);
|
||||
if (!m) continue;
|
||||
const name = m[1];
|
||||
if (!seen.has(name)) {
|
||||
seen.add(name);
|
||||
ordered.push(name);
|
||||
}
|
||||
}
|
||||
return ordered;
|
||||
}
|
||||
|
||||
/**
|
||||
* The canonical /ship action vocabulary. Each action is detected from the Bash
|
||||
* commands the agent ran (plus a couple of Write/Edit signals). Order is the
|
||||
* rough ship sequence; detection is order-independent.
|
||||
*
|
||||
* Keep this list aligned with the ship skeleton's numbered steps. The
|
||||
* section-loading eval asserts the sectioned ship still triggers the same
|
||||
* actions a monolith run did for the same fixture situation.
|
||||
*/
|
||||
export const SHIP_ACTIONS = [
|
||||
'merged_base', // git merge <base>
|
||||
'ran_tests', // bun test / npm test / the project test cmd
|
||||
'bumped_version', // wrote VERSION / package.json version / ran gstack-version-bump
|
||||
'wrote_changelog', // edited CHANGELOG.md
|
||||
'committed', // git commit
|
||||
'pushed', // git push
|
||||
'opened_pr', // gh pr create / glab mr create
|
||||
] as const;
|
||||
export type ShipAction = (typeof SHIP_ACTIONS)[number];
|
||||
|
||||
const BASH_ACTION_PATTERNS: Array<{ action: ShipAction; re: RegExp }> = [
|
||||
{ action: 'merged_base', re: /\bgit\s+merge\b/ },
|
||||
{ action: 'ran_tests', re: /\b(bun\s+test|npm\s+(run\s+)?test|yarn\s+test|pytest|go\s+test|cargo\s+test|rspec)\b/ },
|
||||
{ action: 'bumped_version', re: /gstack-version-bump\b|gstack-next-version\b|>\s*VERSION\b|npm\s+version\b/ },
|
||||
{ action: 'wrote_changelog', re: /CHANGELOG\.md/ },
|
||||
{ action: 'committed', re: /\bgit\s+commit\b/ },
|
||||
{ action: 'pushed', re: /\bgit\s+push\b/ },
|
||||
{ action: 'opened_pr', re: /\bgh\s+pr\s+create\b|\bglab\s+mr\s+create\b/ },
|
||||
];
|
||||
|
||||
/**
|
||||
* The observable action fingerprint of a ship run. Works on monolith AND
|
||||
* sectioned skills because it reads what the agent DID (Bash + file writes),
|
||||
* not which prose it loaded.
|
||||
*/
|
||||
export function extractShipActions(result: TranscriptResultLike): ShipAction[] {
|
||||
const found = new Set<ShipAction>();
|
||||
for (const call of result.toolCalls) {
|
||||
if (call.tool === 'Bash') {
|
||||
const cmd = bashCommand(call.input);
|
||||
if (!cmd) continue;
|
||||
for (const { action, re } of BASH_ACTION_PATTERNS) {
|
||||
if (re.test(cmd)) found.add(action);
|
||||
}
|
||||
} else if (call.tool === 'Write' || call.tool === 'Edit') {
|
||||
const fp = readFilePath(call.input);
|
||||
if (fp && /CHANGELOG\.md$/.test(fp)) found.add('wrote_changelog');
|
||||
if (fp && /(?:^|\/)VERSION$/.test(fp)) found.add('bumped_version');
|
||||
}
|
||||
}
|
||||
// Preserve canonical order.
|
||||
return SHIP_ACTIONS.filter(a => found.has(a));
|
||||
}
|
||||
|
||||
export interface ShipBaseline {
|
||||
tag: string;
|
||||
/** Fixture/situation id this baseline was captured for. */
|
||||
situation: string;
|
||||
/** Action fingerprint observed on the monolith ship. */
|
||||
actions: ShipAction[];
|
||||
/** Section reads observed (empty on the monolith — present after carve). */
|
||||
sectionReads: string[];
|
||||
capturedAt: string;
|
||||
}
|
||||
|
||||
const DEFAULT_BASELINE_DIR = path.join(os.homedir(), '.gstack-dev', 'ship-baselines');
|
||||
|
||||
/** Where a baseline for a given situation lives. */
|
||||
export function baselinePath(situation: string, dir = DEFAULT_BASELINE_DIR): string {
|
||||
return path.join(dir, `${situation}.json`);
|
||||
}
|
||||
|
||||
/** Persist a ship baseline (used once on the monolith, before the carve). */
|
||||
export function writeShipBaseline(baseline: ShipBaseline, dir = DEFAULT_BASELINE_DIR): string {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
const p = baselinePath(baseline.situation, dir);
|
||||
fs.writeFileSync(p, JSON.stringify(baseline, null, 2) + '\n');
|
||||
return p;
|
||||
}
|
||||
|
||||
/** Read a previously-captured baseline, or null if none exists yet. */
|
||||
export function readShipBaseline(situation: string, dir = DEFAULT_BASELINE_DIR): ShipBaseline | null {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(baselinePath(situation, dir), 'utf-8')) as ShipBaseline;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ShipActionDiff {
|
||||
/** Actions the baseline performed that the current run did NOT (the regression set). */
|
||||
missing: ShipAction[];
|
||||
/** Actions the current run performed that the baseline did not (usually fine). */
|
||||
added: ShipAction[];
|
||||
/** True when no baseline action was dropped. */
|
||||
ok: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare a current sectioned-ship run against the monolith baseline. A dropped
|
||||
* action (in baseline, not in current) is the carve regression we care about:
|
||||
* the sectioned ship stopped doing something the monolith did.
|
||||
*/
|
||||
export function compareShipActions(baseline: ShipBaseline, current: ShipAction[]): ShipActionDiff {
|
||||
const cur = new Set(current);
|
||||
const base = new Set(baseline.actions);
|
||||
const missing = baseline.actions.filter(a => !cur.has(a));
|
||||
const added = current.filter(a => !base.has(a));
|
||||
return { missing, added, ok: missing.length === 0 };
|
||||
}
|
||||
Reference in new issue
Block a user