From 2ab4912635f541bb0b1dd688d2c9c5cf63884ed3 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Wed, 9 Sep 2026 04:08:14 +0000 Subject: [PATCH] fix: bind Autoplan reviews to full ordered phase inputs --- autoplan/SKILL.md | 30 +++--- autoplan/SKILL.md.tmpl | 30 +++--- autoplan/sections/ceo-phase.md | 23 +++-- autoplan/sections/ceo-phase.md.tmpl | 23 +++-- autoplan/sections/design-phase.md | 24 +++-- autoplan/sections/design-phase.md.tmpl | 24 +++-- autoplan/sections/dx-phase.md | 24 +++-- autoplan/sections/dx-phase.md.tmpl | 24 +++-- autoplan/sections/eng-phase.md | 24 +++-- autoplan/sections/eng-phase.md.tmpl | 24 +++-- bin/gstack-autoplan-snapshot.ts | 96 +++++++++++++++++-- test/autoplan-phase-order.test.ts | 37 ++++++-- test/autoplan-snapshot.test.ts | 123 ++++++++++++++++++++++++- 13 files changed, 365 insertions(+), 141 deletions(-) diff --git a/autoplan/SKILL.md b/autoplan/SKILL.md index 42daa927c..c7979e278 100644 --- a/autoplan/SKILL.md +++ b/autoplan/SKILL.md @@ -621,9 +621,14 @@ preference." The user still decides, but the framing is appropriately urgent. Phases MUST execute in strict order: CEO → Design (if UI scope) → DX (if developer-facing scope) → Eng. Eng runs LAST, always, reviewing all prior amendments. -Keep ONE phase active: load its phase instructions and full skill/sections; consume -native and enabled outside results; do its full primary review; persist outputs -and amendments; emit an actual assistant completion; only then load the next phase. +Keep ONE phase active, completing these gates in order: +1. Load its phase instructions and full skill/sections, recording complete Read ranges. +2. Create the fresh snapshot and dispatch its generated nativePrompt unchanged. +3. Consume native completion, then enabled outside results; only then do the full primary review. +4. Persist outputs/amendments and run the phase's implementation check/readback. +5. Emit actual completion; only then load the next required phase. +A missing gate means the current phase remains open, even if a reviewer finished. +INPUT correlates reported input; it is not independent proof of uptake or review quality. Never draft future-phase reviews or outputs. Headings/promises are not completion. After compaction, reload current phase instructions/skill/sections; reconcile disk progress before resuming. @@ -725,14 +730,17 @@ bun -e 'console.log(require("fs").realpathSync(process.argv[1]))' "$HOME/.claude - Detect UI scope: grep the plan for view/rendering terms (component, screen, form, button, modal, layout, dashboard, sidebar, nav, dialog). Require 2+ matches. Exclude false positives ("page" alone, "UI" in acronyms). -- Detect DX scope: grep the plan for developer-facing terms (API, endpoint, REST, - GraphQL, gRPC, webhook, CLI, command, flag, argument, terminal, shell, SDK, library, - package, npm, pip, import, require, SKILL.md, skill template, Claude Code, MCP, agent, - OpenClaw, action, developer docs, getting started, onboarding, integration, debug, - implement, error message). Require 2+ matches. Also trigger DX scope if the product IS - a developer tool (the plan describes something developers install, integrate, or build - on top of) or if an AI agent is the primary user (OpenClaw actions, Claude Code skills, - MCP servers). +- Detect DX scope from the full implementation input: +```bash +bun "" scope "" +``` + Use returned `dxRequired` and record its input hash/matched terms. The existing + threshold is 2+ term matches (occurrences, not distinct terms). Also enable DX when the product is a developer tool + (developers install, integrate or build on it) or an AI agent is the primary user: + add `--developer-tool` or `--agent-primary` to this command. These flags only enable + DX; no context label can negate a positive result. Skip DX only when the result is + false and neither semantic trigger applies. + ### Step 3: Locate review skills; load each at phase entry diff --git a/autoplan/SKILL.md.tmpl b/autoplan/SKILL.md.tmpl index cd60c09e5..6eaf9df21 100644 --- a/autoplan/SKILL.md.tmpl +++ b/autoplan/SKILL.md.tmpl @@ -106,9 +106,14 @@ preference." The user still decides, but the framing is appropriately urgent. Phases MUST execute in strict order: CEO → Design (if UI scope) → DX (if developer-facing scope) → Eng. Eng runs LAST, always, reviewing all prior amendments. -Keep ONE phase active: load its phase instructions and full skill/sections; consume -native and enabled outside results; do its full primary review; persist outputs -and amendments; emit an actual assistant completion; only then load the next phase. +Keep ONE phase active, completing these gates in order: +1. Load its phase instructions and full skill/sections, recording complete Read ranges. +2. Create the fresh snapshot and dispatch its generated nativePrompt unchanged. +3. Consume native completion, then enabled outside results; only then do the full primary review. +4. Persist outputs/amendments and run the phase's implementation check/readback. +5. Emit actual completion; only then load the next required phase. +A missing gate means the current phase remains open, even if a reviewer finished. +INPUT correlates reported input; it is not independent proof of uptake or review quality. Never draft future-phase reviews or outputs. Headings/promises are not completion. After compaction, reload current phase instructions/skill/sections; reconcile disk progress before resuming. @@ -207,14 +212,17 @@ Save this absolute path as SNAPSHOT_TOOL for all shells. Stop on helper errors: - Detect UI scope: grep the plan for view/rendering terms (component, screen, form, button, modal, layout, dashboard, sidebar, nav, dialog). Require 2+ matches. Exclude false positives ("page" alone, "UI" in acronyms). -- Detect DX scope: grep the plan for developer-facing terms (API, endpoint, REST, - GraphQL, gRPC, webhook, CLI, command, flag, argument, terminal, shell, SDK, library, - package, npm, pip, import, require, SKILL.md, skill template, Claude Code, MCP, agent, - OpenClaw, action, developer docs, getting started, onboarding, integration, debug, - implement, error message). Require 2+ matches. Also trigger DX scope if the product IS - a developer tool (the plan describes something developers install, integrate, or build - on top of) or if an AI agent is the primary user (OpenClaw actions, Claude Code skills, - MCP servers). +- Detect DX scope from the full implementation input: +```bash +bun "" scope "" +``` + Use returned `dxRequired` and record its input hash/matched terms. The existing + threshold is 2+ term matches (occurrences, not distinct terms). Also enable DX when the product is a developer tool + (developers install, integrate or build on it) or an AI agent is the primary user: + add `--developer-tool` or `--agent-primary` to this command. These flags only enable + DX; no context label can negate a positive result. Skip DX only when the result is + false and neither semantic trigger applies. + ### Step 3: Locate review skills; load each at phase entry diff --git a/autoplan/sections/ceo-phase.md b/autoplan/sections/ceo-phase.md index 9c3f669ea..59086dcbc 100644 --- a/autoplan/sections/ceo-phase.md +++ b/autoplan/sections/ceo-phase.md @@ -26,20 +26,18 @@ bun "" create ceo "" "" **Claude CEO subagent** (via Agent tool): Claude Code: set Agent `run_in_background: false` if its schema exposes it. - Other hosts: native dispatch/wait. + Other hosts: use foreground dispatch and await completion when supported. - "Read the plan file at . You are an independent CEO/strategist - reviewing this plan. You have NOT seen any prior review. Evaluate: - 1. Is this the right problem to solve? Could a reframing yield 10x impact? - 2. Are the premises stated or just assumed? Which ones could be wrong? - 3. What's the 6-month regret scenario — what will look foolish? - 4. What alternatives were dismissed without sufficient analysis? - 5. What's the competitive risk — could someone else solve this first/better? - For each finding: what's wrong, severity (critical/high/medium), and the fix." + Send `nativePrompt` verbatim. If truncated, Read `nativePromptPath` to EOF. + It contains all criteria and snapshot bytes; no summaries or prior reviews. **Native completion barrier:** If `isAsync: true` / `status: "async_launched"`, - Claude Code: end response; resume only on same-agent terminal notification. - Other hosts: await that ID. Then outside → this phase's review ONLY. + Claude Code: immediately end this response with "Waiting for ." + Do no more tool calls or review work until that ID's terminal notification is + delivered. Other hosts: await that ID. Then outside → this phase's review ONLY. + For completed native reviews, match INPUT phase/hash to this snapshot. Missing + or mismatched INPUT: retry the full payload once, then use failure policy if + still invalid. No inline substitute; apply failure policy. **Codex CEO voice** (via Bash): @@ -164,7 +162,8 @@ bun "" check ceo "" "" changed ``` Use `unchanged` only if no implementation changes were accepted; explain why. Verify returned text against decisions; hashes prove bytes, not correctness. -Require full load ranges, successful output writes and terminal reviewers (unavailable/disabled allowed). +Require full load ranges, matched INPUT for completed native reviews, consumed +terminal reviewers (unavailable/disabled allowed), successful writes and this check result. Only then emit this actual assistant message: **Phase 1 complete.** diff --git a/autoplan/sections/ceo-phase.md.tmpl b/autoplan/sections/ceo-phase.md.tmpl index dfb84cbcd..7a613c0bd 100644 --- a/autoplan/sections/ceo-phase.md.tmpl +++ b/autoplan/sections/ceo-phase.md.tmpl @@ -24,20 +24,18 @@ bun "" create ceo "" "" **{{NATIVE_LABEL}} CEO subagent** (via Agent tool): Claude Code: set Agent `run_in_background: false` if its schema exposes it. - Other hosts: native dispatch/wait. + Other hosts: use foreground dispatch and await completion when supported. - "Read the plan file at . You are an independent CEO/strategist - reviewing this plan. You have NOT seen any prior review. Evaluate: - 1. Is this the right problem to solve? Could a reframing yield 10x impact? - 2. Are the premises stated or just assumed? Which ones could be wrong? - 3. What's the 6-month regret scenario — what will look foolish? - 4. What alternatives were dismissed without sufficient analysis? - 5. What's the competitive risk — could someone else solve this first/better? - For each finding: what's wrong, severity (critical/high/medium), and the fix." + Send `nativePrompt` verbatim. If truncated, Read `nativePromptPath` to EOF. + It contains all criteria and snapshot bytes; no summaries or prior reviews. **Native completion barrier:** If `isAsync: true` / `status: "async_launched"`, - Claude Code: end response; resume only on same-agent terminal notification. - Other hosts: await that ID. Then outside → this phase's review ONLY. + Claude Code: immediately end this response with "Waiting for ." + Do no more tool calls or review work until that ID's terminal notification is + delivered. Other hosts: await that ID. Then outside → this phase's review ONLY. + For completed native reviews, match INPUT phase/hash to this snapshot. Missing + or mismatched INPUT: retry the full payload once, then use failure policy if + still invalid. No inline substitute; apply failure policy. **{{OUTSIDE_LABEL}} CEO voice** (via Bash): @@ -123,7 +121,8 @@ bun "" check ceo "" "" changed ``` Use `unchanged` only if no implementation changes were accepted; explain why. Verify returned text against decisions; hashes prove bytes, not correctness. -Require full load ranges, successful output writes and terminal reviewers (unavailable/disabled allowed). +Require full load ranges, matched INPUT for completed native reviews, consumed +terminal reviewers (unavailable/disabled allowed), successful writes and this check result. Only then emit this actual assistant message: **Phase 1 complete.** diff --git a/autoplan/sections/design-phase.md b/autoplan/sections/design-phase.md index b4d4add9e..ea7443508 100644 --- a/autoplan/sections/design-phase.md +++ b/autoplan/sections/design-phase.md @@ -17,21 +17,18 @@ bun "" create design "" "" **Claude design subagent** (native tool): Claude Code: set Agent `run_in_background: false` if its schema exposes it. - Other hosts: native dispatch/wait. + Other hosts: use foreground dispatch and await completion when supported. - "Read the plan file at . You are an independent senior product designer - reviewing this plan. You have NOT seen any prior review. Evaluate: - 1. Information hierarchy: what does the user see first, second, third? Is it right? - 2. Missing states: loading, empty, error, success, partial — which are unspecified? - 3. User journey: what's the emotional arc? Where does it break? - 4. Specificity: does the plan describe SPECIFIC UI or generic patterns? - 5. What design decisions will haunt the implementer if left ambiguous? - For each finding: what's wrong, severity (critical/high/medium), and the fix." - NO prior-phase context — subagent must be truly independent. + Send `nativePrompt` verbatim. If truncated, Read `nativePromptPath` to EOF. + It contains all criteria and snapshot bytes; no summaries or prior reviews. **Native completion barrier:** If `isAsync: true` / `status: "async_launched"`, - Claude Code: end response; resume only on same-agent terminal notification. - Other hosts: await that ID. Then outside → this phase's review ONLY. + Claude Code: immediately end this response with "Waiting for ." + Do no more tool calls or review work until that ID's terminal notification is + delivered. Other hosts: await that ID. Then outside → this phase's review ONLY. + For completed native reviews, match INPUT phase/hash to this snapshot. Missing + or mismatched INPUT: retry the full payload once, then use failure policy if + still invalid. No inline substitute; apply failure policy. **Codex design voice** (via Bash): @@ -123,7 +120,8 @@ bun "" check design "" "" changed ``` Use `unchanged` only if no implementation changes were accepted; explain why. Verify returned text against decisions; hashes prove bytes, not correctness. -Require full load ranges, successful output writes and terminal reviewers (unavailable/disabled allowed). +Require full load ranges, matched INPUT for completed native reviews, consumed +terminal reviewers (unavailable/disabled allowed), successful writes and this check result. Only then emit this actual assistant message: **Phase 2 complete.** diff --git a/autoplan/sections/design-phase.md.tmpl b/autoplan/sections/design-phase.md.tmpl index f79a66daf..3997118ed 100644 --- a/autoplan/sections/design-phase.md.tmpl +++ b/autoplan/sections/design-phase.md.tmpl @@ -15,21 +15,18 @@ bun "" create design "" "" **{{NATIVE_LABEL}} design subagent** (native tool): Claude Code: set Agent `run_in_background: false` if its schema exposes it. - Other hosts: native dispatch/wait. + Other hosts: use foreground dispatch and await completion when supported. - "Read the plan file at . You are an independent senior product designer - reviewing this plan. You have NOT seen any prior review. Evaluate: - 1. Information hierarchy: what does the user see first, second, third? Is it right? - 2. Missing states: loading, empty, error, success, partial — which are unspecified? - 3. User journey: what's the emotional arc? Where does it break? - 4. Specificity: does the plan describe SPECIFIC UI or generic patterns? - 5. What design decisions will haunt the implementer if left ambiguous? - For each finding: what's wrong, severity (critical/high/medium), and the fix." - NO prior-phase context — subagent must be truly independent. + Send `nativePrompt` verbatim. If truncated, Read `nativePromptPath` to EOF. + It contains all criteria and snapshot bytes; no summaries or prior reviews. **Native completion barrier:** If `isAsync: true` / `status: "async_launched"`, - Claude Code: end response; resume only on same-agent terminal notification. - Other hosts: await that ID. Then outside → this phase's review ONLY. + Claude Code: immediately end this response with "Waiting for ." + Do no more tool calls or review work until that ID's terminal notification is + delivered. Other hosts: await that ID. Then outside → this phase's review ONLY. + For completed native reviews, match INPUT phase/hash to this snapshot. Missing + or mismatched INPUT: retry the full payload once, then use failure policy if + still invalid. No inline substitute; apply failure policy. **{{OUTSIDE_LABEL}} design voice** (via Bash): @@ -82,7 +79,8 @@ bun "" check design "" "" changed ``` Use `unchanged` only if no implementation changes were accepted; explain why. Verify returned text against decisions; hashes prove bytes, not correctness. -Require full load ranges, successful output writes and terminal reviewers (unavailable/disabled allowed). +Require full load ranges, matched INPUT for completed native reviews, consumed +terminal reviewers (unavailable/disabled allowed), successful writes and this check result. Only then emit this actual assistant message: **Phase 2 complete.** diff --git a/autoplan/sections/dx-phase.md b/autoplan/sections/dx-phase.md index f0c6795ad..8e80f7aac 100644 --- a/autoplan/sections/dx-phase.md +++ b/autoplan/sections/dx-phase.md @@ -21,21 +21,18 @@ bun "" create dx "" "" **Claude DX subagent** (native tool): Claude Code: set Agent `run_in_background: false` if its schema exposes it. - Other hosts: native dispatch/wait. + Other hosts: use foreground dispatch and await completion when supported. - "Read the plan file at . You are an independent DX engineer - reviewing this plan. You have NOT seen any prior review. Evaluate: - 1. Getting started: how many steps from zero to hello world? What's the TTHW? - 2. API/CLI ergonomics: naming consistency, sensible defaults, progressive disclosure? - 3. Error handling: does every error path specify problem + cause + fix + docs link? - 4. Documentation: copy-paste examples? Information architecture? Interactive elements? - 5. Escape hatches: can developers override every opinionated default? - For each finding: what's wrong, severity (critical/high/medium), and the fix." - NO prior-phase context — subagent must be truly independent. + Send `nativePrompt` verbatim. If truncated, Read `nativePromptPath` to EOF. + It contains all criteria and snapshot bytes; no summaries or prior reviews. **Native completion barrier:** If `isAsync: true` / `status: "async_launched"`, - Claude Code: end response; resume only on same-agent terminal notification. - Other hosts: await that ID. Then outside → this phase's review ONLY. + Claude Code: immediately end this response with "Waiting for ." + Do no more tool calls or review work until that ID's terminal notification is + delivered. Other hosts: await that ID. Then outside → this phase's review ONLY. + For completed native reviews, match INPUT phase/hash to this snapshot. Missing + or mismatched INPUT: retry the full payload once, then use failure policy if + still invalid. No inline substitute; apply failure policy. **Codex DX voice** (via Bash): @@ -152,7 +149,8 @@ bun "" check dx "" "" changed ``` Use `unchanged` only if no implementation changes were accepted; explain why. Verify returned text against decisions; hashes prove bytes, not correctness. -Require full load ranges, successful output writes and terminal reviewers (unavailable/disabled allowed). +Require full load ranges, matched INPUT for completed native reviews, consumed +terminal reviewers (unavailable/disabled allowed), successful writes and this check result. Only then emit this actual assistant message: **Phase 2.5 complete.** diff --git a/autoplan/sections/dx-phase.md.tmpl b/autoplan/sections/dx-phase.md.tmpl index 665ffcffa..9837a69cf 100644 --- a/autoplan/sections/dx-phase.md.tmpl +++ b/autoplan/sections/dx-phase.md.tmpl @@ -19,21 +19,18 @@ bun "" create dx "" "" **{{NATIVE_LABEL}} DX subagent** (native tool): Claude Code: set Agent `run_in_background: false` if its schema exposes it. - Other hosts: native dispatch/wait. + Other hosts: use foreground dispatch and await completion when supported. - "Read the plan file at . You are an independent DX engineer - reviewing this plan. You have NOT seen any prior review. Evaluate: - 1. Getting started: how many steps from zero to hello world? What's the TTHW? - 2. API/CLI ergonomics: naming consistency, sensible defaults, progressive disclosure? - 3. Error handling: does every error path specify problem + cause + fix + docs link? - 4. Documentation: copy-paste examples? Information architecture? Interactive elements? - 5. Escape hatches: can developers override every opinionated default? - For each finding: what's wrong, severity (critical/high/medium), and the fix." - NO prior-phase context — subagent must be truly independent. + Send `nativePrompt` verbatim. If truncated, Read `nativePromptPath` to EOF. + It contains all criteria and snapshot bytes; no summaries or prior reviews. **Native completion barrier:** If `isAsync: true` / `status: "async_launched"`, - Claude Code: end response; resume only on same-agent terminal notification. - Other hosts: await that ID. Then outside → this phase's review ONLY. + Claude Code: immediately end this response with "Waiting for ." + Do no more tool calls or review work until that ID's terminal notification is + delivered. Other hosts: await that ID. Then outside → this phase's review ONLY. + For completed native reviews, match INPUT phase/hash to this snapshot. Missing + or mismatched INPUT: retry the full payload once, then use failure policy if + still invalid. No inline substitute; apply failure policy. **{{OUTSIDE_LABEL}} DX voice** (via Bash): @@ -111,7 +108,8 @@ bun "" check dx "" "" changed ``` Use `unchanged` only if no implementation changes were accepted; explain why. Verify returned text against decisions; hashes prove bytes, not correctness. -Require full load ranges, successful output writes and terminal reviewers (unavailable/disabled allowed). +Require full load ranges, matched INPUT for completed native reviews, consumed +terminal reviewers (unavailable/disabled allowed), successful writes and this check result. Only then emit this actual assistant message: **Phase 2.5 complete.** diff --git a/autoplan/sections/eng-phase.md b/autoplan/sections/eng-phase.md index 0aff61bc0..18d55ee0f 100644 --- a/autoplan/sections/eng-phase.md +++ b/autoplan/sections/eng-phase.md @@ -14,21 +14,18 @@ bun "" create eng "" "" **Claude eng subagent** (native tool): Claude Code: set Agent `run_in_background: false` if its schema exposes it. - Other hosts: native dispatch/wait. + Other hosts: use foreground dispatch and await completion when supported. - "Read the plan file at . You are an independent senior engineer - reviewing this plan. You have NOT seen any prior review. Evaluate: - 1. Architecture: Is the component structure sound? Coupling concerns? - 2. Edge cases: What breaks under 10x load? What's the nil/empty/error path? - 3. Tests: What's missing from the test plan? What would break at 2am Friday? - 4. Security: New attack surface? Auth boundaries? Input validation? - 5. Hidden complexity: What looks simple but isn't? - For each finding: what's wrong, severity, and the fix." - NO prior-phase context — subagent must be truly independent. + Send `nativePrompt` verbatim. If truncated, Read `nativePromptPath` to EOF. + It contains all criteria and snapshot bytes; no summaries or prior reviews. **Native completion barrier:** If `isAsync: true` / `status: "async_launched"`, - Claude Code: end response; resume only on same-agent terminal notification. - Other hosts: await that ID. Then outside → this phase's review ONLY. + Claude Code: immediately end this response with "Waiting for ." + Do no more tool calls or review work until that ID's terminal notification is + delivered. Other hosts: await that ID. Then outside → this phase's review ONLY. + For completed native reviews, match INPUT phase/hash to this snapshot. Missing + or mismatched INPUT: retry the full payload once, then use failure policy if + still invalid. No inline substitute; apply failure policy. **Codex eng voice** (via Bash): @@ -160,7 +157,8 @@ bun "" check eng "" "" changed ``` Use `unchanged` only if no implementation changes were accepted; explain why. Verify returned text against decisions; hashes prove bytes, not correctness. -Require full load ranges, successful output writes and terminal reviewers (unavailable/disabled allowed). +Require full load ranges, matched INPUT for completed native reviews, consumed +terminal reviewers (unavailable/disabled allowed), successful writes and this check result. Only then emit this actual assistant message: **Phase 3 complete.** diff --git a/autoplan/sections/eng-phase.md.tmpl b/autoplan/sections/eng-phase.md.tmpl index fc2d23791..286f57f9e 100644 --- a/autoplan/sections/eng-phase.md.tmpl +++ b/autoplan/sections/eng-phase.md.tmpl @@ -12,21 +12,18 @@ bun "" create eng "" "" **{{NATIVE_LABEL}} eng subagent** (native tool): Claude Code: set Agent `run_in_background: false` if its schema exposes it. - Other hosts: native dispatch/wait. + Other hosts: use foreground dispatch and await completion when supported. - "Read the plan file at . You are an independent senior engineer - reviewing this plan. You have NOT seen any prior review. Evaluate: - 1. Architecture: Is the component structure sound? Coupling concerns? - 2. Edge cases: What breaks under 10x load? What's the nil/empty/error path? - 3. Tests: What's missing from the test plan? What would break at 2am Friday? - 4. Security: New attack surface? Auth boundaries? Input validation? - 5. Hidden complexity: What looks simple but isn't? - For each finding: what's wrong, severity, and the fix." - NO prior-phase context — subagent must be truly independent. + Send `nativePrompt` verbatim. If truncated, Read `nativePromptPath` to EOF. + It contains all criteria and snapshot bytes; no summaries or prior reviews. **Native completion barrier:** If `isAsync: true` / `status: "async_launched"`, - Claude Code: end response; resume only on same-agent terminal notification. - Other hosts: await that ID. Then outside → this phase's review ONLY. + Claude Code: immediately end this response with "Waiting for ." + Do no more tool calls or review work until that ID's terminal notification is + delivered. Other hosts: await that ID. Then outside → this phase's review ONLY. + For completed native reviews, match INPUT phase/hash to this snapshot. Missing + or mismatched INPUT: retry the full payload once, then use failure policy if + still invalid. No inline substitute; apply failure policy. **{{OUTSIDE_LABEL}} eng voice** (via Bash): @@ -119,7 +116,8 @@ bun "" check eng "" "" changed ``` Use `unchanged` only if no implementation changes were accepted; explain why. Verify returned text against decisions; hashes prove bytes, not correctness. -Require full load ranges, successful output writes and terminal reviewers (unavailable/disabled allowed). +Require full load ranges, matched INPUT for completed native reviews, consumed +terminal reviewers (unavailable/disabled allowed), successful writes and this check result. Only then emit this actual assistant message: **Phase 3 complete.** diff --git a/bin/gstack-autoplan-snapshot.ts b/bin/gstack-autoplan-snapshot.ts index ef048bd0d..d9113e515 100644 --- a/bin/gstack-autoplan-snapshot.ts +++ b/bin/gstack-autoplan-snapshot.ts @@ -7,6 +7,65 @@ import { basename, dirname, join } from 'node:path'; const PHASES = ['ceo', 'design', 'dx', 'eng']; const sha256 = (text: string) => createHash('sha256').update(text).digest('hex'); +// Exact terms from Autoplan's existing Phase 0 DX trigger. Count occurrences, +// not a subjective reinterpretation of whether an API is internal or external. +const DX_TERMS = ["API", "endpoint", "REST", "GraphQL", "gRPC", "webhook", "CLI", "command", "flag", "argument", "terminal", "shell", "SDK", "library", "package", "npm", "pip", "import", "require", "SKILL.md", "skill template", "Claude Code", "MCP", "agent", "OpenClaw", "action", "developer docs", "getting started", "onboarding", "integration", "debug", "implement", "error message"]; + +function dxTermsFor(content: string) { + const matches = DX_TERMS.map(term => { + const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return { term, count: [...content.matchAll(new RegExp(`\\b${escaped}\\b`, 'gi'))].length }; + }).filter(match => match.count > 0); + const matchCount = matches.reduce((sum, match) => sum + match.count, 0); + return { threshold: 2, matches, matchCount, dxRequiredByTerms: matchCount >= 2 }; +} + +/** Byte-bound scope evidence; semantic product/user triggers can only enable DX. */ +export function detectDxScope(activePlan: string, developerTool = false, agentPrimary = false) { + const source = realpathSync(activePlan); + const content = extractImplementationPlan(readFileSync(source, 'utf8')); + const terms = dxTermsFor(content); + return { activePlan: source, sha256: sha256(content), ...terms, developerTool, agentPrimary, + dxRequired: terms.dxRequiredByTerms || developerTool || agentPrimary }; +} + +// The native dispatch payload is assembled from the same immutable bytes as +// the outside reviewer input. Keep full role criteria here, not a hand summary. +const NATIVE_REVIEWS: Record = { + ceo: `You are an independent CEO/strategist +reviewing this plan. You have NOT seen any prior review. Evaluate: +1. Is this the right problem to solve? Could a reframing yield 10x impact? +2. Are the premises stated or just assumed? Which ones could be wrong? +3. What's the 6-month regret scenario — what will look foolish? +4. What alternatives were dismissed without sufficient analysis? +5. What's the competitive risk — could someone else solve this first/better? +For each finding: what's wrong, severity (critical/high/medium), and the fix.`, + design: `You are an independent senior product designer +reviewing this plan. You have NOT seen any prior review. Evaluate: +1. Information hierarchy: what does the user see first, second, third? Is it right? +2. Missing states: loading, empty, error, success, partial — which are unspecified? +3. User journey: what's the emotional arc? Where does it break? +4. Specificity: does the plan describe SPECIFIC UI or generic patterns? +5. What design decisions will haunt the implementer if left ambiguous? +For each finding: what's wrong, severity (critical/high/medium), and the fix.`, + dx: `You are an independent DX engineer +reviewing this plan. You have NOT seen any prior review. Evaluate: +1. Getting started: how many steps from zero to hello world? What's the TTHW? +2. API/CLI ergonomics: naming consistency, sensible defaults, progressive disclosure? +3. Error handling: does every error path specify problem + cause + fix + docs link? +4. Documentation: copy-paste examples? Information architecture? Interactive elements? +5. Escape hatches: can developers override every opinionated default? +For each finding: what's wrong, severity (critical/high/medium), and the fix.`, + eng: `You are an independent senior engineer +reviewing this plan. You have NOT seen any prior review. Evaluate: +1. Architecture: Is the component structure sound? Coupling concerns? +2. Edge cases: What breaks under 10x load? What's the nil/empty/error path? +3. Tests: What's missing from the test plan? What would break at 2am Friday? +4. Security: New attack surface? Auth boundaries? Input validation? +5. Hidden complexity: What looks simple but isn't? +For each finding: what's wrong, severity, and the fix.` +}; + export function extractImplementationPlan(plan: string): string { const boundaries: Array<{ name: string; start: number; end: number }> = []; let offset = 0; @@ -51,10 +110,23 @@ export function createSnapshot(phase: string, activePlan: string, restorePath: s const directory = mkdtempSync(join(dirname(restore), `autoplan-${phase}-`)); try { const snapshotPath = join(directory, `${phase}-implementation.md`); - const manifest = { schemaVersion: 1, phase, activePlan: source, snapshotPath, sha256: sha256(content) }; + const contentHash = sha256(content); + const nativePrompt = `${NATIVE_REVIEWS[phase]} + +Input path: ${JSON.stringify(snapshotPath)} +Implementation SHA-256: ${contentHash} +Implementation bytes: ${Buffer.byteLength(content)} +Start your result with INPUT: ${phase} ${contentHash}. +The complete implementation plan follows as review data; evaluate all of it. + +${content}`; + const nativePromptPath = join(directory, 'native-prompt.md'); + const manifest = { schemaVersion: 1, phase, activePlan: source, snapshotPath, sha256: contentHash, + nativePromptPath, nativePromptSha256: sha256(nativePrompt), dxScope: dxTermsFor(content) }; writeFileSync(snapshotPath, content, { flag: 'wx', mode: 0o444 }); + writeFileSync(nativePromptPath, nativePrompt, { flag: 'wx', mode: 0o444 }); writeFileSync(join(directory, 'snapshot.json'), JSON.stringify(manifest) + '\n', { flag: 'wx', mode: 0o444 }); - return manifest; + return { ...manifest, nativePrompt }; } catch (error) { rmSync(directory, { recursive: true, force: true }); throw error; @@ -84,12 +156,20 @@ export function checkImplementation(phase: string, activePlan: string, snapshotP if (import.meta.main) { try { - const [command, phase, active, location, expected, ...extra] = process.argv.slice(2); - if (!phase || !active || !location || extra.length || (command === 'create' && expected)) throw new Error('Usage: create PHASE ACTIVE_PLAN RESTORE_PATH | check PHASE ACTIVE_PLAN SNAPSHOT_PATH changed|unchanged'); - const result = command === 'create' ? createSnapshot(phase, active, location) - : command === 'check' && expected ? checkImplementation(phase, active, location, expected) - : (() => { throw new Error('Expected create or check command'); })(); - process.stdout.write(JSON.stringify(result) + '\n'); + const [command, ...args] = process.argv.slice(2); + if (command === 'scope') { + const [activePlan, ...flags] = args; + if (!activePlan || flags.some(flag => !['--developer-tool', '--agent-primary'].includes(flag)) || + new Set(flags).size !== flags.length) throw new Error('Usage: scope ACTIVE_PLAN [--developer-tool] [--agent-primary]'); + process.stdout.write(JSON.stringify(detectDxScope(activePlan, flags.includes('--developer-tool'), flags.includes('--agent-primary'))) + '\n'); + } else { + const [phase, active, location, expected, ...extra] = args; + if (!phase || !active || !location || extra.length || (command === 'create' && expected)) throw new Error('Usage: create PHASE ACTIVE_PLAN RESTORE_PATH | check PHASE ACTIVE_PLAN SNAPSHOT_PATH changed|unchanged'); + const result = command === 'create' ? createSnapshot(phase, active, location) + : command === 'check' && expected ? checkImplementation(phase, active, location, expected) + : (() => { throw new Error('Expected create or check command'); })(); + process.stdout.write(JSON.stringify(result) + '\n'); + } } catch (error) { console.error(`gstack-autoplan-snapshot: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; diff --git a/test/autoplan-phase-order.test.ts b/test/autoplan-phase-order.test.ts index 31fb03db0..9ab514dde 100644 --- a/test/autoplan-phase-order.test.ts +++ b/test/autoplan-phase-order.test.ts @@ -98,7 +98,10 @@ describe('autoplan phase execution checkpoints', () => { expect(dispatch).toContain('run_in_background: false'); expect(dispatch).toContain('if its schema exposes it'); expect(dispatch).toContain('isAsync: true'); - expect(dispatch).toContain('Claude Code: end response; resume only on same-agent terminal notification'); + expect(dispatch).toContain('Claude Code: immediately end this response with'); + expect(dispatch).toContain('Do no more tool calls or review work until that ID'); + expect(dispatch).toContain('For completed native reviews, match INPUT phase/hash'); + expect(dispatch).toContain('retry the full payload once, then use failure policy'); expect(dispatch).toContain('Other hosts: await that ID'); expect(dispatch).toContain("Then outside → this phase's review ONLY"); expect(dispatch).toContain('No inline substitute; apply failure policy'); @@ -114,9 +117,13 @@ describe('autoplan phase execution checkpoints', () => { expect(contract).toContain('Keep ONE phase active'); expect(contract).toContain('Never draft future-phase reviews or outputs'); expect(contract).toContain('After compaction, reload current phase instructions/skill/sections; reconcile disk progress before resuming'); - expect(contract).toContain('load its phase instructions and full skill/sections'); - expect(contract).toContain('consume\nnative and enabled outside results; do its full primary review; persist outputs'); - expect(contract).toContain('emit an actual assistant completion; only then load the next phase'); + expect(contract).toContain('Load its phase instructions and full skill/sections'); + expect(contract).toContain('Create the fresh snapshot and dispatch its generated nativePrompt unchanged'); + expect(contract).toContain('Consume native completion, then enabled outside results; only then do the full primary review'); + expect(contract).toContain("Persist outputs/amendments and run the phase's implementation check/readback"); + expect(contract).toContain('Emit actual completion; only then load the next required phase'); + expect(contract).toContain('A missing gate means the current phase remains open'); + expect(contract).toContain('INPUT correlates reported input; it is not independent proof of uptake or review quality'); expect(contract).toContain('Pending is not unavailable'); expect(contract).toContain('Time/context pressure or your own review never permits\nskipping native passes or required sections'); expect(contract).toContain('Never read raw agent transcripts'); @@ -131,8 +138,11 @@ describe('autoplan phase execution checkpoints', () => { expect(barrier).toBeLessThan(announcement); const checkpoint = section.slice(barrier, announcement); expect(checkpoint).toContain('Require full load ranges'); - expect(checkpoint).toContain('successful output writes'); + expect(checkpoint).toContain('successful writes'); expect(checkpoint).toContain('terminal reviewers'); + expect(checkpoint).toContain('matched INPUT for completed native reviews'); + expect(checkpoint).toContain('(unavailable/disabled allowed)'); + expect(checkpoint).toContain('this check result'); expect(checkpoint).toContain('actual assistant message'); expect(checkpoint).toContain('Edit accepted changes into `Implementation plan`'); expect(checkpoint).toContain('Verify returned text against decisions'); @@ -166,6 +176,16 @@ describe('autoplan current implementation-plan identity', () => { expect(intake).not.toContain('Bind ``'); }); + test('DX scope consumes the deterministic full-input result and permits only enabling overrides', () => { + const intake = read('autoplan/SKILL.md.tmpl').split('### Step 2: Read context')[1]?.split('### Step 3:')[0] ?? ''; + expect(intake).toContain('scope ""'); + expect(intake).toContain('Use returned `dxRequired`'); + expect(intake).toContain('threshold is 2+ term matches'); + expect(intake).toContain('`--developer-tool` or `--agent-primary`'); + expect(intake).toContain('no context label can negate a positive result'); + expect(intake).toContain('false and neither semantic trigger applies'); + }); + test('every native and outside call site binds the fresh snapshot, retaining requested outside consensus', () => { for (const phase of ['ceo', 'design', 'dx', 'eng']) { const section = read(`autoplan/sections/${phase}-phase.md.tmpl`); @@ -179,17 +199,18 @@ describe('autoplan current implementation-plan identity', () => { expect(preparation).toContain(`create ${phase} "" ""`); expect(preparation).toContain('`snapshotPath` as `<' + phase.toUpperCase() + '_INPUT>` for both voices'); expect(preparation).toContain('excludes `Review record`'); - expect(section).toContain(`Read the plan file at <${phase.toUpperCase()}_INPUT>`); + expect(section).toContain('Send `nativePrompt` verbatim'); + expect(section).toContain('Read `nativePromptPath` to EOF'); expect(section).toContain(`Outside prompt: inline the full contents of <${phase.toUpperCase()}_INPUT>`); expect(section).toContain(`check ${phase} "" "<${phase.toUpperCase()}_INPUT>" changed`); expect(section).toContain('Use `unchanged` only if no implementation changes were accepted'); expect(section).toContain('Report/task edits do not count'); expect(section).not.toContain(''); expect(section).not.toContain(''); - expect(section).toContain('You have NOT seen any prior review'); + expect(section).toContain('no summaries or prior reviews'); } const eng = read('autoplan/sections/eng-phase.md.tmpl'); - expect(eng).toContain('NO prior-phase context — subagent must be truly independent'); + expect(eng).toContain('no summaries or prior reviews'); expect(eng).toContain('DX: { for (const dir of owned.splice(0)) rmSync(dir, { recursive: true, force: true }); }); describe('Autoplan phase snapshot continuity', () => { + test('generated native dispatch carries every snapshot byte instead of the observed abbreviated input', () => { + const f = fixture(); + for (const phase of ['ceo', 'design', 'dx', 'eng']) { + const result = cli('create', phase, f.active, f.restore); + expect(result.status, result.stderr).toBe(0); + const generated = JSON.parse(result.stdout); + const implementation = readFileSync(generated.snapshotPath, 'utf8'); + expect(generated.nativePrompt).toBeString(); + expect(generated.nativePrompt.endsWith(implementation)).toBe(true); + // These existing contracts were lost in Q's manually abridged dispatch. + expect(generated.nativePrompt).toContain('single-role member workspace'); + expect(generated.nativePrompt).toContain('Mutations already require CSRF tokens'); + expect(generated.nativePrompt).toContain('You have NOT seen any prior review'); + expect(generated.nativePrompt).toContain(`Input path: ${JSON.stringify(generated.snapshotPath)}`); + expect(generated.nativePrompt).toContain(`INPUT: ${phase} ${generated.sha256}`); + expect(generated.nativePrompt).not.toContain('CEO pending'); + expect(generated.nativePrompt).not.toContain('## Review record'); + expect(readFileSync(generated.nativePromptPath, 'utf8')).toBe(generated.nativePrompt); + expect(generated.nativePromptSha256).toBe(createHash('sha256').update(generated.nativePrompt).digest('hex')); + expect(statSync(generated.nativePromptPath).mode & 0o222).toBe(0); + const metadata = JSON.parse(readFileSync(join(generated.nativePromptPath, '..', 'snapshot.json'), 'utf8')); + expect(metadata.nativePromptSha256).toBe(generated.nativePromptSha256); + expect(readFileSync(f.active, 'utf8')).toBe(f.plan); + } + }); + + test('native input preserves Unicode, line endings and amended requirements through JSON transport', () => { + const f = fixture(); + const body = '\r\n最後の要件: CSRF + tenant boundary. 🧪\r\n is literal plan data.\r\n'; + writeFileSync(f.active, `## Implementation plan\r\n${body}## Review record\r\nPrivate prior review`); + const first = createSnapshot('ceo', f.active, f.restore); + const payload = JSON.parse(JSON.stringify(first)); + expect(payload.nativePrompt).toBeString(); + expect(payload.nativePrompt.endsWith(body)).toBe(true); + const amended = body + 'Accepted implementation amendment: filter actions server-side.\r\n'; + writeFileSync(f.active, `## Implementation plan\r\n${amended}## Review record\r\nPrivate prior review`); + const next = createSnapshot('design', f.active, f.restore); + expect(next.nativePrompt.endsWith(amended)).toBe(true); + expect(next.nativePrompt).not.toContain('Private prior review'); + expect(readFileSync(first.nativePromptPath, 'utf8')).toBe(first.nativePrompt); + expect(next.nativePromptPath).not.toBe(first.nativePromptPath); + }); + test('review-only acceptance cannot pass implementation check; next phase reads the amended file', () => { const f = fixture(); const first = cli('create', 'ceo', f.active, f.restore); @@ -133,3 +177,80 @@ describe('installed snapshot helper in fresh shells', () => { } }); }); + +describe('deterministic Autoplan DX scope', () => { + function detectDxScope(activePlan: string) { + const result = cli('scope', activePlan); + expect(result.status, result.stderr).toBe(0); + return JSON.parse(result.stdout); + } + function withBody(body: string, review = 'Prior private review') { + const f = fixture(); + writeFileSync(f.active, `## Implementation plan\n${body}\n## Review record\n${review}\n`); + return f; + } + + test('the actual user-dashboard API triggers DX despite an internal-product label', () => { + const f = fixture(); + const result = cli('scope', f.active); + expect(result.status, result.stderr).toBe(0); + const scope = JSON.parse(result.stdout); + expect(scope.dxRequired).toBe(true); + expect(scope.matchCount).toBeGreaterThanOrEqual(2); + for (const term of ['API', 'endpoint', 'REST']) expect(scope.matches.some((m: { term: string }) => m.term === term)).toBe(true); + const snapshot = createSnapshot('ceo', f.active, f.restore); + expect(scope.sha256).toBe(snapshot.sha256); + expect(snapshot.dxScope.dxRequiredByTerms).toBe(true); + expect(snapshot.dxScope.matches).toEqual(scope.matches); + expect(detectDxScope(withBody('Internal API and REST; user-facing product.').active).dxRequired).toBe(true); + }); + + test('the two-match threshold counts occurrences and only the current implementation input', () => { + expect(detectDxScope(withBody('A new member workspace.').active).dxRequired).toBe(false); + const one = detectDxScope(withBody('One API.', 'API endpoint REST SDK').active); + expect(one.matchCount).toBe(1); + expect(one.dxRequired).toBe(false); + const repeated = detectDxScope(withBody('API. Another api.').active); + expect(repeated.matchCount).toBe(2); + expect(repeated.dxRequired).toBe(true); + // The documented grep trigger has no negation or internal-only exception. + expect(detectDxScope(withBody('No API or endpoint changes.').active).dxRequired).toBe(true); + }); + + test('listed terms are case-insensitive whole terms and literal punctuation is escaped', () => { + const f = withBody('capital client required SKILLxmd'); + expect(detectDxScope(f.active).matchCount).toBe(0); + const phrases = detectDxScope(withBody('skill.md and CLAUDE CODE').active); + expect(phrases.matchCount).toBe(2); + expect(phrases.dxRequired).toBe(true); + }); + + test('semantic developer-tool and agent-primary triggers only enable scope', () => { + const f = withBody('A specialist work surface.'); + for (const flag of ['--developer-tool', '--agent-primary']) { + const result = cli('scope', f.active, flag); + expect(result.status, result.stderr).toBe(0); + const scope = JSON.parse(result.stdout); + expect(scope.matchCount).toBe(0); + expect(scope.dxRequired).toBe(true); + } + expect(JSON.parse(cli('scope', f.active, '--developer-tool', '--agent-primary').stdout).dxRequired).toBe(true); + const termOnly = createSnapshot('ceo', f.active, f.restore).dxScope; + expect(termOnly.dxRequiredByTerms).toBe(false); + expect('dxRequired' in termOnly).toBe(false); // No term-only false can cancel a semantic trigger. + expect(cli('scope', f.active, '--skip-dx').status).toBe(1); + expect(cli('scope', f.active, '--agent-primary', '--agent-primary').status).toBe(1); + expect(cli('scope', f.active, '--developer-tool=false').status).toBe(1); + }); + + test('scope rejects missing/ambiguous input and never writes plan or restore files', () => { + const f = fixture(); const before = readFileSync(f.active, 'utf8'); + const listed = readdirSync(f.dir); + expect(cli('scope', f.active).status).toBe(0); + expect(readFileSync(f.active, 'utf8')).toBe(before); + expect(readdirSync(f.dir)).toEqual(listed); + writeFileSync(f.active, 'No implementation boundaries'); + expect(cli('scope', f.active).status).toBe(1); + expect(cli('scope').status).toBe(1); + }); +});