autoplan: run-state fields, plan-text hashing, version migration and admission modules

RunState gains gstack_version, plan_input/heading, scope_flags, plan_revision, amendments and plan_reality; voices
record receipt kind and reviewed hash. New pure modules: Implementation-plan review hash, scaffold and drift lines
(B1c); gstack_version stamp and legacy decision-id migration (B1a); install-verdict and Bun-minimum admission
with a local verdict shim until lane A's lib/install-verdict.ts lands.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

bin/gstack-autoplan-snapshot.ts exports its section scanner and review projection, and initializes only a plan with
no section headings by wrapping it.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-13 01:10:13 +00:00
1 parent 4128ba751b
commit ee7b341e5d
5 files changed
+241 -4

No files matched your search

+11 -3
View File
@@ -66,7 +66,8 @@ reviewing this plan. You have NOT seen any prior review. Evaluate:
For each finding: what's wrong, severity, and the fix.`
};
function implementationBounds(plan: string) {
/** The section headings outside code fences, in order (the runner's PLAN_SECTIONS_INVALID diagnosis reads these). */
export function sectionHeadings(plan: string) {
const boundaries: Array<{ name: string; start: number; end: number }> = [];
let offset = 0;
let fence: { char: string; length: number } | null = null;
@@ -86,6 +87,11 @@ function implementationBounds(plan: string) {
}
offset += raw.length;
}
return boundaries;
}
function implementationBounds(plan: string) {
const boundaries = sectionHeadings(plan);
if (boundaries.length !== 2 || boundaries[0]!.name !== 'Implementation plan' || boundaries[1]!.name !== 'Review record') {
throw new Error('Expected one Implementation plan section followed by one Review record section outside Markdown code/quotes');
}
@@ -149,7 +155,7 @@ export function acceptedBlocks(text: string): Map<string, AcceptedBlock> {
// Marker lines belong to the author's retention record, not blind review data.
// Keep every requirement-body byte, including line endings and literal examples.
function implementationForReview(source: string): string {
export function implementationForReview(source: string): string {
let result = ''; let offset = 0;
for (const block of acceptedBlocks(source).values()) {
if (block.none) throw new Error('No-change record does not belong in Implementation plan');
@@ -411,7 +417,9 @@ export function initializePlan(sourcePlan: string, activePlan: string, restorePa
if (!text.trim() || !Buffer.from(text).equals(original)) throw new Error('Initialization source must be nonempty UTF-8 text');
let plan = text;
try { extractImplementationPlan(plan); }
catch {
catch (error) {
// Only a plan with no section headings is wrapped; a present-but-wrong or empty section reports its own error.
if (sectionHeadings(text).length) throw error;
plan = `## Implementation plan\n${text}${text.endsWith('\n') ? '' : '\n'}## Review record\n`;
// Partial/duplicate boundaries and unclosed fences remain errors, not raw-plan fallbacks.
extractImplementationPlan(plan);