gsd-remix 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +939 -0
- package/README.zh-CN.md +876 -0
- package/agents/gsd-advisor-researcher.md +127 -0
- package/agents/gsd-ai-researcher.md +133 -0
- package/agents/gsd-assumptions-analyzer.md +105 -0
- package/agents/gsd-code-fixer.md +517 -0
- package/agents/gsd-code-reviewer.md +371 -0
- package/agents/gsd-codebase-mapper.md +781 -0
- package/agents/gsd-debug-session-manager.md +314 -0
- package/agents/gsd-debugger.md +1452 -0
- package/agents/gsd-doc-classifier.md +168 -0
- package/agents/gsd-doc-synthesizer.md +204 -0
- package/agents/gsd-doc-verifier.md +217 -0
- package/agents/gsd-doc-writer.md +615 -0
- package/agents/gsd-domain-researcher.md +153 -0
- package/agents/gsd-eval-auditor.md +191 -0
- package/agents/gsd-eval-planner.md +154 -0
- package/agents/gsd-executor.md +603 -0
- package/agents/gsd-framework-selector.md +160 -0
- package/agents/gsd-integration-checker.md +470 -0
- package/agents/gsd-intel-updater.md +334 -0
- package/agents/gsd-nyquist-auditor.md +203 -0
- package/agents/gsd-pattern-mapper.md +335 -0
- package/agents/gsd-phase-researcher.md +841 -0
- package/agents/gsd-plan-checker.md +978 -0
- package/agents/gsd-planner.md +1251 -0
- package/agents/gsd-project-researcher.md +677 -0
- package/agents/gsd-research-synthesizer.md +247 -0
- package/agents/gsd-roadmapper.md +688 -0
- package/agents/gsd-security-auditor.md +155 -0
- package/agents/gsd-ui-auditor.md +495 -0
- package/agents/gsd-ui-checker.md +309 -0
- package/agents/gsd-ui-researcher.md +380 -0
- package/agents/gsd-user-profiler.md +171 -0
- package/agents/gsd-verifier.md +830 -0
- package/bin/install.js +7062 -0
- package/commands/gsd/add-backlog.md +79 -0
- package/commands/gsd/add-phase.md +43 -0
- package/commands/gsd/add-tests.md +41 -0
- package/commands/gsd/add-todo.md +47 -0
- package/commands/gsd/ai-integration-phase.md +36 -0
- package/commands/gsd/analyze-dependencies.md +34 -0
- package/commands/gsd/audit-fix.md +33 -0
- package/commands/gsd/audit-milestone.md +36 -0
- package/commands/gsd/audit-uat.md +24 -0
- package/commands/gsd/autonomous.md +46 -0
- package/commands/gsd/check-todos.md +45 -0
- package/commands/gsd/cleanup.md +23 -0
- package/commands/gsd/code-review-fix.md +52 -0
- package/commands/gsd/code-review.md +55 -0
- package/commands/gsd/complete-milestone.md +136 -0
- package/commands/gsd/debug.md +263 -0
- package/commands/gsd/discuss-phase.md +69 -0
- package/commands/gsd/do.md +30 -0
- package/commands/gsd/docs-update.md +48 -0
- package/commands/gsd/eval-review.md +32 -0
- package/commands/gsd/execute-phase.md +63 -0
- package/commands/gsd/explore.md +27 -0
- package/commands/gsd/extract_learnings.md +22 -0
- package/commands/gsd/fast.md +30 -0
- package/commands/gsd/forensics.md +56 -0
- package/commands/gsd/from-gsd2.md +47 -0
- package/commands/gsd/graphify.md +201 -0
- package/commands/gsd/health.md +22 -0
- package/commands/gsd/help.md +24 -0
- package/commands/gsd/import.md +37 -0
- package/commands/gsd/inbox.md +38 -0
- package/commands/gsd/ingest-docs.md +42 -0
- package/commands/gsd/insert-phase.md +32 -0
- package/commands/gsd/intel.md +179 -0
- package/commands/gsd/join-discord.md +19 -0
- package/commands/gsd/list-phase-assumptions.md +46 -0
- package/commands/gsd/list-workspaces.md +19 -0
- package/commands/gsd/manager.md +40 -0
- package/commands/gsd/map-codebase.md +71 -0
- package/commands/gsd/milestone-summary.md +51 -0
- package/commands/gsd/new-milestone.md +44 -0
- package/commands/gsd/new-project.md +46 -0
- package/commands/gsd/new-workspace.md +44 -0
- package/commands/gsd/next.md +28 -0
- package/commands/gsd/note.md +34 -0
- package/commands/gsd/pause-work.md +38 -0
- package/commands/gsd/plan-milestone-gaps.md +34 -0
- package/commands/gsd/plan-phase.md +52 -0
- package/commands/gsd/plan-review-convergence.md +52 -0
- package/commands/gsd/plant-seed.md +28 -0
- package/commands/gsd/pr-branch.md +25 -0
- package/commands/gsd/profile-user.md +46 -0
- package/commands/gsd/progress.md +25 -0
- package/commands/gsd/quick.md +173 -0
- package/commands/gsd/reapply-patches.md +331 -0
- package/commands/gsd/remove-phase.md +31 -0
- package/commands/gsd/remove-workspace.md +26 -0
- package/commands/gsd/research-phase.md +195 -0
- package/commands/gsd/resume-work.md +40 -0
- package/commands/gsd/review-backlog.md +62 -0
- package/commands/gsd/review.md +40 -0
- package/commands/gsd/scan.md +26 -0
- package/commands/gsd/secure-phase.md +35 -0
- package/commands/gsd/session-report.md +19 -0
- package/commands/gsd/set-profile.md +12 -0
- package/commands/gsd/settings.md +36 -0
- package/commands/gsd/ship.md +23 -0
- package/commands/gsd/sketch-wrap-up.md +31 -0
- package/commands/gsd/sketch.md +49 -0
- package/commands/gsd/spec-phase.md +62 -0
- package/commands/gsd/spike-wrap-up.md +31 -0
- package/commands/gsd/spike.md +46 -0
- package/commands/gsd/stats.md +18 -0
- package/commands/gsd/sync-skills.md +19 -0
- package/commands/gsd/thread.md +227 -0
- package/commands/gsd/ui-phase.md +34 -0
- package/commands/gsd/ui-review.md +32 -0
- package/commands/gsd/ultraplan-phase.md +33 -0
- package/commands/gsd/undo.md +34 -0
- package/commands/gsd/update.md +37 -0
- package/commands/gsd/validate-phase.md +35 -0
- package/commands/gsd/verify-work.md +38 -0
- package/commands/gsd/workstreams.md +69 -0
- package/get-shit-done/bin/gsd-tools.cjs +1263 -0
- package/get-shit-done/bin/lib/artifacts.cjs +52 -0
- package/get-shit-done/bin/lib/audit.cjs +757 -0
- package/get-shit-done/bin/lib/commands.cjs +1023 -0
- package/get-shit-done/bin/lib/config-schema.cjs +79 -0
- package/get-shit-done/bin/lib/config.cjs +463 -0
- package/get-shit-done/bin/lib/core.cjs +1794 -0
- package/get-shit-done/bin/lib/docs.cjs +267 -0
- package/get-shit-done/bin/lib/frontmatter.cjs +379 -0
- package/get-shit-done/bin/lib/graphify.cjs +494 -0
- package/get-shit-done/bin/lib/gsd2-import.cjs +511 -0
- package/get-shit-done/bin/lib/init.cjs +1878 -0
- package/get-shit-done/bin/lib/intel.cjs +639 -0
- package/get-shit-done/bin/lib/learnings.cjs +378 -0
- package/get-shit-done/bin/lib/milestone.cjs +283 -0
- package/get-shit-done/bin/lib/model-profiles.cjs +71 -0
- package/get-shit-done/bin/lib/phase.cjs +1058 -0
- package/get-shit-done/bin/lib/profile-output.cjs +1080 -0
- package/get-shit-done/bin/lib/profile-pipeline.cjs +539 -0
- package/get-shit-done/bin/lib/roadmap.cjs +523 -0
- package/get-shit-done/bin/lib/schema-detect.cjs +238 -0
- package/get-shit-done/bin/lib/security.cjs +504 -0
- package/get-shit-done/bin/lib/state.cjs +1649 -0
- package/get-shit-done/bin/lib/template.cjs +226 -0
- package/get-shit-done/bin/lib/uat.cjs +288 -0
- package/get-shit-done/bin/lib/verify.cjs +1184 -0
- package/get-shit-done/bin/lib/workstream.cjs +495 -0
- package/get-shit-done/bin/repair-sdk.cjs +177 -0
- package/get-shit-done/contexts/dev.md +21 -0
- package/get-shit-done/contexts/research.md +22 -0
- package/get-shit-done/contexts/review.md +22 -0
- package/get-shit-done/references/agent-contracts.md +79 -0
- package/get-shit-done/references/ai-evals.md +156 -0
- package/get-shit-done/references/ai-frameworks.md +186 -0
- package/get-shit-done/references/artifact-types.md +131 -0
- package/get-shit-done/references/autonomous-smart-discuss.md +277 -0
- package/get-shit-done/references/checkpoints.md +808 -0
- package/get-shit-done/references/common-bug-patterns.md +114 -0
- package/get-shit-done/references/context-budget.md +49 -0
- package/get-shit-done/references/continuation-format.md +253 -0
- package/get-shit-done/references/debugger-philosophy.md +76 -0
- package/get-shit-done/references/decimal-phase-calculation.md +64 -0
- package/get-shit-done/references/doc-conflict-engine.md +91 -0
- package/get-shit-done/references/domain-probes.md +125 -0
- package/get-shit-done/references/executor-examples.md +110 -0
- package/get-shit-done/references/few-shot-examples/plan-checker.md +73 -0
- package/get-shit-done/references/few-shot-examples/verifier.md +109 -0
- package/get-shit-done/references/gate-prompts.md +100 -0
- package/get-shit-done/references/gates.md +70 -0
- package/get-shit-done/references/git-integration.md +295 -0
- package/get-shit-done/references/git-planning-commit.md +40 -0
- package/get-shit-done/references/ios-scaffold.md +123 -0
- package/get-shit-done/references/mandatory-initial-read.md +2 -0
- package/get-shit-done/references/model-profile-resolution.md +38 -0
- package/get-shit-done/references/model-profiles.md +145 -0
- package/get-shit-done/references/phase-argument-parsing.md +61 -0
- package/get-shit-done/references/planner-antipatterns.md +89 -0
- package/get-shit-done/references/planner-gap-closure.md +62 -0
- package/get-shit-done/references/planner-reviews.md +39 -0
- package/get-shit-done/references/planner-revision.md +87 -0
- package/get-shit-done/references/planner-source-audit.md +73 -0
- package/get-shit-done/references/planning-config.md +460 -0
- package/get-shit-done/references/project-skills-discovery.md +19 -0
- package/get-shit-done/references/questioning.md +162 -0
- package/get-shit-done/references/revision-loop.md +97 -0
- package/get-shit-done/references/sketch-interactivity.md +41 -0
- package/get-shit-done/references/sketch-theme-system.md +94 -0
- package/get-shit-done/references/sketch-tooling.md +45 -0
- package/get-shit-done/references/sketch-variant-patterns.md +81 -0
- package/get-shit-done/references/tdd.md +330 -0
- package/get-shit-done/references/thinking-models-debug.md +44 -0
- package/get-shit-done/references/thinking-models-execution.md +50 -0
- package/get-shit-done/references/thinking-models-planning.md +62 -0
- package/get-shit-done/references/thinking-models-research.md +50 -0
- package/get-shit-done/references/thinking-models-verification.md +55 -0
- package/get-shit-done/references/thinking-partner.md +96 -0
- package/get-shit-done/references/ui-brand.md +160 -0
- package/get-shit-done/references/universal-anti-patterns.md +63 -0
- package/get-shit-done/references/user-profiling.md +681 -0
- package/get-shit-done/references/verification-overrides.md +227 -0
- package/get-shit-done/references/verification-patterns.md +612 -0
- package/get-shit-done/references/workstream-flag.md +111 -0
- package/get-shit-done/templates/AI-SPEC.md +246 -0
- package/get-shit-done/templates/DEBUG.md +169 -0
- package/get-shit-done/templates/README.md +76 -0
- package/get-shit-done/templates/SECURITY.md +61 -0
- package/get-shit-done/templates/UAT.md +265 -0
- package/get-shit-done/templates/UI-SPEC.md +100 -0
- package/get-shit-done/templates/VALIDATION.md +76 -0
- package/get-shit-done/templates/claude-md.md +145 -0
- package/get-shit-done/templates/codebase/architecture.md +255 -0
- package/get-shit-done/templates/codebase/concerns.md +310 -0
- package/get-shit-done/templates/codebase/conventions.md +307 -0
- package/get-shit-done/templates/codebase/integrations.md +280 -0
- package/get-shit-done/templates/codebase/stack.md +186 -0
- package/get-shit-done/templates/codebase/structure.md +285 -0
- package/get-shit-done/templates/codebase/testing.md +480 -0
- package/get-shit-done/templates/config.json +56 -0
- package/get-shit-done/templates/context.md +352 -0
- package/get-shit-done/templates/continue-here.md +78 -0
- package/get-shit-done/templates/copilot-instructions.md +7 -0
- package/get-shit-done/templates/debug-subagent-prompt.md +91 -0
- package/get-shit-done/templates/dev-preferences.md +21 -0
- package/get-shit-done/templates/discovery.md +146 -0
- package/get-shit-done/templates/discussion-log.md +63 -0
- package/get-shit-done/templates/milestone-archive.md +123 -0
- package/get-shit-done/templates/milestone.md +115 -0
- package/get-shit-done/templates/phase-prompt.md +610 -0
- package/get-shit-done/templates/planner-subagent-prompt.md +117 -0
- package/get-shit-done/templates/project.md +186 -0
- package/get-shit-done/templates/requirements.md +231 -0
- package/get-shit-done/templates/research-project/ARCHITECTURE.md +204 -0
- package/get-shit-done/templates/research-project/FEATURES.md +147 -0
- package/get-shit-done/templates/research-project/PITFALLS.md +200 -0
- package/get-shit-done/templates/research-project/STACK.md +120 -0
- package/get-shit-done/templates/research-project/SUMMARY.md +170 -0
- package/get-shit-done/templates/research.md +592 -0
- package/get-shit-done/templates/retrospective.md +54 -0
- package/get-shit-done/templates/roadmap.md +202 -0
- package/get-shit-done/templates/spec.md +307 -0
- package/get-shit-done/templates/state.md +184 -0
- package/get-shit-done/templates/summary-complex.md +59 -0
- package/get-shit-done/templates/summary-minimal.md +41 -0
- package/get-shit-done/templates/summary-standard.md +48 -0
- package/get-shit-done/templates/summary.md +248 -0
- package/get-shit-done/templates/user-profile.md +146 -0
- package/get-shit-done/templates/user-setup.md +311 -0
- package/get-shit-done/templates/verification-report.md +322 -0
- package/get-shit-done/workflows/add-phase.md +112 -0
- package/get-shit-done/workflows/add-tests.md +354 -0
- package/get-shit-done/workflows/add-todo.md +160 -0
- package/get-shit-done/workflows/ai-integration-phase.md +284 -0
- package/get-shit-done/workflows/analyze-dependencies.md +96 -0
- package/get-shit-done/workflows/audit-fix.md +175 -0
- package/get-shit-done/workflows/audit-milestone.md +340 -0
- package/get-shit-done/workflows/audit-uat.md +109 -0
- package/get-shit-done/workflows/autonomous.md +789 -0
- package/get-shit-done/workflows/check-todos.md +179 -0
- package/get-shit-done/workflows/cleanup.md +154 -0
- package/get-shit-done/workflows/code-review-fix.md +497 -0
- package/get-shit-done/workflows/code-review.md +515 -0
- package/get-shit-done/workflows/complete-milestone.md +847 -0
- package/get-shit-done/workflows/diagnose-issues.md +238 -0
- package/get-shit-done/workflows/discovery-phase.md +291 -0
- package/get-shit-done/workflows/discuss-phase-assumptions.md +670 -0
- package/get-shit-done/workflows/discuss-phase-power.md +308 -0
- package/get-shit-done/workflows/discuss-phase.md +1378 -0
- package/get-shit-done/workflows/do.md +110 -0
- package/get-shit-done/workflows/docs-update.md +1155 -0
- package/get-shit-done/workflows/eval-review.md +155 -0
- package/get-shit-done/workflows/execute-phase.md +1677 -0
- package/get-shit-done/workflows/execute-plan.md +533 -0
- package/get-shit-done/workflows/explore.md +141 -0
- package/get-shit-done/workflows/extract_learnings.md +242 -0
- package/get-shit-done/workflows/fast.md +105 -0
- package/get-shit-done/workflows/forensics.md +265 -0
- package/get-shit-done/workflows/graduation.md +195 -0
- package/get-shit-done/workflows/health.md +314 -0
- package/get-shit-done/workflows/help.md +667 -0
- package/get-shit-done/workflows/import.md +246 -0
- package/get-shit-done/workflows/inbox.md +387 -0
- package/get-shit-done/workflows/ingest-docs.md +328 -0
- package/get-shit-done/workflows/insert-phase.md +130 -0
- package/get-shit-done/workflows/list-phase-assumptions.md +178 -0
- package/get-shit-done/workflows/list-workspaces.md +56 -0
- package/get-shit-done/workflows/manager.md +365 -0
- package/get-shit-done/workflows/map-codebase.md +393 -0
- package/get-shit-done/workflows/milestone-summary.md +223 -0
- package/get-shit-done/workflows/new-milestone.md +611 -0
- package/get-shit-done/workflows/new-project.md +1391 -0
- package/get-shit-done/workflows/new-workspace.md +239 -0
- package/get-shit-done/workflows/next.md +220 -0
- package/get-shit-done/workflows/node-repair.md +92 -0
- package/get-shit-done/workflows/note.md +158 -0
- package/get-shit-done/workflows/pause-work.md +243 -0
- package/get-shit-done/workflows/plan-milestone-gaps.md +273 -0
- package/get-shit-done/workflows/plan-phase.md +1349 -0
- package/get-shit-done/workflows/plan-review-convergence.md +254 -0
- package/get-shit-done/workflows/plant-seed.md +172 -0
- package/get-shit-done/workflows/pr-branch.md +157 -0
- package/get-shit-done/workflows/profile-user.md +452 -0
- package/get-shit-done/workflows/progress.md +619 -0
- package/get-shit-done/workflows/quick.md +970 -0
- package/get-shit-done/workflows/remove-phase.md +155 -0
- package/get-shit-done/workflows/remove-workspace.md +92 -0
- package/get-shit-done/workflows/research-phase.md +89 -0
- package/get-shit-done/workflows/resume-project.md +326 -0
- package/get-shit-done/workflows/review.md +344 -0
- package/get-shit-done/workflows/scan.md +102 -0
- package/get-shit-done/workflows/secure-phase.md +166 -0
- package/get-shit-done/workflows/session-report.md +146 -0
- package/get-shit-done/workflows/settings.md +319 -0
- package/get-shit-done/workflows/ship.md +302 -0
- package/get-shit-done/workflows/sketch-wrap-up.md +283 -0
- package/get-shit-done/workflows/sketch.md +286 -0
- package/get-shit-done/workflows/spec-phase.md +262 -0
- package/get-shit-done/workflows/spike-wrap-up.md +281 -0
- package/get-shit-done/workflows/spike.md +362 -0
- package/get-shit-done/workflows/stats.md +60 -0
- package/get-shit-done/workflows/sync-skills.md +182 -0
- package/get-shit-done/workflows/transition.md +693 -0
- package/get-shit-done/workflows/ui-phase.md +323 -0
- package/get-shit-done/workflows/ui-review.md +190 -0
- package/get-shit-done/workflows/ultraplan-phase.md +189 -0
- package/get-shit-done/workflows/undo.md +314 -0
- package/get-shit-done/workflows/update.md +587 -0
- package/get-shit-done/workflows/validate-phase.md +176 -0
- package/get-shit-done/workflows/verify-phase.md +465 -0
- package/get-shit-done/workflows/verify-work.md +740 -0
- package/hooks/dist/gsd-check-update-worker.js +108 -0
- package/hooks/dist/gsd-check-update.js +64 -0
- package/hooks/dist/gsd-context-monitor.js +192 -0
- package/hooks/dist/gsd-phase-boundary.sh +28 -0
- package/hooks/dist/gsd-prompt-guard.js +97 -0
- package/hooks/dist/gsd-read-guard.js +82 -0
- package/hooks/dist/gsd-read-injection-scanner.js +152 -0
- package/hooks/dist/gsd-session-state.sh +34 -0
- package/hooks/dist/gsd-statusline.js +293 -0
- package/hooks/dist/gsd-validate-commit.sh +48 -0
- package/hooks/dist/gsd-workflow-guard.js +94 -0
- package/hooks/gsd-check-update-worker.js +108 -0
- package/hooks/gsd-check-update.js +64 -0
- package/hooks/gsd-context-monitor.js +192 -0
- package/hooks/gsd-phase-boundary.sh +28 -0
- package/hooks/gsd-prompt-guard.js +97 -0
- package/hooks/gsd-read-guard.js +82 -0
- package/hooks/gsd-read-injection-scanner.js +152 -0
- package/hooks/gsd-session-state.sh +34 -0
- package/hooks/gsd-statusline.js +293 -0
- package/hooks/gsd-validate-commit.sh +48 -0
- package/hooks/gsd-workflow-guard.js +94 -0
- package/package.json +59 -0
- package/scripts/base64-scan.sh +262 -0
- package/scripts/build-hooks.js +95 -0
- package/scripts/gen-inventory-manifest.cjs +109 -0
- package/scripts/prompt-injection-scan.sh +201 -0
- package/scripts/run-tests.cjs +33 -0
- package/scripts/secret-scan.sh +227 -0
- package/sdk/package-lock.json +1998 -0
- package/sdk/package.json +52 -0
- package/sdk/prompts/agents/gsd-executor.md +110 -0
- package/sdk/prompts/agents/gsd-phase-researcher.md +158 -0
- package/sdk/prompts/agents/gsd-plan-checker.md +160 -0
- package/sdk/prompts/agents/gsd-planner.md +214 -0
- package/sdk/prompts/agents/gsd-project-researcher.md +323 -0
- package/sdk/prompts/agents/gsd-research-synthesizer.md +237 -0
- package/sdk/prompts/agents/gsd-roadmapper.md +670 -0
- package/sdk/prompts/agents/gsd-verifier.md +159 -0
- package/sdk/prompts/templates/project.md +186 -0
- package/sdk/prompts/templates/requirements.md +231 -0
- package/sdk/prompts/templates/research-project/ARCHITECTURE.md +204 -0
- package/sdk/prompts/templates/research-project/FEATURES.md +147 -0
- package/sdk/prompts/templates/research-project/PITFALLS.md +200 -0
- package/sdk/prompts/templates/research-project/STACK.md +120 -0
- package/sdk/prompts/templates/research-project/SUMMARY.md +170 -0
- package/sdk/prompts/templates/roadmap.md +202 -0
- package/sdk/prompts/templates/state.md +175 -0
- package/sdk/prompts/workflows/discuss-phase.md +126 -0
- package/sdk/prompts/workflows/execute-plan.md +106 -0
- package/sdk/prompts/workflows/plan-phase.md +84 -0
- package/sdk/prompts/workflows/research-phase.md +45 -0
- package/sdk/prompts/workflows/verify-phase.md +142 -0
- package/sdk/src/assembled-prompts.test.ts +349 -0
- package/sdk/src/cli-transport.test.ts +388 -0
- package/sdk/src/cli-transport.ts +130 -0
- package/sdk/src/cli.test.ts +383 -0
- package/sdk/src/cli.ts +670 -0
- package/sdk/src/config.test.ts +168 -0
- package/sdk/src/config.ts +177 -0
- package/sdk/src/context-engine.test.ts +295 -0
- package/sdk/src/context-engine.ts +170 -0
- package/sdk/src/context-truncation.test.ts +163 -0
- package/sdk/src/context-truncation.ts +233 -0
- package/sdk/src/e2e.integration.test.ts +178 -0
- package/sdk/src/errors.ts +72 -0
- package/sdk/src/event-stream.test.ts +661 -0
- package/sdk/src/event-stream.ts +441 -0
- package/sdk/src/failure-memory.test.ts +457 -0
- package/sdk/src/failure-memory.ts +1324 -0
- package/sdk/src/golden/capture.ts +95 -0
- package/sdk/src/golden/fixtures/generate-slug.golden.json +1 -0
- package/sdk/src/golden/fixtures/profile-sample-sessions/demo-project/sample.jsonl +3 -0
- package/sdk/src/golden/fixtures/summary-extract-sample.md +26 -0
- package/sdk/src/golden/fixtures/uat-render-checkpoint-sample.md +15 -0
- package/sdk/src/golden/golden-integration-covered.ts +30 -0
- package/sdk/src/golden/golden-mutation-covered.ts +7 -0
- package/sdk/src/golden/golden-policy.test.ts +8 -0
- package/sdk/src/golden/golden-policy.ts +112 -0
- package/sdk/src/golden/golden.integration.test.ts +373 -0
- package/sdk/src/golden/init-golden-normalize.ts +15 -0
- package/sdk/src/golden/read-only-golden-rows.ts +77 -0
- package/sdk/src/golden/read-only-parity.integration.test.ts +125 -0
- package/sdk/src/golden/registry-canonical-commands.ts +31 -0
- package/sdk/src/gsd-tools.test.ts +409 -0
- package/sdk/src/gsd-tools.ts +595 -0
- package/sdk/src/headless-prompts.test.ts +159 -0
- package/sdk/src/index.ts +333 -0
- package/sdk/src/init-e2e.integration.test.ts +136 -0
- package/sdk/src/init-runner.test.ts +783 -0
- package/sdk/src/init-runner.ts +735 -0
- package/sdk/src/lifecycle-e2e.integration.test.ts +258 -0
- package/sdk/src/logger.test.ts +149 -0
- package/sdk/src/logger.ts +113 -0
- package/sdk/src/milestone-runner.test.ts +421 -0
- package/sdk/src/phase-prompt.test.ts +538 -0
- package/sdk/src/phase-prompt.ts +264 -0
- package/sdk/src/phase-runner-types.test.ts +421 -0
- package/sdk/src/phase-runner.integration.test.ts +377 -0
- package/sdk/src/phase-runner.test.ts +2333 -0
- package/sdk/src/phase-runner.ts +1203 -0
- package/sdk/src/plan-parser.test.ts +528 -0
- package/sdk/src/plan-parser.ts +427 -0
- package/sdk/src/prompt-builder.test.ts +306 -0
- package/sdk/src/prompt-builder.ts +193 -0
- package/sdk/src/prompt-sanitizer.test.ts +260 -0
- package/sdk/src/prompt-sanitizer.ts +71 -0
- package/sdk/src/query/QUERY-HANDLERS.md +317 -0
- package/sdk/src/query/audit-open.ts +722 -0
- package/sdk/src/query/check-auto-mode.test.ts +77 -0
- package/sdk/src/query/check-auto-mode.ts +50 -0
- package/sdk/src/query/check-completion.test.ts +113 -0
- package/sdk/src/query/check-completion.ts +182 -0
- package/sdk/src/query/check-gates.test.ts +103 -0
- package/sdk/src/query/check-gates.ts +112 -0
- package/sdk/src/query/check-ship-ready.test.ts +77 -0
- package/sdk/src/query/check-ship-ready.ts +103 -0
- package/sdk/src/query/check-verification-status.test.ts +143 -0
- package/sdk/src/query/check-verification-status.ts +160 -0
- package/sdk/src/query/commit.test.ts +202 -0
- package/sdk/src/query/commit.ts +301 -0
- package/sdk/src/query/config-gates.test.ts +89 -0
- package/sdk/src/query/config-gates.ts +69 -0
- package/sdk/src/query/config-mutation.test.ts +365 -0
- package/sdk/src/query/config-mutation.ts +497 -0
- package/sdk/src/query/config-query.test.ts +161 -0
- package/sdk/src/query/config-query.ts +190 -0
- package/sdk/src/query/context-history.test.ts +165 -0
- package/sdk/src/query/context-history.ts +467 -0
- package/sdk/src/query/decomposed-handlers.test.ts +365 -0
- package/sdk/src/query/detect-custom-files.ts +97 -0
- package/sdk/src/query/detect-phase-type.test.ts +105 -0
- package/sdk/src/query/detect-phase-type.ts +141 -0
- package/sdk/src/query/docs-init.ts +257 -0
- package/sdk/src/query/failure-capture.ts +58 -0
- package/sdk/src/query/frontmatter-array.test.ts +14 -0
- package/sdk/src/query/frontmatter-mutation.test.ts +259 -0
- package/sdk/src/query/frontmatter-mutation.ts +343 -0
- package/sdk/src/query/frontmatter.test.ts +281 -0
- package/sdk/src/query/frontmatter.ts +397 -0
- package/sdk/src/query/helpers.test.ts +426 -0
- package/sdk/src/query/helpers.ts +482 -0
- package/sdk/src/query/index.ts +586 -0
- package/sdk/src/query/init-complex.test.ts +232 -0
- package/sdk/src/query/init-complex.ts +578 -0
- package/sdk/src/query/init.test.ts +522 -0
- package/sdk/src/query/init.ts +1046 -0
- package/sdk/src/query/intel.test.ts +90 -0
- package/sdk/src/query/intel.ts +404 -0
- package/sdk/src/query/normalize-query-command.test.ts +50 -0
- package/sdk/src/query/normalize-query-command.ts +56 -0
- package/sdk/src/query/phase-lifecycle.test.ts +1126 -0
- package/sdk/src/query/phase-lifecycle.ts +1799 -0
- package/sdk/src/query/phase-list-queries.test.ts +88 -0
- package/sdk/src/query/phase-list-queries.ts +152 -0
- package/sdk/src/query/phase-ready.test.ts +65 -0
- package/sdk/src/query/phase-ready.ts +158 -0
- package/sdk/src/query/phase.test.ts +307 -0
- package/sdk/src/query/phase.ts +340 -0
- package/sdk/src/query/pipeline.test.ts +169 -0
- package/sdk/src/query/pipeline.ts +243 -0
- package/sdk/src/query/plan-execution-route.test.ts +166 -0
- package/sdk/src/query/plan-execution-route.ts +209 -0
- package/sdk/src/query/plan-task-structure.test.ts +65 -0
- package/sdk/src/query/plan-task-structure.ts +63 -0
- package/sdk/src/query/profile-extract-messages.ts +247 -0
- package/sdk/src/query/profile-output.ts +908 -0
- package/sdk/src/query/profile-questionnaire-data.ts +181 -0
- package/sdk/src/query/profile-sample.ts +184 -0
- package/sdk/src/query/profile-scan-sessions.ts +174 -0
- package/sdk/src/query/profile.test.ts +74 -0
- package/sdk/src/query/profile.ts +337 -0
- package/sdk/src/query/progress.test.ts +156 -0
- package/sdk/src/query/progress.ts +566 -0
- package/sdk/src/query/registry.test.ts +216 -0
- package/sdk/src/query/registry.ts +174 -0
- package/sdk/src/query/requirements-extract-from-plans.test.ts +58 -0
- package/sdk/src/query/requirements-extract-from-plans.ts +86 -0
- package/sdk/src/query/roadmap-update-plan-progress.ts +132 -0
- package/sdk/src/query/roadmap.test.ts +359 -0
- package/sdk/src/query/roadmap.ts +591 -0
- package/sdk/src/query/route-next-action.test.ts +61 -0
- package/sdk/src/query/route-next-action.ts +345 -0
- package/sdk/src/query/runtime-health.ts +7 -0
- package/sdk/src/query/schema-detect.ts +189 -0
- package/sdk/src/query/skill-manifest.ts +214 -0
- package/sdk/src/query/skills.test.ts +80 -0
- package/sdk/src/query/skills.ts +62 -0
- package/sdk/src/query/state-mutation.test.ts +450 -0
- package/sdk/src/query/state-mutation.ts +1444 -0
- package/sdk/src/query/state-project-load.ts +109 -0
- package/sdk/src/query/state.test.ts +347 -0
- package/sdk/src/query/state.ts +397 -0
- package/sdk/src/query/summary.test.ts +95 -0
- package/sdk/src/query/summary.ts +296 -0
- package/sdk/src/query/template.test.ts +180 -0
- package/sdk/src/query/template.ts +242 -0
- package/sdk/src/query/uat.test.ts +77 -0
- package/sdk/src/query/uat.ts +314 -0
- package/sdk/src/query/utils.test.ts +82 -0
- package/sdk/src/query/utils.ts +92 -0
- package/sdk/src/query/validate.test.ts +656 -0
- package/sdk/src/query/validate.ts +807 -0
- package/sdk/src/query/verify.test.ts +414 -0
- package/sdk/src/query/verify.ts +645 -0
- package/sdk/src/query/websearch.test.ts +31 -0
- package/sdk/src/query/websearch.ts +82 -0
- package/sdk/src/query/workspace.test.ts +119 -0
- package/sdk/src/query/workspace.ts +131 -0
- package/sdk/src/query/workstream.test.ts +51 -0
- package/sdk/src/query/workstream.ts +434 -0
- package/sdk/src/research-gate.test.ts +190 -0
- package/sdk/src/research-gate.ts +94 -0
- package/sdk/src/runtime-health.test.ts +176 -0
- package/sdk/src/runtime-health.ts +387 -0
- package/sdk/src/session-runner.test.ts +98 -0
- package/sdk/src/session-runner.ts +299 -0
- package/sdk/src/tool-scoping.test.ts +160 -0
- package/sdk/src/tool-scoping.ts +61 -0
- package/sdk/src/types.ts +917 -0
- package/sdk/src/workstream-utils.ts +33 -0
- package/sdk/src/ws-flag.test.ts +285 -0
- package/sdk/src/ws-transport.test.ts +161 -0
- package/sdk/src/ws-transport.ts +93 -0
- package/sdk/tsconfig.json +20 -0
|
@@ -0,0 +1,1203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase Runner — core state machine driving the full phase lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates: discuss → research → plan → execute → verify → advance
|
|
5
|
+
* with config-driven step skipping, human gate callbacks, event emission,
|
|
6
|
+
* and structured error handling per step.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
PhaseOpInfo,
|
|
11
|
+
PhaseStepResult,
|
|
12
|
+
PhaseRunnerResult,
|
|
13
|
+
HumanGateCallbacks,
|
|
14
|
+
PhaseRunnerOptions,
|
|
15
|
+
PlanResult,
|
|
16
|
+
SessionOptions,
|
|
17
|
+
ParsedPlan,
|
|
18
|
+
PhasePlanIndex,
|
|
19
|
+
PlanInfo,
|
|
20
|
+
} from './types.js';
|
|
21
|
+
import { PhaseStepType, PhaseType, GSDEventType } from './types.js';
|
|
22
|
+
import type { GSDConfig } from './config.js';
|
|
23
|
+
import type { GSDTools } from './gsd-tools.js';
|
|
24
|
+
import type { GSDEventStream } from './event-stream.js';
|
|
25
|
+
import type { PromptFactory } from './phase-prompt.js';
|
|
26
|
+
import type { ContextEngine } from './context-engine.js';
|
|
27
|
+
import type { GSDLogger } from './logger.js';
|
|
28
|
+
import { runPhaseStepSession, runPlanSession } from './session-runner.js';
|
|
29
|
+
import { readFile } from 'node:fs/promises';
|
|
30
|
+
import { join } from 'node:path';
|
|
31
|
+
import { checkResearchGate } from './research-gate.js';
|
|
32
|
+
import { recordPhaseArtifactFailureEvents, recordPlanResultFailure } from './failure-memory.js';
|
|
33
|
+
|
|
34
|
+
// ─── Error type ──────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
export class PhaseRunnerError extends Error {
|
|
37
|
+
constructor(
|
|
38
|
+
message: string,
|
|
39
|
+
public readonly phaseNumber: string,
|
|
40
|
+
public readonly step: PhaseStepType,
|
|
41
|
+
public readonly cause?: Error,
|
|
42
|
+
) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = 'PhaseRunnerError';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Verification result enum ────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
export type VerificationOutcome = 'passed' | 'human_needed' | 'gaps_found';
|
|
51
|
+
|
|
52
|
+
// ─── PhaseRunner deps interface ──────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
export interface PhaseRunnerDeps {
|
|
55
|
+
projectDir: string;
|
|
56
|
+
tools: GSDTools;
|
|
57
|
+
promptFactory: PromptFactory;
|
|
58
|
+
contextEngine: ContextEngine;
|
|
59
|
+
eventStream: GSDEventStream;
|
|
60
|
+
config: GSDConfig;
|
|
61
|
+
logger?: GSDLogger;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── PhaseRunner ─────────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
export class PhaseRunner {
|
|
67
|
+
private readonly projectDir: string;
|
|
68
|
+
private readonly tools: GSDTools;
|
|
69
|
+
private readonly promptFactory: PromptFactory;
|
|
70
|
+
private readonly contextEngine: ContextEngine;
|
|
71
|
+
private readonly eventStream: GSDEventStream;
|
|
72
|
+
private readonly config: GSDConfig;
|
|
73
|
+
private readonly logger?: GSDLogger;
|
|
74
|
+
|
|
75
|
+
constructor(deps: PhaseRunnerDeps) {
|
|
76
|
+
this.projectDir = deps.projectDir;
|
|
77
|
+
this.tools = deps.tools;
|
|
78
|
+
this.promptFactory = deps.promptFactory;
|
|
79
|
+
this.contextEngine = deps.contextEngine;
|
|
80
|
+
this.eventStream = deps.eventStream;
|
|
81
|
+
this.config = deps.config;
|
|
82
|
+
this.logger = deps.logger;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Run a full phase lifecycle: discuss → research → plan → plan-check → execute → verify → advance.
|
|
87
|
+
*
|
|
88
|
+
* Each step is gated by config flags and phase state. Human gate callbacks
|
|
89
|
+
* are invoked at decision points; when not provided, auto-approve is used.
|
|
90
|
+
*/
|
|
91
|
+
async run(phaseNumber: string, options?: PhaseRunnerOptions): Promise<PhaseRunnerResult> {
|
|
92
|
+
const startTime = Date.now();
|
|
93
|
+
const steps: PhaseStepResult[] = [];
|
|
94
|
+
const callbacks = options?.callbacks ?? {};
|
|
95
|
+
|
|
96
|
+
// ── Init: query phase state ──
|
|
97
|
+
let phaseOp: PhaseOpInfo;
|
|
98
|
+
try {
|
|
99
|
+
phaseOp = await this.tools.initPhaseOp(phaseNumber);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
throw new PhaseRunnerError(
|
|
102
|
+
`Failed to initialize phase ${phaseNumber}: ${err instanceof Error ? err.message : String(err)}`,
|
|
103
|
+
phaseNumber,
|
|
104
|
+
PhaseStepType.Discuss,
|
|
105
|
+
err instanceof Error ? err : undefined,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Validate phase exists
|
|
110
|
+
if (!phaseOp.phase_found) {
|
|
111
|
+
throw new PhaseRunnerError(
|
|
112
|
+
`Phase ${phaseNumber} not found on disk`,
|
|
113
|
+
phaseNumber,
|
|
114
|
+
PhaseStepType.Discuss,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const phaseName = phaseOp.phase_name;
|
|
119
|
+
|
|
120
|
+
// Emit phase_start
|
|
121
|
+
this.eventStream.emitEvent({
|
|
122
|
+
type: GSDEventType.PhaseStart,
|
|
123
|
+
timestamp: new Date().toISOString(),
|
|
124
|
+
sessionId: '',
|
|
125
|
+
phaseNumber,
|
|
126
|
+
phaseName,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const sessionOpts: SessionOptions = {
|
|
130
|
+
maxTurns: options?.maxTurnsPerStep ?? 50,
|
|
131
|
+
maxBudgetUsd: options?.maxBudgetPerStep ?? 5.0,
|
|
132
|
+
model: options?.model,
|
|
133
|
+
cwd: this.projectDir,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
let halted = false;
|
|
137
|
+
|
|
138
|
+
// ── Step 1: Discuss ──
|
|
139
|
+
if (!halted) {
|
|
140
|
+
const shouldSkip = phaseOp.has_context || this.config.workflow.skip_discuss;
|
|
141
|
+
if (shouldSkip && !(this.config.workflow.auto_advance && !phaseOp.has_context && !this.config.workflow.skip_discuss)) {
|
|
142
|
+
this.logger?.debug(`Skipping discuss: has_context=${phaseOp.has_context}, skip_discuss=${this.config.workflow.skip_discuss}`);
|
|
143
|
+
} else if (!phaseOp.has_context && !this.config.workflow.skip_discuss && this.config.workflow.auto_advance) {
|
|
144
|
+
// AI self-discuss: auto-mode with no context — run a self-discuss session
|
|
145
|
+
const result = await this.retryOnce('self-discuss', () => this.runSelfDiscussStep(phaseNumber, sessionOpts));
|
|
146
|
+
steps.push(result);
|
|
147
|
+
|
|
148
|
+
// Re-query phase state to check if context was created
|
|
149
|
+
try {
|
|
150
|
+
phaseOp = await this.tools.initPhaseOp(phaseNumber);
|
|
151
|
+
} catch {
|
|
152
|
+
// If re-query fails, proceed with original state
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!phaseOp.has_context) {
|
|
156
|
+
const decision = await this.invokeBlockerCallback(callbacks, phaseNumber, PhaseStepType.Discuss, 'No context after self-discuss step');
|
|
157
|
+
if (decision === 'stop') {
|
|
158
|
+
halted = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} else if (!shouldSkip) {
|
|
162
|
+
const result = await this.retryOnce('discuss', () => this.runStep(PhaseStepType.Discuss, phaseNumber, sessionOpts));
|
|
163
|
+
steps.push(result);
|
|
164
|
+
|
|
165
|
+
// Re-query phase state to check if context was created
|
|
166
|
+
try {
|
|
167
|
+
phaseOp = await this.tools.initPhaseOp(phaseNumber);
|
|
168
|
+
} catch {
|
|
169
|
+
// If re-query fails, proceed with original state
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!phaseOp.has_context) {
|
|
173
|
+
// No context after discuss — invoke blocker callback
|
|
174
|
+
const decision = await this.invokeBlockerCallback(callbacks, phaseNumber, PhaseStepType.Discuss, 'No context after discuss step');
|
|
175
|
+
if (decision === 'stop') {
|
|
176
|
+
halted = true;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── Step 2: Research ──
|
|
183
|
+
if (!halted) {
|
|
184
|
+
if (!this.config.workflow.research) {
|
|
185
|
+
this.logger?.debug('Skipping research: config.workflow.research=false');
|
|
186
|
+
} else {
|
|
187
|
+
const result = await this.retryOnce('research', () => this.runStep(PhaseStepType.Research, phaseNumber, sessionOpts));
|
|
188
|
+
steps.push(result);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ── Step 2.5: Research gate (#1602) ──
|
|
193
|
+
// Check RESEARCH.md for unresolved open questions before planning
|
|
194
|
+
if (!halted && phaseOp.has_research) {
|
|
195
|
+
const gateResult = await this.checkResearchGate(phaseOp);
|
|
196
|
+
if (!gateResult.pass) {
|
|
197
|
+
const questionList = gateResult.unresolvedQuestions.join(', ');
|
|
198
|
+
const error = `RESEARCH.md has unresolved open questions: ${questionList}`;
|
|
199
|
+
this.logger?.warn(error, { phase: phaseNumber });
|
|
200
|
+
const decision = await this.invokeBlockerCallback(callbacks, phaseNumber, PhaseStepType.Research, error);
|
|
201
|
+
if (decision === 'stop') {
|
|
202
|
+
halted = true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ── Step 3: Plan ──
|
|
208
|
+
if (!halted) {
|
|
209
|
+
const result = await this.retryOnce('plan', () => this.runStep(PhaseStepType.Plan, phaseNumber, sessionOpts));
|
|
210
|
+
steps.push(result);
|
|
211
|
+
|
|
212
|
+
// Re-query to check for plans
|
|
213
|
+
try {
|
|
214
|
+
phaseOp = await this.tools.initPhaseOp(phaseNumber);
|
|
215
|
+
} catch {
|
|
216
|
+
// Proceed with prior state
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!phaseOp.has_plans || phaseOp.plan_count === 0) {
|
|
220
|
+
const decision = await this.invokeBlockerCallback(callbacks, phaseNumber, PhaseStepType.Plan, 'No plans created after plan step');
|
|
221
|
+
if (decision === 'stop') {
|
|
222
|
+
halted = true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ── Step 3.5: Plan Check ──
|
|
228
|
+
if (!halted && this.config.workflow.plan_check) {
|
|
229
|
+
const planCheckResult = await this.retryOnce('plan-check', () => this.runPlanCheckStep(phaseNumber, sessionOpts));
|
|
230
|
+
steps.push(planCheckResult);
|
|
231
|
+
|
|
232
|
+
// If plan-check failed, re-plan once then re-check once (D023)
|
|
233
|
+
if (!planCheckResult.success) {
|
|
234
|
+
this.logger?.info(`Plan check failed for phase ${phaseNumber}, re-planning once (D023)`);
|
|
235
|
+
|
|
236
|
+
// Re-run plan step with feedback
|
|
237
|
+
const replanResult = await this.runStep(PhaseStepType.Plan, phaseNumber, sessionOpts);
|
|
238
|
+
steps.push(replanResult);
|
|
239
|
+
|
|
240
|
+
// Re-check once
|
|
241
|
+
const recheckResult = await this.runPlanCheckStep(phaseNumber, sessionOpts);
|
|
242
|
+
steps.push(recheckResult);
|
|
243
|
+
|
|
244
|
+
if (!recheckResult.success) {
|
|
245
|
+
this.logger?.warn(`Plan check failed again after re-plan for phase ${phaseNumber}. Proceeding with warning (D023).`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Step 4: Execute ──
|
|
251
|
+
if (!halted) {
|
|
252
|
+
const executeResult = await this.retryOnce('execute', () => this.runExecuteStep(phaseNumber, sessionOpts));
|
|
253
|
+
steps.push(executeResult);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ── Step 5: Verify ──
|
|
257
|
+
if (!halted) {
|
|
258
|
+
if (!this.config.workflow.verifier) {
|
|
259
|
+
this.logger?.debug('Skipping verify: config.workflow.verifier=false');
|
|
260
|
+
} else {
|
|
261
|
+
// Verify has its own internal retry logic (gap closure). retryOnce only
|
|
262
|
+
// retries on unexpected session throws, not on verification outcomes like gaps_found.
|
|
263
|
+
const verifyResult = await this.retryOnce('verify', () => this.runVerifyStep(phaseNumber, sessionOpts, callbacks, options));
|
|
264
|
+
steps.push(verifyResult);
|
|
265
|
+
|
|
266
|
+
// Check if verify resulted in a halt
|
|
267
|
+
if (!verifyResult.success && verifyResult.error === 'halted_by_callback') {
|
|
268
|
+
halted = true;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ── Step 6: Advance ──
|
|
274
|
+
// Only advance if verify passed — never mark a phase complete when gaps were found.
|
|
275
|
+
const verifyPassed = steps.every(s => s.step !== PhaseStepType.Verify || s.success);
|
|
276
|
+
if (!halted && verifyPassed) {
|
|
277
|
+
const advanceResult = await this.runAdvanceStep(phaseNumber, sessionOpts, callbacks);
|
|
278
|
+
steps.push(advanceResult);
|
|
279
|
+
} else if (!halted && !verifyPassed) {
|
|
280
|
+
this.logger?.warn(`Skipping advance for phase ${phaseNumber}: verification found gaps`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const totalDurationMs = Date.now() - startTime;
|
|
284
|
+
const totalCostUsd = steps.reduce((sum, s) => {
|
|
285
|
+
const stepCost = s.planResults?.reduce((c, pr) => c + pr.totalCostUsd, 0) ?? 0;
|
|
286
|
+
return sum + stepCost;
|
|
287
|
+
}, 0);
|
|
288
|
+
const success = !halted && steps.every(s => s.success);
|
|
289
|
+
|
|
290
|
+
// Emit phase_complete
|
|
291
|
+
this.eventStream.emitEvent({
|
|
292
|
+
type: GSDEventType.PhaseComplete,
|
|
293
|
+
timestamp: new Date().toISOString(),
|
|
294
|
+
sessionId: '',
|
|
295
|
+
phaseNumber,
|
|
296
|
+
phaseName,
|
|
297
|
+
success,
|
|
298
|
+
totalCostUsd,
|
|
299
|
+
totalDurationMs,
|
|
300
|
+
stepsCompleted: steps.length,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
await this.recordFailureSignals(phaseNumber, phaseName, phaseOp.phase_dir, steps);
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
phaseNumber,
|
|
307
|
+
phaseName,
|
|
308
|
+
steps,
|
|
309
|
+
success,
|
|
310
|
+
totalCostUsd,
|
|
311
|
+
totalDurationMs,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ─── Step runners ──────────────────────────────────────────────────────
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Retry a step function once on failure.
|
|
319
|
+
* On first error/failure, logs a warning and calls the function once more.
|
|
320
|
+
* Returns the result from the last attempt.
|
|
321
|
+
*/
|
|
322
|
+
private async retryOnce<T extends PhaseStepResult>(label: string, fn: () => Promise<T>): Promise<T> {
|
|
323
|
+
const result = await fn();
|
|
324
|
+
if (result.success) return result;
|
|
325
|
+
|
|
326
|
+
// Don't retry verify outcomes (gaps_found, human_needed) — they have their own retry logic.
|
|
327
|
+
if (result.error?.startsWith('verification_')) return result;
|
|
328
|
+
|
|
329
|
+
this.logger?.warn(`Step "${label}" failed, retrying once...`);
|
|
330
|
+
return fn();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Run the plan-check step.
|
|
335
|
+
* Loads the gsd-plan-checker agent definition, runs a Verify-scoped session,
|
|
336
|
+
* and parses output for PASS/FAIL signals.
|
|
337
|
+
*/
|
|
338
|
+
private async runPlanCheckStep(
|
|
339
|
+
phaseNumber: string,
|
|
340
|
+
sessionOpts: SessionOptions,
|
|
341
|
+
): Promise<PhaseStepResult> {
|
|
342
|
+
const stepStart = Date.now();
|
|
343
|
+
|
|
344
|
+
this.eventStream.emitEvent({
|
|
345
|
+
type: GSDEventType.PhaseStepStart,
|
|
346
|
+
timestamp: new Date().toISOString(),
|
|
347
|
+
sessionId: '',
|
|
348
|
+
phaseNumber,
|
|
349
|
+
step: PhaseStepType.PlanCheck,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
let planResult: PlanResult;
|
|
353
|
+
try {
|
|
354
|
+
// Load plan-checker agent definition (same pattern as PromptFactory.loadAgentDef)
|
|
355
|
+
const agentDef = await this.promptFactory.loadAgentDef(PhaseType.Verify);
|
|
356
|
+
|
|
357
|
+
// Build prompt using Verify phase type for context resolution
|
|
358
|
+
const contextFiles = await this.contextEngine.resolveContextFiles(PhaseType.Verify);
|
|
359
|
+
let prompt = await this.promptFactory.buildPrompt(PhaseType.Verify, null, contextFiles);
|
|
360
|
+
|
|
361
|
+
// Supplement with plan-checker instructions
|
|
362
|
+
prompt += '\n\n## Plan Checker Instructions\n\nYou are a plan checker. Review the plans for this phase and verify they are well-formed, complete, and achievable. If all plans pass, output "VERIFICATION PASSED". If any issues are found, output "ISSUES FOUND" followed by a description of each issue.';
|
|
363
|
+
|
|
364
|
+
planResult = await runPhaseStepSession(
|
|
365
|
+
prompt,
|
|
366
|
+
PhaseStepType.PlanCheck,
|
|
367
|
+
this.config,
|
|
368
|
+
sessionOpts,
|
|
369
|
+
this.eventStream,
|
|
370
|
+
{ phase: PhaseType.Verify, planName: undefined },
|
|
371
|
+
);
|
|
372
|
+
} catch (err) {
|
|
373
|
+
const durationMs = Date.now() - stepStart;
|
|
374
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
375
|
+
|
|
376
|
+
this.eventStream.emitEvent({
|
|
377
|
+
type: GSDEventType.PhaseStepComplete,
|
|
378
|
+
timestamp: new Date().toISOString(),
|
|
379
|
+
sessionId: '',
|
|
380
|
+
phaseNumber,
|
|
381
|
+
step: PhaseStepType.PlanCheck,
|
|
382
|
+
success: false,
|
|
383
|
+
durationMs,
|
|
384
|
+
error: errorMsg,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
step: PhaseStepType.PlanCheck,
|
|
389
|
+
success: false,
|
|
390
|
+
durationMs,
|
|
391
|
+
error: errorMsg,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const durationMs = Date.now() - stepStart;
|
|
396
|
+
// Parse plan-check outcome: success if the session succeeded (real output parsing would check for VERIFICATION PASSED / ISSUES FOUND)
|
|
397
|
+
const success = planResult.success;
|
|
398
|
+
|
|
399
|
+
this.eventStream.emitEvent({
|
|
400
|
+
type: GSDEventType.PhaseStepComplete,
|
|
401
|
+
timestamp: new Date().toISOString(),
|
|
402
|
+
sessionId: planResult.sessionId,
|
|
403
|
+
phaseNumber,
|
|
404
|
+
step: PhaseStepType.PlanCheck,
|
|
405
|
+
success,
|
|
406
|
+
durationMs,
|
|
407
|
+
error: planResult.error?.messages.join('; ') || undefined,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
step: PhaseStepType.PlanCheck,
|
|
412
|
+
success,
|
|
413
|
+
durationMs,
|
|
414
|
+
error: planResult.error?.messages.join('; ') || undefined,
|
|
415
|
+
planResults: [planResult],
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Run the self-discuss step for auto-mode.
|
|
421
|
+
* When auto_advance is true and no context exists, run an AI self-discuss
|
|
422
|
+
* session that identifies gray areas and makes opinionated decisions.
|
|
423
|
+
*/
|
|
424
|
+
private async runSelfDiscussStep(
|
|
425
|
+
phaseNumber: string,
|
|
426
|
+
sessionOpts: SessionOptions,
|
|
427
|
+
): Promise<PhaseStepResult> {
|
|
428
|
+
const stepStart = Date.now();
|
|
429
|
+
|
|
430
|
+
this.eventStream.emitEvent({
|
|
431
|
+
type: GSDEventType.PhaseStepStart,
|
|
432
|
+
timestamp: new Date().toISOString(),
|
|
433
|
+
sessionId: '',
|
|
434
|
+
phaseNumber,
|
|
435
|
+
step: PhaseStepType.Discuss,
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
let planResult: PlanResult;
|
|
439
|
+
try {
|
|
440
|
+
const contextFiles = await this.contextEngine.resolveContextFiles(PhaseType.Discuss);
|
|
441
|
+
let prompt = await this.promptFactory.buildPrompt(PhaseType.Discuss, null, contextFiles);
|
|
442
|
+
|
|
443
|
+
// Supplement with self-discuss instructions with pass cap
|
|
444
|
+
const maxPasses = this.config.workflow.max_discuss_passes ?? 3;
|
|
445
|
+
prompt += `\n\n## Self-Discuss Mode\n\nYou are the AI discussing decisions with yourself. No human is present. Identify 3-5 gray areas in the project scope, reason through each one, make opinionated choices, and write CONTEXT.md with your decisions.\n\n**CRITICAL: Single-pass only.** You MUST complete all decisions in ONE pass and write CONTEXT.md once. Do NOT re-read your own CONTEXT.md to find "gaps" and do additional passes. The maximum allowed passes is ${maxPasses} — if you have already written CONTEXT.md, you are DONE. Proceed to the next workflow step. Self-referential gap-finding loops waste resources without adding value.`;
|
|
446
|
+
|
|
447
|
+
planResult = await runPhaseStepSession(
|
|
448
|
+
prompt,
|
|
449
|
+
PhaseStepType.Discuss,
|
|
450
|
+
this.config,
|
|
451
|
+
sessionOpts,
|
|
452
|
+
this.eventStream,
|
|
453
|
+
{ phase: PhaseType.Discuss, planName: undefined },
|
|
454
|
+
);
|
|
455
|
+
} catch (err) {
|
|
456
|
+
const durationMs = Date.now() - stepStart;
|
|
457
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
458
|
+
|
|
459
|
+
this.eventStream.emitEvent({
|
|
460
|
+
type: GSDEventType.PhaseStepComplete,
|
|
461
|
+
timestamp: new Date().toISOString(),
|
|
462
|
+
sessionId: '',
|
|
463
|
+
phaseNumber,
|
|
464
|
+
step: PhaseStepType.Discuss,
|
|
465
|
+
success: false,
|
|
466
|
+
durationMs,
|
|
467
|
+
error: errorMsg,
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
return {
|
|
471
|
+
step: PhaseStepType.Discuss,
|
|
472
|
+
success: false,
|
|
473
|
+
durationMs,
|
|
474
|
+
error: errorMsg,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const durationMs = Date.now() - stepStart;
|
|
479
|
+
const success = planResult.success;
|
|
480
|
+
|
|
481
|
+
this.eventStream.emitEvent({
|
|
482
|
+
type: GSDEventType.PhaseStepComplete,
|
|
483
|
+
timestamp: new Date().toISOString(),
|
|
484
|
+
sessionId: planResult.sessionId,
|
|
485
|
+
phaseNumber,
|
|
486
|
+
step: PhaseStepType.Discuss,
|
|
487
|
+
success,
|
|
488
|
+
durationMs,
|
|
489
|
+
error: planResult.error?.messages.join('; ') || undefined,
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
step: PhaseStepType.Discuss,
|
|
494
|
+
success,
|
|
495
|
+
durationMs,
|
|
496
|
+
error: planResult.error?.messages.join('; ') || undefined,
|
|
497
|
+
planResults: [planResult],
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Run a single phase step session (discuss, research, plan).
|
|
503
|
+
* Emits step start/complete events and captures errors.
|
|
504
|
+
*/
|
|
505
|
+
private async runStep(
|
|
506
|
+
step: PhaseStepType,
|
|
507
|
+
phaseNumber: string,
|
|
508
|
+
sessionOpts: SessionOptions,
|
|
509
|
+
): Promise<PhaseStepResult> {
|
|
510
|
+
const stepStart = Date.now();
|
|
511
|
+
|
|
512
|
+
this.eventStream.emitEvent({
|
|
513
|
+
type: GSDEventType.PhaseStepStart,
|
|
514
|
+
timestamp: new Date().toISOString(),
|
|
515
|
+
sessionId: '',
|
|
516
|
+
phaseNumber,
|
|
517
|
+
step,
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
let planResult: PlanResult;
|
|
521
|
+
try {
|
|
522
|
+
// Map step to PhaseType for prompt/context resolution
|
|
523
|
+
const phaseType = this.stepToPhaseType(step);
|
|
524
|
+
const contextFiles = await this.contextEngine.resolveContextFiles(phaseType);
|
|
525
|
+
const prompt = await this.promptFactory.buildPrompt(phaseType, null, contextFiles);
|
|
526
|
+
|
|
527
|
+
planResult = await runPhaseStepSession(
|
|
528
|
+
prompt,
|
|
529
|
+
step,
|
|
530
|
+
this.config,
|
|
531
|
+
sessionOpts,
|
|
532
|
+
this.eventStream,
|
|
533
|
+
{ phase: phaseType, planName: undefined },
|
|
534
|
+
);
|
|
535
|
+
} catch (err) {
|
|
536
|
+
const durationMs = Date.now() - stepStart;
|
|
537
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
538
|
+
|
|
539
|
+
this.eventStream.emitEvent({
|
|
540
|
+
type: GSDEventType.PhaseStepComplete,
|
|
541
|
+
timestamp: new Date().toISOString(),
|
|
542
|
+
sessionId: '',
|
|
543
|
+
phaseNumber,
|
|
544
|
+
step,
|
|
545
|
+
success: false,
|
|
546
|
+
durationMs,
|
|
547
|
+
error: errorMsg,
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
step,
|
|
552
|
+
success: false,
|
|
553
|
+
durationMs,
|
|
554
|
+
error: errorMsg,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const durationMs = Date.now() - stepStart;
|
|
559
|
+
const success = planResult.success;
|
|
560
|
+
|
|
561
|
+
this.eventStream.emitEvent({
|
|
562
|
+
type: GSDEventType.PhaseStepComplete,
|
|
563
|
+
timestamp: new Date().toISOString(),
|
|
564
|
+
sessionId: planResult.sessionId,
|
|
565
|
+
phaseNumber,
|
|
566
|
+
step,
|
|
567
|
+
success,
|
|
568
|
+
durationMs,
|
|
569
|
+
error: planResult.error?.messages.join('; ') || undefined,
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
return {
|
|
573
|
+
step,
|
|
574
|
+
success,
|
|
575
|
+
durationMs,
|
|
576
|
+
error: planResult.error?.messages.join('; ') || undefined,
|
|
577
|
+
planResults: [planResult],
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Run the execute step — uses phase-plan-index for wave-grouped parallel execution.
|
|
583
|
+
* Plans in the same wave run concurrently via Promise.allSettled().
|
|
584
|
+
* Waves execute sequentially (wave 1 completes before wave 2 starts).
|
|
585
|
+
* Respects config.parallelization: false to fall back to sequential execution.
|
|
586
|
+
* Filters out plans with has_summary: true (already completed).
|
|
587
|
+
*/
|
|
588
|
+
private async runExecuteStep(
|
|
589
|
+
phaseNumber: string,
|
|
590
|
+
sessionOpts: SessionOptions,
|
|
591
|
+
): Promise<PhaseStepResult> {
|
|
592
|
+
const stepStart = Date.now();
|
|
593
|
+
|
|
594
|
+
this.eventStream.emitEvent({
|
|
595
|
+
type: GSDEventType.PhaseStepStart,
|
|
596
|
+
timestamp: new Date().toISOString(),
|
|
597
|
+
sessionId: '',
|
|
598
|
+
phaseNumber,
|
|
599
|
+
step: PhaseStepType.Execute,
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
// Get the plan index from gsd-tools
|
|
603
|
+
let planIndex: PhasePlanIndex;
|
|
604
|
+
try {
|
|
605
|
+
planIndex = await this.tools.phasePlanIndex(phaseNumber);
|
|
606
|
+
} catch (err) {
|
|
607
|
+
const durationMs = Date.now() - stepStart;
|
|
608
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
609
|
+
this.eventStream.emitEvent({
|
|
610
|
+
type: GSDEventType.PhaseStepComplete,
|
|
611
|
+
timestamp: new Date().toISOString(),
|
|
612
|
+
sessionId: '',
|
|
613
|
+
phaseNumber,
|
|
614
|
+
step: PhaseStepType.Execute,
|
|
615
|
+
success: false,
|
|
616
|
+
durationMs,
|
|
617
|
+
error: errorMsg,
|
|
618
|
+
});
|
|
619
|
+
return {
|
|
620
|
+
step: PhaseStepType.Execute,
|
|
621
|
+
success: false,
|
|
622
|
+
durationMs,
|
|
623
|
+
error: errorMsg,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Filter to incomplete plans only (has_summary === false)
|
|
628
|
+
const incompletePlans = planIndex.plans.filter(p => !p.has_summary);
|
|
629
|
+
|
|
630
|
+
if (incompletePlans.length === 0) {
|
|
631
|
+
const durationMs = Date.now() - stepStart;
|
|
632
|
+
this.eventStream.emitEvent({
|
|
633
|
+
type: GSDEventType.PhaseStepComplete,
|
|
634
|
+
timestamp: new Date().toISOString(),
|
|
635
|
+
sessionId: '',
|
|
636
|
+
phaseNumber,
|
|
637
|
+
step: PhaseStepType.Execute,
|
|
638
|
+
success: true,
|
|
639
|
+
durationMs,
|
|
640
|
+
});
|
|
641
|
+
return {
|
|
642
|
+
step: PhaseStepType.Execute,
|
|
643
|
+
success: true,
|
|
644
|
+
durationMs,
|
|
645
|
+
planResults: [],
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const planResults: PlanResult[] = [];
|
|
650
|
+
|
|
651
|
+
// Sequential fallback when parallelization is disabled
|
|
652
|
+
if (this.config.parallelization === false) {
|
|
653
|
+
for (const plan of incompletePlans) {
|
|
654
|
+
const result = await this.executeSinglePlan(phaseNumber, plan.id, sessionOpts);
|
|
655
|
+
planResults.push(result);
|
|
656
|
+
}
|
|
657
|
+
} else {
|
|
658
|
+
// Group incomplete plans by wave, sort waves numerically
|
|
659
|
+
const waveMap = new Map<number, PlanInfo[]>();
|
|
660
|
+
for (const plan of incompletePlans) {
|
|
661
|
+
const existing = waveMap.get(plan.wave) ?? [];
|
|
662
|
+
existing.push(plan);
|
|
663
|
+
waveMap.set(plan.wave, existing);
|
|
664
|
+
}
|
|
665
|
+
const sortedWaves = [...waveMap.keys()].sort((a, b) => a - b);
|
|
666
|
+
|
|
667
|
+
for (const waveNum of sortedWaves) {
|
|
668
|
+
const wavePlans = waveMap.get(waveNum)!;
|
|
669
|
+
const wavePlanIds = wavePlans.map(p => p.id);
|
|
670
|
+
|
|
671
|
+
// Emit wave_start
|
|
672
|
+
this.eventStream.emitEvent({
|
|
673
|
+
type: GSDEventType.WaveStart,
|
|
674
|
+
timestamp: new Date().toISOString(),
|
|
675
|
+
sessionId: '',
|
|
676
|
+
phaseNumber,
|
|
677
|
+
waveNumber: waveNum,
|
|
678
|
+
planCount: wavePlans.length,
|
|
679
|
+
planIds: wavePlanIds,
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
const waveStart = Date.now();
|
|
683
|
+
|
|
684
|
+
// Execute all plans in this wave concurrently
|
|
685
|
+
const settled = await Promise.allSettled(
|
|
686
|
+
wavePlans.map(plan => this.executeSinglePlan(phaseNumber, plan.id, sessionOpts)),
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
// Map settled results to PlanResult[]
|
|
690
|
+
let successCount = 0;
|
|
691
|
+
let failureCount = 0;
|
|
692
|
+
for (const outcome of settled) {
|
|
693
|
+
if (outcome.status === 'fulfilled') {
|
|
694
|
+
planResults.push(outcome.value);
|
|
695
|
+
if (outcome.value.success) successCount++;
|
|
696
|
+
else failureCount++;
|
|
697
|
+
} else {
|
|
698
|
+
failureCount++;
|
|
699
|
+
planResults.push({
|
|
700
|
+
success: false,
|
|
701
|
+
sessionId: '',
|
|
702
|
+
totalCostUsd: 0,
|
|
703
|
+
durationMs: 0,
|
|
704
|
+
usage: { inputTokens: 0, outputTokens: 0, cacheReadInputTokens: 0, cacheCreationInputTokens: 0 },
|
|
705
|
+
numTurns: 0,
|
|
706
|
+
error: {
|
|
707
|
+
subtype: 'error_during_execution',
|
|
708
|
+
messages: [outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason)],
|
|
709
|
+
},
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Emit wave_complete
|
|
715
|
+
this.eventStream.emitEvent({
|
|
716
|
+
type: GSDEventType.WaveComplete,
|
|
717
|
+
timestamp: new Date().toISOString(),
|
|
718
|
+
sessionId: '',
|
|
719
|
+
phaseNumber,
|
|
720
|
+
waveNumber: waveNum,
|
|
721
|
+
successCount,
|
|
722
|
+
failureCount,
|
|
723
|
+
durationMs: Date.now() - waveStart,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const durationMs = Date.now() - stepStart;
|
|
729
|
+
const allSucceeded = planResults.every(r => r.success);
|
|
730
|
+
|
|
731
|
+
this.eventStream.emitEvent({
|
|
732
|
+
type: GSDEventType.PhaseStepComplete,
|
|
733
|
+
timestamp: new Date().toISOString(),
|
|
734
|
+
sessionId: '',
|
|
735
|
+
phaseNumber,
|
|
736
|
+
step: PhaseStepType.Execute,
|
|
737
|
+
success: allSucceeded,
|
|
738
|
+
durationMs,
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
return {
|
|
742
|
+
step: PhaseStepType.Execute,
|
|
743
|
+
success: allSucceeded,
|
|
744
|
+
durationMs,
|
|
745
|
+
planResults,
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Execute a single plan by ID within the execute step.
|
|
751
|
+
*/
|
|
752
|
+
private async executeSinglePlan(
|
|
753
|
+
phaseNumber: string,
|
|
754
|
+
planId: string,
|
|
755
|
+
sessionOpts: SessionOptions,
|
|
756
|
+
): Promise<PlanResult> {
|
|
757
|
+
try {
|
|
758
|
+
const phaseType = PhaseType.Execute;
|
|
759
|
+
const contextFiles = await this.contextEngine.resolveContextFiles(phaseType);
|
|
760
|
+
const prompt = await this.promptFactory.buildPrompt(phaseType, null, contextFiles);
|
|
761
|
+
|
|
762
|
+
return await runPhaseStepSession(
|
|
763
|
+
prompt,
|
|
764
|
+
PhaseStepType.Execute,
|
|
765
|
+
this.config,
|
|
766
|
+
sessionOpts,
|
|
767
|
+
this.eventStream,
|
|
768
|
+
{ phase: phaseType, planName: planId },
|
|
769
|
+
);
|
|
770
|
+
} catch (err) {
|
|
771
|
+
return {
|
|
772
|
+
success: false,
|
|
773
|
+
sessionId: '',
|
|
774
|
+
totalCostUsd: 0,
|
|
775
|
+
durationMs: 0,
|
|
776
|
+
usage: { inputTokens: 0, outputTokens: 0, cacheReadInputTokens: 0, cacheCreationInputTokens: 0 },
|
|
777
|
+
numTurns: 0,
|
|
778
|
+
error: {
|
|
779
|
+
subtype: 'error_during_execution',
|
|
780
|
+
messages: [err instanceof Error ? err.message : String(err)],
|
|
781
|
+
},
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Run the verify step with full gap closure cycle.
|
|
788
|
+
* Verification outcome routing:
|
|
789
|
+
* - passed → proceed to advance
|
|
790
|
+
* - human_needed → invoke onVerificationReview callback
|
|
791
|
+
* - gaps_found → plan (create gap plans) → execute (run gap plans) → re-verify
|
|
792
|
+
* Gap closure retries are capped at configurable maxGapRetries (default 1).
|
|
793
|
+
*/
|
|
794
|
+
private async runVerifyStep(
|
|
795
|
+
phaseNumber: string,
|
|
796
|
+
sessionOpts: SessionOptions,
|
|
797
|
+
callbacks: HumanGateCallbacks,
|
|
798
|
+
options?: PhaseRunnerOptions,
|
|
799
|
+
): Promise<PhaseStepResult> {
|
|
800
|
+
const stepStart = Date.now();
|
|
801
|
+
|
|
802
|
+
this.eventStream.emitEvent({
|
|
803
|
+
type: GSDEventType.PhaseStepStart,
|
|
804
|
+
timestamp: new Date().toISOString(),
|
|
805
|
+
sessionId: '',
|
|
806
|
+
phaseNumber,
|
|
807
|
+
step: PhaseStepType.Verify,
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
const maxGapRetries = options?.maxGapRetries ?? 1;
|
|
811
|
+
let gapRetryCount = 0;
|
|
812
|
+
let lastResult: PlanResult | undefined;
|
|
813
|
+
let outcome: VerificationOutcome = 'passed';
|
|
814
|
+
const allPlanResults: PlanResult[] = [];
|
|
815
|
+
|
|
816
|
+
while (true) {
|
|
817
|
+
try {
|
|
818
|
+
const phaseType = PhaseType.Verify;
|
|
819
|
+
const contextFiles = await this.contextEngine.resolveContextFiles(phaseType);
|
|
820
|
+
const prompt = await this.promptFactory.buildPrompt(phaseType, null, contextFiles);
|
|
821
|
+
|
|
822
|
+
lastResult = await runPhaseStepSession(
|
|
823
|
+
prompt,
|
|
824
|
+
PhaseStepType.Verify,
|
|
825
|
+
this.config,
|
|
826
|
+
sessionOpts,
|
|
827
|
+
this.eventStream,
|
|
828
|
+
{ phase: phaseType },
|
|
829
|
+
);
|
|
830
|
+
allPlanResults.push(lastResult);
|
|
831
|
+
} catch (err) {
|
|
832
|
+
const durationMs = Date.now() - stepStart;
|
|
833
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
834
|
+
|
|
835
|
+
this.eventStream.emitEvent({
|
|
836
|
+
type: GSDEventType.PhaseStepComplete,
|
|
837
|
+
timestamp: new Date().toISOString(),
|
|
838
|
+
sessionId: '',
|
|
839
|
+
phaseNumber,
|
|
840
|
+
step: PhaseStepType.Verify,
|
|
841
|
+
success: false,
|
|
842
|
+
durationMs,
|
|
843
|
+
error: errorMsg,
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
return {
|
|
847
|
+
step: PhaseStepType.Verify,
|
|
848
|
+
success: false,
|
|
849
|
+
durationMs,
|
|
850
|
+
error: errorMsg,
|
|
851
|
+
planResults: allPlanResults.length > 0 ? allPlanResults : undefined,
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// Parse verification outcome from session result
|
|
856
|
+
outcome = this.parseVerificationOutcome(lastResult);
|
|
857
|
+
|
|
858
|
+
if (outcome === 'passed') {
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
if (outcome === 'human_needed') {
|
|
863
|
+
// Invoke verification review callback
|
|
864
|
+
const decision = await this.invokeVerificationCallback(callbacks, phaseNumber, {
|
|
865
|
+
step: PhaseStepType.Verify,
|
|
866
|
+
success: lastResult.success,
|
|
867
|
+
durationMs: Date.now() - stepStart,
|
|
868
|
+
planResults: allPlanResults,
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
if (decision === 'accept') {
|
|
872
|
+
outcome = 'passed';
|
|
873
|
+
break; // Treat as passed
|
|
874
|
+
} else if (decision === 'retry' && gapRetryCount < maxGapRetries) {
|
|
875
|
+
gapRetryCount++;
|
|
876
|
+
continue;
|
|
877
|
+
} else {
|
|
878
|
+
// reject or exceeded retries
|
|
879
|
+
const durationMs = Date.now() - stepStart;
|
|
880
|
+
this.eventStream.emitEvent({
|
|
881
|
+
type: GSDEventType.PhaseStepComplete,
|
|
882
|
+
timestamp: new Date().toISOString(),
|
|
883
|
+
sessionId: lastResult.sessionId,
|
|
884
|
+
phaseNumber,
|
|
885
|
+
step: PhaseStepType.Verify,
|
|
886
|
+
success: false,
|
|
887
|
+
durationMs,
|
|
888
|
+
error: 'halted_by_callback',
|
|
889
|
+
});
|
|
890
|
+
return {
|
|
891
|
+
step: PhaseStepType.Verify,
|
|
892
|
+
success: false,
|
|
893
|
+
durationMs,
|
|
894
|
+
error: 'halted_by_callback',
|
|
895
|
+
planResults: allPlanResults,
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (outcome === 'gaps_found') {
|
|
901
|
+
if (gapRetryCount < maxGapRetries) {
|
|
902
|
+
gapRetryCount++;
|
|
903
|
+
this.logger?.info(`Gap closure attempt ${gapRetryCount}/${maxGapRetries} for phase ${phaseNumber}`);
|
|
904
|
+
|
|
905
|
+
// ── Gap closure cycle: plan → execute → re-verify ──
|
|
906
|
+
|
|
907
|
+
// 1. Run a plan step to create gap plans
|
|
908
|
+
try {
|
|
909
|
+
const planResult = await this.runStep(PhaseStepType.Plan, phaseNumber, sessionOpts);
|
|
910
|
+
if (planResult.planResults) {
|
|
911
|
+
allPlanResults.push(...planResult.planResults);
|
|
912
|
+
}
|
|
913
|
+
} catch (err) {
|
|
914
|
+
this.logger?.warn(`Gap closure plan step failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
915
|
+
// Proceed to re-verify anyway
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// 2. Re-query phase state to discover newly created gap plans
|
|
919
|
+
try {
|
|
920
|
+
await this.tools.initPhaseOp(phaseNumber);
|
|
921
|
+
} catch (err) {
|
|
922
|
+
this.logger?.warn(`Gap closure re-query failed, proceeding with stale state: ${err instanceof Error ? err.message : String(err)}`);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// 3. Execute gap plans via the wave-capable runExecuteStep
|
|
926
|
+
try {
|
|
927
|
+
const executeResult = await this.runExecuteStep(phaseNumber, sessionOpts);
|
|
928
|
+
if (executeResult.planResults) {
|
|
929
|
+
allPlanResults.push(...executeResult.planResults);
|
|
930
|
+
}
|
|
931
|
+
} catch (err) {
|
|
932
|
+
this.logger?.warn(`Gap closure execute step failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
933
|
+
// Proceed to re-verify anyway
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// 4. Continue the loop to re-verify
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
// Exceeded gap closure retries — proceed
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
break; // Safety: unknown outcome → proceed
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const durationMs = Date.now() - stepStart;
|
|
947
|
+
const verifySuccess = outcome === 'passed';
|
|
948
|
+
|
|
949
|
+
this.eventStream.emitEvent({
|
|
950
|
+
type: GSDEventType.PhaseStepComplete,
|
|
951
|
+
timestamp: new Date().toISOString(),
|
|
952
|
+
sessionId: lastResult?.sessionId ?? '',
|
|
953
|
+
phaseNumber,
|
|
954
|
+
step: PhaseStepType.Verify,
|
|
955
|
+
success: verifySuccess,
|
|
956
|
+
durationMs,
|
|
957
|
+
...(!verifySuccess && { error: `verification_${outcome}` }),
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
return {
|
|
961
|
+
step: PhaseStepType.Verify,
|
|
962
|
+
success: verifySuccess,
|
|
963
|
+
durationMs,
|
|
964
|
+
planResults: allPlanResults,
|
|
965
|
+
...(!verifySuccess && { error: `verification_${outcome}` }),
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Run the advance step — mark phase complete.
|
|
971
|
+
* Gated by config.workflow.auto_advance or callback approval.
|
|
972
|
+
*/
|
|
973
|
+
private async runAdvanceStep(
|
|
974
|
+
phaseNumber: string,
|
|
975
|
+
_sessionOpts: SessionOptions,
|
|
976
|
+
callbacks: HumanGateCallbacks,
|
|
977
|
+
): Promise<PhaseStepResult> {
|
|
978
|
+
const stepStart = Date.now();
|
|
979
|
+
|
|
980
|
+
this.eventStream.emitEvent({
|
|
981
|
+
type: GSDEventType.PhaseStepStart,
|
|
982
|
+
timestamp: new Date().toISOString(),
|
|
983
|
+
sessionId: '',
|
|
984
|
+
phaseNumber,
|
|
985
|
+
step: PhaseStepType.Advance,
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
// Check if auto_advance or callback approves
|
|
989
|
+
let shouldAdvance = this.config.workflow.auto_advance;
|
|
990
|
+
|
|
991
|
+
if (!shouldAdvance && callbacks.onBlockerDecision) {
|
|
992
|
+
try {
|
|
993
|
+
const decision = await callbacks.onBlockerDecision({
|
|
994
|
+
phaseNumber,
|
|
995
|
+
step: PhaseStepType.Advance,
|
|
996
|
+
error: undefined,
|
|
997
|
+
});
|
|
998
|
+
shouldAdvance = decision !== 'stop';
|
|
999
|
+
} catch (err) {
|
|
1000
|
+
this.logger?.warn(`Advance callback threw, auto-approving: ${err instanceof Error ? err.message : String(err)}`);
|
|
1001
|
+
shouldAdvance = true; // Auto-approve on callback error
|
|
1002
|
+
}
|
|
1003
|
+
} else if (!shouldAdvance) {
|
|
1004
|
+
// No callback, auto-approve
|
|
1005
|
+
shouldAdvance = true;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (!shouldAdvance) {
|
|
1009
|
+
const durationMs = Date.now() - stepStart;
|
|
1010
|
+
this.eventStream.emitEvent({
|
|
1011
|
+
type: GSDEventType.PhaseStepComplete,
|
|
1012
|
+
timestamp: new Date().toISOString(),
|
|
1013
|
+
sessionId: '',
|
|
1014
|
+
phaseNumber,
|
|
1015
|
+
step: PhaseStepType.Advance,
|
|
1016
|
+
success: false,
|
|
1017
|
+
durationMs,
|
|
1018
|
+
error: 'advance_rejected',
|
|
1019
|
+
});
|
|
1020
|
+
return {
|
|
1021
|
+
step: PhaseStepType.Advance,
|
|
1022
|
+
success: false,
|
|
1023
|
+
durationMs,
|
|
1024
|
+
error: 'advance_rejected',
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
try {
|
|
1029
|
+
await this.tools.phaseComplete(phaseNumber);
|
|
1030
|
+
} catch (err) {
|
|
1031
|
+
const durationMs = Date.now() - stepStart;
|
|
1032
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1033
|
+
|
|
1034
|
+
this.eventStream.emitEvent({
|
|
1035
|
+
type: GSDEventType.PhaseStepComplete,
|
|
1036
|
+
timestamp: new Date().toISOString(),
|
|
1037
|
+
sessionId: '',
|
|
1038
|
+
phaseNumber,
|
|
1039
|
+
step: PhaseStepType.Advance,
|
|
1040
|
+
success: false,
|
|
1041
|
+
durationMs,
|
|
1042
|
+
error: errorMsg,
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
return {
|
|
1046
|
+
step: PhaseStepType.Advance,
|
|
1047
|
+
success: false,
|
|
1048
|
+
durationMs,
|
|
1049
|
+
error: errorMsg,
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
const durationMs = Date.now() - stepStart;
|
|
1054
|
+
|
|
1055
|
+
this.eventStream.emitEvent({
|
|
1056
|
+
type: GSDEventType.PhaseStepComplete,
|
|
1057
|
+
timestamp: new Date().toISOString(),
|
|
1058
|
+
sessionId: '',
|
|
1059
|
+
phaseNumber,
|
|
1060
|
+
step: PhaseStepType.Advance,
|
|
1061
|
+
success: true,
|
|
1062
|
+
durationMs,
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
return {
|
|
1066
|
+
step: PhaseStepType.Advance,
|
|
1067
|
+
success: true,
|
|
1068
|
+
durationMs,
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// ─── Helpers ───────────────────────────────────────────────────────────
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* Map PhaseStepType to PhaseType for prompt/context resolution.
|
|
1076
|
+
*/
|
|
1077
|
+
private stepToPhaseType(step: PhaseStepType): PhaseType {
|
|
1078
|
+
const mapping: Record<string, PhaseType> = {
|
|
1079
|
+
[PhaseStepType.Discuss]: PhaseType.Discuss,
|
|
1080
|
+
[PhaseStepType.Research]: PhaseType.Research,
|
|
1081
|
+
[PhaseStepType.Plan]: PhaseType.Plan,
|
|
1082
|
+
[PhaseStepType.PlanCheck]: PhaseType.Verify,
|
|
1083
|
+
[PhaseStepType.Execute]: PhaseType.Execute,
|
|
1084
|
+
[PhaseStepType.Verify]: PhaseType.Verify,
|
|
1085
|
+
};
|
|
1086
|
+
return mapping[step] ?? PhaseType.Execute;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/**
|
|
1090
|
+
* Parse the verification outcome from a PlanResult.
|
|
1091
|
+
* In a real implementation, this would parse the session output for
|
|
1092
|
+
* structured verification signals. For now, map from success/error.
|
|
1093
|
+
*/
|
|
1094
|
+
private parseVerificationOutcome(result: PlanResult): VerificationOutcome {
|
|
1095
|
+
if (result.success) return 'passed';
|
|
1096
|
+
if (result.error?.subtype === 'human_review_needed') return 'human_needed';
|
|
1097
|
+
return 'gaps_found';
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* Check RESEARCH.md for unresolved open questions (#1602).
|
|
1102
|
+
* Returns the gate result — pass means safe to proceed to planning.
|
|
1103
|
+
*/
|
|
1104
|
+
private async checkResearchGate(phaseOp: PhaseOpInfo): Promise<{ pass: boolean; unresolvedQuestions: string[] }> {
|
|
1105
|
+
try {
|
|
1106
|
+
const researchPath = phaseOp.research_path ||
|
|
1107
|
+
join(phaseOp.phase_dir, `${phaseOp.padded_phase}-RESEARCH.md`);
|
|
1108
|
+
const content = await readFile(researchPath, 'utf-8');
|
|
1109
|
+
return checkResearchGate(content);
|
|
1110
|
+
} catch {
|
|
1111
|
+
// File doesn't exist or can't be read — pass (nothing to gate on)
|
|
1112
|
+
return { pass: true, unresolvedQuestions: [] };
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Invoke the onBlockerDecision callback, falling back to auto-approve.
|
|
1118
|
+
*/
|
|
1119
|
+
private async invokeBlockerCallback(
|
|
1120
|
+
callbacks: HumanGateCallbacks,
|
|
1121
|
+
phaseNumber: string,
|
|
1122
|
+
step: PhaseStepType,
|
|
1123
|
+
error?: string,
|
|
1124
|
+
): Promise<'retry' | 'skip' | 'stop'> {
|
|
1125
|
+
if (!callbacks.onBlockerDecision) {
|
|
1126
|
+
return 'skip'; // Auto-approve: skip the blocker
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
try {
|
|
1130
|
+
const decision = await callbacks.onBlockerDecision({ phaseNumber, step, error });
|
|
1131
|
+
// Validate return value
|
|
1132
|
+
if (decision === 'retry' || decision === 'skip' || decision === 'stop') {
|
|
1133
|
+
return decision;
|
|
1134
|
+
}
|
|
1135
|
+
this.logger?.warn(`Unexpected blocker callback return value: ${String(decision)}, falling back to skip`);
|
|
1136
|
+
return 'skip';
|
|
1137
|
+
} catch (err) {
|
|
1138
|
+
this.logger?.warn(`Blocker callback threw, auto-approving: ${err instanceof Error ? err.message : String(err)}`);
|
|
1139
|
+
return 'skip'; // Auto-approve on error
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* Invoke the onVerificationReview callback, falling back to auto-accept.
|
|
1145
|
+
*/
|
|
1146
|
+
private async invokeVerificationCallback(
|
|
1147
|
+
callbacks: HumanGateCallbacks,
|
|
1148
|
+
phaseNumber: string,
|
|
1149
|
+
stepResult: PhaseStepResult,
|
|
1150
|
+
): Promise<'accept' | 'reject' | 'retry'> {
|
|
1151
|
+
if (!callbacks.onVerificationReview) {
|
|
1152
|
+
return 'accept'; // Auto-approve
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
try {
|
|
1156
|
+
const decision = await callbacks.onVerificationReview({ phaseNumber, stepResult });
|
|
1157
|
+
if (decision === 'accept' || decision === 'reject' || decision === 'retry') {
|
|
1158
|
+
return decision;
|
|
1159
|
+
}
|
|
1160
|
+
this.logger?.warn(`Unexpected verification callback return value: ${String(decision)}, falling back to accept`);
|
|
1161
|
+
return 'accept';
|
|
1162
|
+
} catch (err) {
|
|
1163
|
+
this.logger?.warn(`Verification callback threw, auto-accepting: ${err instanceof Error ? err.message : String(err)}`);
|
|
1164
|
+
return 'accept'; // Auto-approve on error
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
private async recordFailureSignals(
|
|
1169
|
+
phaseNumber: string,
|
|
1170
|
+
phaseName: string,
|
|
1171
|
+
phaseDir: string,
|
|
1172
|
+
steps: PhaseStepResult[],
|
|
1173
|
+
): Promise<void> {
|
|
1174
|
+
try {
|
|
1175
|
+
for (const step of steps) {
|
|
1176
|
+
for (const result of step.planResults ?? []) {
|
|
1177
|
+
if (!result.success) {
|
|
1178
|
+
await recordPlanResultFailure(
|
|
1179
|
+
this.projectDir,
|
|
1180
|
+
{
|
|
1181
|
+
phaseNumber,
|
|
1182
|
+
phaseName,
|
|
1183
|
+
phaseDir,
|
|
1184
|
+
step: step.step,
|
|
1185
|
+
},
|
|
1186
|
+
result,
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
await recordPhaseArtifactFailureEvents(this.projectDir, {
|
|
1193
|
+
phaseNumber,
|
|
1194
|
+
phaseName,
|
|
1195
|
+
phaseDir,
|
|
1196
|
+
});
|
|
1197
|
+
} catch (err) {
|
|
1198
|
+
this.logger?.warn(
|
|
1199
|
+
`Failure memory capture failed for phase ${phaseNumber}: ${err instanceof Error ? err.message : String(err)}`,
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|