gsd-pi 2.65.0 → 2.66.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/dist/mcp-server.js +6 -2
- package/dist/resources/extensions/browser-tools/capture.js +20 -1
- package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +93 -0
- package/dist/resources/extensions/gsd/auto/finalize-timeout.js +2 -0
- package/dist/resources/extensions/gsd/auto/loop.js +2 -2
- package/dist/resources/extensions/gsd/auto/phases.js +48 -5
- package/dist/resources/extensions/gsd/auto/run-unit.js +13 -2
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto/types.js +2 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +2 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +99 -9
- package/dist/resources/extensions/gsd/auto-model-selection.js +7 -5
- package/dist/resources/extensions/gsd/auto-post-unit.js +17 -6
- package/dist/resources/extensions/gsd/auto-prompts.js +24 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +40 -22
- package/dist/resources/extensions/gsd/auto-start.js +175 -12
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +10 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +29 -7
- package/dist/resources/extensions/gsd/auto.js +21 -15
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -4
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +10 -0
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +6 -4
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +5 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -3
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +3 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +31 -1
- package/dist/resources/extensions/gsd/commands/context.js +8 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +23 -2
- package/dist/resources/extensions/gsd/commands-extensions.js +1 -1
- package/dist/resources/extensions/gsd/config-overlay.js +312 -0
- package/dist/resources/extensions/gsd/db-writer.js +13 -3
- package/dist/resources/extensions/gsd/detection.js +1 -1
- package/dist/resources/extensions/gsd/dispatch-guard.js +2 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -0
- package/dist/resources/extensions/gsd/doctor.js +2 -1
- package/dist/resources/extensions/gsd/files.js +17 -0
- package/dist/resources/extensions/gsd/gitignore.js +1 -0
- package/dist/resources/extensions/gsd/gsd-db.js +47 -4
- package/dist/resources/extensions/gsd/guided-flow.js +220 -29
- package/dist/resources/extensions/gsd/index.js +1 -1
- package/dist/resources/extensions/gsd/json-persistence.js +5 -2
- package/dist/resources/extensions/gsd/md-importer.js +14 -7
- package/dist/resources/extensions/gsd/notification-overlay.js +1 -1
- package/dist/resources/extensions/gsd/notification-widget.js +2 -1
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +1 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +17 -11
- package/dist/resources/extensions/gsd/pre-execution-checks.js +26 -5
- package/dist/resources/extensions/gsd/preferences-types.js +3 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +45 -1
- package/dist/resources/extensions/gsd/preferences.js +9 -2
- package/dist/resources/extensions/gsd/preparation.js +1092 -0
- package/dist/resources/extensions/gsd/prompt-validation.js +67 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +3 -3
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +424 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +6 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -4
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +23 -0
- package/dist/resources/extensions/gsd/prompts/queue.md +2 -0
- package/dist/resources/extensions/gsd/prompts/rethink.md +2 -1
- package/dist/resources/extensions/gsd/prompts/system.md +2 -2
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +56 -23
- package/dist/resources/extensions/gsd/quick.js +19 -15
- package/dist/resources/extensions/gsd/reactive-graph.js +12 -0
- package/dist/resources/extensions/gsd/roadmap-slices.js +24 -5
- package/dist/resources/extensions/gsd/safety/content-validator.js +3 -3
- package/dist/resources/extensions/gsd/session-lock.js +23 -1
- package/dist/resources/extensions/gsd/state.js +115 -28
- package/dist/resources/extensions/gsd/templates/context-enhanced.md +138 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +15 -3
- package/dist/resources/extensions/gsd/tools/complete-slice.js +27 -6
- package/dist/resources/extensions/gsd/tools/complete-task.js +31 -7
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +7 -5
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +5 -2
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +119 -0
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +30 -0
- package/dist/resources/extensions/gsd/tools/reopen-task.js +18 -0
- package/dist/resources/extensions/gsd/triage-resolution.js +33 -16
- package/dist/resources/extensions/gsd/undo.js +3 -2
- package/dist/resources/extensions/gsd/workflow-events.js +1 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-projections.js +7 -9
- package/dist/resources/extensions/gsd/workflow-reconcile.js +100 -9
- package/dist/resources/extensions/gsd/workflow-templates.js +11 -2
- package/dist/resources/extensions/gsd/worktree-manager.js +5 -2
- package/dist/resources/extensions/gsd/worktree.js +9 -0
- package/dist/resources/extensions/shared/interview-ui.js +1 -1
- package/dist/resources/extensions/subagent/agents.js +19 -5
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/6502.8874bcae249c02e1.js +9 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-a1c1e452c6b32d04.js → webpack-9fed74684e1c5bb1.js} +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +30 -19
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js +51 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +9 -9
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +10 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +20 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +15 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +18 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/retry-handler.test.ts +80 -0
- package/packages/pi-coding-agent/src/core/retry-handler.ts +37 -25
- package/packages/pi-coding-agent/src/core/sdk.ts +9 -9
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +10 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +20 -4
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +27 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +16 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +5 -0
- package/packages/pi-tui/dist/components/image.d.ts +2 -0
- package/packages/pi-tui/dist/components/image.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/image.js +4 -0
- package/packages/pi-tui/dist/components/image.js.map +1 -1
- package/packages/pi-tui/dist/components/image.test.d.ts +6 -0
- package/packages/pi-tui/dist/components/image.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/components/image.test.js +32 -0
- package/packages/pi-tui/dist/components/image.test.js.map +1 -0
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +3 -1
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/components/image.test.ts +36 -0
- package/packages/pi-tui/src/components/image.ts +5 -0
- package/packages/pi-tui/src/tui.ts +3 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/capture.ts +19 -1
- package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +93 -0
- package/src/resources/extensions/gsd/auto/finalize-timeout.ts +3 -0
- package/src/resources/extensions/gsd/auto/loop.ts +2 -2
- package/src/resources/extensions/gsd/auto/phases.ts +68 -3
- package/src/resources/extensions/gsd/auto/run-unit.ts +12 -2
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto/types.ts +5 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +2 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +110 -9
- package/src/resources/extensions/gsd/auto-model-selection.ts +7 -5
- package/src/resources/extensions/gsd/auto-post-unit.ts +16 -6
- package/src/resources/extensions/gsd/auto-prompts.ts +31 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +29 -23
- package/src/resources/extensions/gsd/auto-start.ts +188 -10
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +10 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +28 -7
- package/src/resources/extensions/gsd/auto.ts +19 -8
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +16 -4
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +10 -0
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +5 -4
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +11 -3
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +3 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +36 -1
- package/src/resources/extensions/gsd/commands/context.ts +7 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +26 -2
- package/src/resources/extensions/gsd/commands-extensions.ts +1 -1
- package/src/resources/extensions/gsd/config-overlay.ts +331 -0
- package/src/resources/extensions/gsd/db-writer.ts +11 -3
- package/src/resources/extensions/gsd/detection.ts +1 -1
- package/src/resources/extensions/gsd/dispatch-guard.ts +2 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -0
- package/src/resources/extensions/gsd/doctor.ts +2 -1
- package/src/resources/extensions/gsd/files.ts +19 -0
- package/src/resources/extensions/gsd/gitignore.ts +1 -0
- package/src/resources/extensions/gsd/gsd-db.ts +46 -4
- package/src/resources/extensions/gsd/guided-flow.ts +254 -30
- package/src/resources/extensions/gsd/index.ts +1 -0
- package/src/resources/extensions/gsd/json-persistence.ts +6 -3
- package/src/resources/extensions/gsd/md-importer.ts +13 -6
- package/src/resources/extensions/gsd/notification-overlay.ts +1 -1
- package/src/resources/extensions/gsd/notification-widget.ts +2 -1
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +1 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +19 -11
- package/src/resources/extensions/gsd/pre-execution-checks.ts +32 -7
- package/src/resources/extensions/gsd/preferences-types.ts +25 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +45 -1
- package/src/resources/extensions/gsd/preferences.ts +9 -2
- package/src/resources/extensions/gsd/preparation.ts +1419 -0
- package/src/resources/extensions/gsd/prompt-validation.ts +88 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +3 -3
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss-prepared.md +424 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +6 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -4
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +23 -0
- package/src/resources/extensions/gsd/prompts/queue.md +2 -0
- package/src/resources/extensions/gsd/prompts/rethink.md +2 -1
- package/src/resources/extensions/gsd/prompts/system.md +2 -2
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +56 -23
- package/src/resources/extensions/gsd/quick.ts +20 -15
- package/src/resources/extensions/gsd/reactive-graph.ts +18 -0
- package/src/resources/extensions/gsd/roadmap-slices.ts +21 -5
- package/src/resources/extensions/gsd/safety/content-validator.ts +3 -3
- package/src/resources/extensions/gsd/session-lock.ts +17 -1
- package/src/resources/extensions/gsd/state.ts +115 -26
- package/src/resources/extensions/gsd/templates/context-enhanced.md +138 -0
- package/src/resources/extensions/gsd/tests/adversarial-review-fixes.test.ts +223 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +33 -2
- package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +72 -0
- package/src/resources/extensions/gsd/tests/complete-task-normalize-lists.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/defer-milestone-stamp.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard-closed-status.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/error-success-mask.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/frontmatter-parse-noise.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/guided-flow-state-rebuild.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +11 -9
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +28 -30
- package/src/resources/extensions/gsd/tests/integration/test-isolation.ts +53 -0
- package/src/resources/extensions/gsd/tests/integration-prepared-discussion.test.ts +525 -0
- package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +11 -10
- package/src/resources/extensions/gsd/tests/needs-remediation-revalidation.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/note-captures-executed.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +189 -0
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +77 -0
- package/src/resources/extensions/gsd/tests/phantom-ghost-detection.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/phantom-milestone-default-queued.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +284 -20
- package/src/resources/extensions/gsd/tests/pre-execution-fail-closed.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/preparation.test.ts +1211 -0
- package/src/resources/extensions/gsd/tests/project-root-cwd-crash.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/projection-no-plan-overwrite.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/prompt-builder.test.ts +669 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +7 -4
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +4 -5
- package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/show-config-command.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/skip-slice-state-rebuild.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/slice-sequence-insert.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/stale-lockfile-recovery.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +147 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +6 -7
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/subagent-agent-discovery.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/symlink-extension-discovery.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +29 -1
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +3 -4
- package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/wave1-critical-regressions.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/wave2-events-regressions.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/wave3-session-regressions.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/wave4-write-safety-regressions.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/wave5-consistency-regressions.test.ts +165 -0
- package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +16 -17
- package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +13 -9
- package/src/resources/extensions/gsd/tests/worktree.test.ts +26 -9
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +127 -2
- package/src/resources/extensions/gsd/tests/zero-slice-roadmap-guided.test.ts +19 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +13 -3
- package/src/resources/extensions/gsd/tools/complete-slice.ts +26 -6
- package/src/resources/extensions/gsd/tools/complete-task.ts +29 -7
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +11 -9
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +5 -2
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +152 -0
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +27 -0
- package/src/resources/extensions/gsd/tools/reopen-task.ts +17 -0
- package/src/resources/extensions/gsd/triage-resolution.ts +37 -17
- package/src/resources/extensions/gsd/types.ts +4 -0
- package/src/resources/extensions/gsd/undo.ts +3 -2
- package/src/resources/extensions/gsd/workflow-events.ts +5 -3
- package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-projections.ts +7 -8
- package/src/resources/extensions/gsd/workflow-reconcile.ts +109 -8
- package/src/resources/extensions/gsd/workflow-templates.ts +11 -2
- package/src/resources/extensions/gsd/worktree-manager.ts +4 -2
- package/src/resources/extensions/gsd/worktree.ts +10 -0
- package/src/resources/extensions/shared/interview-ui.ts +1 -1
- package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +8 -10
- package/src/resources/extensions/subagent/agents.ts +30 -6
- package/dist/web/standalone/.next/static/chunks/6502.7593d7797a4b3999.js +0 -9
- /package/dist/web/standalone/.next/static/{MRM3OSYIAa4HMDqVGQ9nt → Bdk1mnQugYZh7ZxuXUYvc}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{MRM3OSYIAa4HMDqVGQ9nt → Bdk1mnQugYZh7ZxuXUYvc}/_ssgManifest.js +0 -0
|
@@ -357,6 +357,7 @@ function initSchema(db, fileBacked) {
|
|
|
357
357
|
db.exec("CREATE INDEX IF NOT EXISTS idx_milestones_status ON milestones(status)");
|
|
358
358
|
db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
|
|
359
359
|
db.exec("CREATE INDEX IF NOT EXISTS idx_verification_evidence_task ON verification_evidence(milestone_id, slice_id, task_id)");
|
|
360
|
+
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_verification_evidence_dedup ON verification_evidence(task_id, slice_id, milestone_id, command, verdict)");
|
|
360
361
|
// v14 index — slice dependency lookups
|
|
361
362
|
db.exec("CREATE INDEX IF NOT EXISTS idx_slice_deps_target ON slice_dependencies(milestone_id, depends_on_slice_id)");
|
|
362
363
|
db.exec(`CREATE VIEW IF NOT EXISTS active_decisions AS SELECT * FROM decisions WHERE superseded_by IS NULL`);
|
|
@@ -390,6 +391,28 @@ function migrateSchema(db) {
|
|
|
390
391
|
const currentVersion = row ? row["v"] : 0;
|
|
391
392
|
if (currentVersion >= SCHEMA_VERSION)
|
|
392
393
|
return;
|
|
394
|
+
// Backup database before migration so a mid-migration crash doesn't
|
|
395
|
+
// leave a partially-migrated DB with no recovery path.
|
|
396
|
+
// WAL-safe: checkpoint first to flush WAL into the main DB file, then copy.
|
|
397
|
+
if (currentPath && currentPath !== ":memory:" && existsSync(currentPath)) {
|
|
398
|
+
try {
|
|
399
|
+
const backupPath = `${currentPath}.backup-v${currentVersion}`;
|
|
400
|
+
if (!existsSync(backupPath)) {
|
|
401
|
+
// Flush WAL to main DB file before copying — without this, the backup
|
|
402
|
+
// may be missing committed data that only exists in the -wal file.
|
|
403
|
+
try {
|
|
404
|
+
db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
405
|
+
}
|
|
406
|
+
catch { /* checkpoint is best-effort */ }
|
|
407
|
+
copyFileSync(currentPath, backupPath);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
catch (backupErr) {
|
|
411
|
+
// Log but proceed — blocking migration leaves the DB stuck at an old
|
|
412
|
+
// schema version permanently on read-only or full filesystems.
|
|
413
|
+
logWarning("db", `Pre-migration backup failed: ${backupErr instanceof Error ? backupErr.message : String(backupErr)}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
393
416
|
db.exec("BEGIN");
|
|
394
417
|
try {
|
|
395
418
|
if (currentVersion < 2) {
|
|
@@ -644,6 +667,7 @@ function migrateSchema(db) {
|
|
|
644
667
|
db.exec("CREATE INDEX IF NOT EXISTS idx_milestones_status ON milestones(status)");
|
|
645
668
|
db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
|
|
646
669
|
db.exec("CREATE INDEX IF NOT EXISTS idx_verification_evidence_task ON verification_evidence(milestone_id, slice_id, task_id)");
|
|
670
|
+
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_verification_evidence_dedup ON verification_evidence(task_id, slice_id, milestone_id, command, verdict)");
|
|
647
671
|
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
648
672
|
":version": 13,
|
|
649
673
|
":applied_at": new Date().toISOString(),
|
|
@@ -935,8 +959,20 @@ export function _resetProvider() {
|
|
|
935
959
|
export function upsertDecision(d) {
|
|
936
960
|
if (!currentDb)
|
|
937
961
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
938
|
-
|
|
939
|
-
|
|
962
|
+
// Use ON CONFLICT DO UPDATE instead of INSERT OR REPLACE to preserve the
|
|
963
|
+
// seq column. INSERT OR REPLACE deletes then reinserts, resetting seq and
|
|
964
|
+
// corrupting decision ordering in DECISIONS.md after reconcile replay.
|
|
965
|
+
currentDb.prepare(`INSERT INTO decisions (id, when_context, scope, decision, choice, rationale, revisable, made_by, superseded_by)
|
|
966
|
+
VALUES (:id, :when_context, :scope, :decision, :choice, :rationale, :revisable, :made_by, :superseded_by)
|
|
967
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
968
|
+
when_context = excluded.when_context,
|
|
969
|
+
scope = excluded.scope,
|
|
970
|
+
decision = excluded.decision,
|
|
971
|
+
choice = excluded.choice,
|
|
972
|
+
rationale = excluded.rationale,
|
|
973
|
+
revisable = excluded.revisable,
|
|
974
|
+
made_by = excluded.made_by,
|
|
975
|
+
superseded_by = excluded.superseded_by`).run({
|
|
940
976
|
":id": d.id,
|
|
941
977
|
":when_context": d.when_context,
|
|
942
978
|
":scope": d.scope,
|
|
@@ -1007,7 +1043,9 @@ export function insertMilestone(m) {
|
|
|
1007
1043
|
)`).run({
|
|
1008
1044
|
":id": m.id,
|
|
1009
1045
|
":title": m.title ?? "",
|
|
1010
|
-
"
|
|
1046
|
+
// Default to "queued" — never auto-create milestones as "active" (#3380).
|
|
1047
|
+
// Callers that need "active" must pass it explicitly.
|
|
1048
|
+
":status": m.status ?? "queued",
|
|
1011
1049
|
":depends_on": JSON.stringify(m.depends_on ?? []),
|
|
1012
1050
|
":created_at": new Date().toISOString(),
|
|
1013
1051
|
":vision": m.planning?.vision ?? "",
|
|
@@ -1198,6 +1236,11 @@ export function updateTaskStatus(milestoneId, sliceId, taskId, status, completed
|
|
|
1198
1236
|
":id": taskId,
|
|
1199
1237
|
});
|
|
1200
1238
|
}
|
|
1239
|
+
export function setTaskBlockerDiscovered(milestoneId, sliceId, taskId, discovered) {
|
|
1240
|
+
if (!currentDb)
|
|
1241
|
+
return;
|
|
1242
|
+
currentDb.prepare(`UPDATE tasks SET blocker_discovered = :discovered WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`).run({ ":discovered": discovered ? 1 : 0, ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
1243
|
+
}
|
|
1201
1244
|
export function upsertTaskPlanning(milestoneId, sliceId, taskId, planning) {
|
|
1202
1245
|
if (!currentDb)
|
|
1203
1246
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
@@ -1323,7 +1366,7 @@ export function getSliceTasks(milestoneId, sliceId) {
|
|
|
1323
1366
|
export function insertVerificationEvidence(e) {
|
|
1324
1367
|
if (!currentDb)
|
|
1325
1368
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1326
|
-
currentDb.prepare(`INSERT INTO verification_evidence (task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at)
|
|
1369
|
+
currentDb.prepare(`INSERT OR IGNORE INTO verification_evidence (task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at)
|
|
1327
1370
|
VALUES (:task_id, :slice_id, :milestone_id, :command, :exit_code, :verdict, :duration_ms, :created_at)`).run({
|
|
1328
1371
|
":task_id": e.taskId,
|
|
1329
1372
|
":slice_id": e.sliceId,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* No execution state, no hooks, no tools — the LLM does the rest.
|
|
7
7
|
*/
|
|
8
8
|
import { showNextAction } from "../shared/tui.js";
|
|
9
|
-
import { loadFile } from "./files.js";
|
|
9
|
+
import { loadFile, saveFile } from "./files.js";
|
|
10
10
|
import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js";
|
|
11
11
|
import { parseRoadmapSlices } from "./roadmap-slices.js";
|
|
12
12
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
@@ -34,6 +34,19 @@ import { findMilestoneIds, nextMilestoneId, reserveMilestoneId, getReservedMiles
|
|
|
34
34
|
import { parkMilestone, discardMilestone } from "./milestone-actions.js";
|
|
35
35
|
import { selectAndApplyModel } from "./auto-model-selection.js";
|
|
36
36
|
import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
|
|
37
|
+
import { runPreparation, formatCodebaseBrief, formatPriorContextBrief, formatEcosystemBrief, } from "./preparation.js";
|
|
38
|
+
// ─── Preparation result storage ─────────────────────────────────────────────
|
|
39
|
+
// Stores the most recent preparation result for injection into discuss prompts.
|
|
40
|
+
// S02 will consume this when building the prepared discussion prompt.
|
|
41
|
+
let lastPreparationResult = null;
|
|
42
|
+
/** Get the most recent preparation result (for S02 prompt building). */
|
|
43
|
+
export function getLastPreparationResult() {
|
|
44
|
+
return lastPreparationResult;
|
|
45
|
+
}
|
|
46
|
+
/** Clear the preparation result (called after discussion completes). */
|
|
47
|
+
export function clearPreparationResult() {
|
|
48
|
+
lastPreparationResult = null;
|
|
49
|
+
}
|
|
37
50
|
// ─── Re-exports (preserve public API for existing importers) ────────────────
|
|
38
51
|
export { MILESTONE_ID_RE, generateMilestoneSuffix, nextMilestoneId, extractMilestoneSeq, parseMilestoneId, milestoneIdSort, maxMilestoneNum, findMilestoneIds, reserveMilestoneId, claimReservedId, getReservedMilestoneIds, clearReservedMilestoneIds, } from "./milestone-ids.js";
|
|
39
52
|
export { showQueue, handleQueueReorder, showQueueAdd, buildExistingMilestonesContext, } from "./guided-flow-queue.js";
|
|
@@ -74,7 +87,7 @@ function _getPendingAutoStart(basePath) {
|
|
|
74
87
|
* Exported for testing (#2985).
|
|
75
88
|
*/
|
|
76
89
|
export function setPendingAutoStart(basePath, entry) {
|
|
77
|
-
pendingAutoStartMap.set(basePath, entry);
|
|
90
|
+
pendingAutoStartMap.set(basePath, { createdAt: Date.now(), ...entry });
|
|
78
91
|
}
|
|
79
92
|
/**
|
|
80
93
|
* Clear pending auto-start state.
|
|
@@ -248,8 +261,10 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType)
|
|
|
248
261
|
// "Grammar is too complex" when the combined tool schema is too large.
|
|
249
262
|
// Discuss flows only need a small subset of GSD tools — strip the heavy
|
|
250
263
|
// planning/execution/completion tools to keep the grammar within limits.
|
|
264
|
+
let savedTools = null;
|
|
251
265
|
if (unitType?.startsWith("discuss-")) {
|
|
252
266
|
const currentTools = pi.getActiveTools();
|
|
267
|
+
savedTools = currentTools;
|
|
253
268
|
// Keep all non-GSD tools (builtins, other extensions) and only the
|
|
254
269
|
// GSD tools on the discuss allowlist.
|
|
255
270
|
const scopedTools = currentTools.filter((t) => !t.startsWith("gsd_") || DISCUSS_TOOLS_ALLOWLIST.includes(t));
|
|
@@ -268,6 +283,12 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType)
|
|
|
268
283
|
content: `Read the following GSD workflow protocol and execute exactly.\n\n${workflow}\n\n## Your Task\n\n${note}`,
|
|
269
284
|
display: false,
|
|
270
285
|
}, { triggerTurn: true });
|
|
286
|
+
// Restore full tool set after the message is queued. The LLM turn has
|
|
287
|
+
// already captured the scoped set — restoring prevents the narrowed
|
|
288
|
+
// tools from leaking into subsequent dispatches (#3628).
|
|
289
|
+
if (savedTools) {
|
|
290
|
+
pi.setActiveTools(savedTools);
|
|
291
|
+
}
|
|
271
292
|
}
|
|
272
293
|
/**
|
|
273
294
|
* Resolve a model ID string to a model object from available models.
|
|
@@ -340,6 +361,86 @@ function buildHeadlessDiscussPrompt(nextId, seedContext, _basePath) {
|
|
|
340
361
|
multiMilestoneCommitInstruction: buildDocsCommitInstruction("docs: project plan — N milestones"),
|
|
341
362
|
});
|
|
342
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Build the prepared discuss prompt with brief injection.
|
|
366
|
+
* Uses the discuss-prepared template which encodes the 4-layer discussion protocol.
|
|
367
|
+
*
|
|
368
|
+
* @param nextId - The milestone ID being discussed
|
|
369
|
+
* @param preamble - Preamble text for the discuss prompt
|
|
370
|
+
* @param _basePath - Root directory of the project (unused, kept for signature consistency)
|
|
371
|
+
* @param prepResult - Preparation result containing briefs to inject
|
|
372
|
+
* @returns The prepared discuss prompt string
|
|
373
|
+
*/
|
|
374
|
+
function buildPreparedPrompt(nextId, preamble, _basePath, prepResult) {
|
|
375
|
+
const milestoneRel = `.gsd/milestones/${nextId}`;
|
|
376
|
+
// Use context-enhanced instead of context for prepared discussions
|
|
377
|
+
const inlinedTemplates = [
|
|
378
|
+
inlineTemplate("project", "Project"),
|
|
379
|
+
inlineTemplate("requirements", "Requirements"),
|
|
380
|
+
inlineTemplate("context-enhanced", "Context Enhanced"),
|
|
381
|
+
inlineTemplate("roadmap", "Roadmap"),
|
|
382
|
+
inlineTemplate("decisions", "Decisions"),
|
|
383
|
+
].join("\n\n---\n\n");
|
|
384
|
+
// Format the briefs from the preparation result
|
|
385
|
+
const codebaseBrief = prepResult.codebaseBrief || formatCodebaseBrief(prepResult.codebase);
|
|
386
|
+
const priorContextBrief = prepResult.priorContextBrief || formatPriorContextBrief(prepResult.priorContext);
|
|
387
|
+
const ecosystemBrief = prepResult.ecosystemBrief || formatEcosystemBrief(prepResult.ecosystem);
|
|
388
|
+
return loadPrompt("discuss-prepared", {
|
|
389
|
+
milestoneId: nextId,
|
|
390
|
+
preamble,
|
|
391
|
+
codebaseBrief,
|
|
392
|
+
priorContextBrief,
|
|
393
|
+
ecosystemBrief,
|
|
394
|
+
contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
|
|
395
|
+
roadmapPath: `${milestoneRel}/${nextId}-ROADMAP.md`,
|
|
396
|
+
inlinedTemplates,
|
|
397
|
+
commitInstruction: buildDocsCommitInstruction(`docs(${nextId}): context, requirements, and roadmap`),
|
|
398
|
+
multiMilestoneCommitInstruction: buildDocsCommitInstruction("docs: project plan — N milestones"),
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Run preparation phase if enabled, then build the discuss prompt.
|
|
403
|
+
* This is the main entry point for new milestone discussions with preparation.
|
|
404
|
+
* Stores the preparation result for S02 to inject into the discuss prompt.
|
|
405
|
+
*
|
|
406
|
+
* When preparation succeeds, uses the discuss-prepared template with brief injection.
|
|
407
|
+
* Falls back to the standard discuss template when preparation is disabled or fails.
|
|
408
|
+
*
|
|
409
|
+
* @param ctx - Extension command context with UI for progress notifications
|
|
410
|
+
* @param nextId - The milestone ID being discussed
|
|
411
|
+
* @param preamble - Preamble text for the discuss prompt
|
|
412
|
+
* @param basePath - Root directory of the project
|
|
413
|
+
* @returns The discuss prompt string
|
|
414
|
+
*/
|
|
415
|
+
async function prepareAndBuildDiscussPrompt(ctx, nextId, preamble, basePath) {
|
|
416
|
+
// Clear stale preparation result immediately to prevent cross-session/project
|
|
417
|
+
// state leaks. This ensures data from a prior milestone/project never leaks
|
|
418
|
+
// into subsequent discussions (adversarial review fix #3602).
|
|
419
|
+
lastPreparationResult = null;
|
|
420
|
+
const prefs = loadEffectiveGSDPreferences()?.preferences ?? {};
|
|
421
|
+
// Run preparation if enabled (default: true)
|
|
422
|
+
if (prefs.discuss_preparation !== false) {
|
|
423
|
+
try {
|
|
424
|
+
const prepResult = await runPreparation(basePath, ctx.ui, {
|
|
425
|
+
discuss_preparation: prefs.discuss_preparation,
|
|
426
|
+
discuss_web_research: prefs.discuss_web_research,
|
|
427
|
+
discuss_depth: prefs.discuss_depth,
|
|
428
|
+
});
|
|
429
|
+
lastPreparationResult = prepResult;
|
|
430
|
+
// Use prepared prompt if preparation was enabled and produced results
|
|
431
|
+
if (prepResult.enabled) {
|
|
432
|
+
return buildPreparedPrompt(nextId, preamble, basePath, prepResult);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
// If preparation throws, ensure stale data doesn't persist
|
|
437
|
+
lastPreparationResult = null;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// Fall back to standard discuss prompt for backward compatibility
|
|
441
|
+
// lastPreparationResult is already null (cleared at entry or on error)
|
|
442
|
+
return buildDiscussPrompt(nextId, preamble, basePath);
|
|
443
|
+
}
|
|
343
444
|
/**
|
|
344
445
|
* Bootstrap a .gsd/ project from scratch for headless use.
|
|
345
446
|
* Ensures git repo, .gsd/ structure, gitignore, and preferences all exist.
|
|
@@ -376,7 +477,7 @@ export async function showHeadlessMilestoneCreation(ctx, pi, basePath, seedConte
|
|
|
376
477
|
// Build and dispatch the headless discuss prompt
|
|
377
478
|
const prompt = buildHeadlessDiscussPrompt(nextId, seedContext, basePath);
|
|
378
479
|
// Set pending auto start (auto-mode triggers on "Milestone X ready." via checkAutoStartAfterDiscuss)
|
|
379
|
-
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId });
|
|
480
|
+
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, createdAt: Date.now() });
|
|
380
481
|
// Dispatch — headless milestone creation is a planning activity
|
|
381
482
|
await dispatchWorkflow(pi, prompt, "gsd-run", ctx, "plan-milestone");
|
|
382
483
|
}
|
|
@@ -457,6 +558,7 @@ async function buildDiscussSlicePrompt(mid, sid, sTitle, base, options) {
|
|
|
457
558
|
contextPath: sliceContextPath,
|
|
458
559
|
projectRoot: base,
|
|
459
560
|
inlinedTemplates,
|
|
561
|
+
structuredQuestionsAvailable: options?.structuredQuestionsAvailable ?? "false",
|
|
460
562
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}/${sid}): slice context from discuss`),
|
|
461
563
|
});
|
|
462
564
|
}
|
|
@@ -474,6 +576,16 @@ export async function showDiscuss(ctx, pi, basePath) {
|
|
|
474
576
|
// Invalidate caches to pick up artifacts written by a just-completed discuss/plan
|
|
475
577
|
invalidateAllCaches();
|
|
476
578
|
const state = await deriveState(basePath);
|
|
579
|
+
// Rebuild STATE.md from derived state before any dispatch (#3475).
|
|
580
|
+
// Without this, guided prompts read a stale STATE.md cache and the
|
|
581
|
+
// agent bootstraps from the wrong milestone.
|
|
582
|
+
try {
|
|
583
|
+
const { buildStateMarkdown } = await import("./doctor.js");
|
|
584
|
+
await saveFile(resolveGsdRootFile(basePath, "STATE"), buildStateMarkdown(state));
|
|
585
|
+
}
|
|
586
|
+
catch (err) {
|
|
587
|
+
logWarning("guided", `STATE.md rebuild failed: ${err.message}`);
|
|
588
|
+
}
|
|
477
589
|
// No active milestone (or corrupted milestone with undefined id) —
|
|
478
590
|
// check for pending milestones to discuss instead
|
|
479
591
|
if (!state.activeMilestone?.id) {
|
|
@@ -521,28 +633,30 @@ export async function showDiscuss(ctx, pi, basePath) {
|
|
|
521
633
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
522
634
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
523
635
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
|
|
636
|
+
fastPathInstruction: "",
|
|
524
637
|
});
|
|
525
638
|
const seed = draftContent
|
|
526
639
|
? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
|
|
527
640
|
: basePrompt;
|
|
528
|
-
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
|
|
641
|
+
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
|
|
529
642
|
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
|
|
530
643
|
}
|
|
531
644
|
else if (choice === "discuss_fresh") {
|
|
532
645
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
533
646
|
const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
|
|
534
|
-
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
|
|
647
|
+
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
|
|
535
648
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
536
649
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
537
650
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
|
|
651
|
+
fastPathInstruction: "",
|
|
538
652
|
}), "gsd-discuss", ctx, "discuss-milestone");
|
|
539
653
|
}
|
|
540
654
|
else if (choice === "skip_milestone") {
|
|
541
655
|
const milestoneIds = findMilestoneIds(basePath);
|
|
542
656
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
543
657
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
544
|
-
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false });
|
|
545
|
-
await dispatchWorkflow(pi,
|
|
658
|
+
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false, createdAt: Date.now() });
|
|
659
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
546
660
|
}
|
|
547
661
|
return;
|
|
548
662
|
}
|
|
@@ -672,7 +786,8 @@ export async function showDiscuss(ctx, pi, basePath) {
|
|
|
672
786
|
if (confirm !== "rediscuss")
|
|
673
787
|
continue;
|
|
674
788
|
}
|
|
675
|
-
const
|
|
789
|
+
const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
|
|
790
|
+
const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss, structuredQuestionsAvailable: sqAvail });
|
|
676
791
|
await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice");
|
|
677
792
|
// Wait for the discuss session to finish, then loop back to the picker
|
|
678
793
|
await ctx.waitForIdle();
|
|
@@ -712,16 +827,55 @@ async function showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones)
|
|
|
712
827
|
const chosen = pendingMilestones.find(m => m.id === choice);
|
|
713
828
|
if (!chosen)
|
|
714
829
|
return;
|
|
715
|
-
|
|
830
|
+
const hasDraft = !!resolveMilestoneFile(basePath, chosen.id, "CONTEXT-DRAFT");
|
|
831
|
+
let fastPath = hasDraft;
|
|
832
|
+
if (!hasDraft) {
|
|
833
|
+
const mode = await showNextAction(ctx, {
|
|
834
|
+
title: `Discuss ${chosen.id}`,
|
|
835
|
+
summary: [
|
|
836
|
+
"Choose how to start the discussion.",
|
|
837
|
+
"Fast path skips generic scouting — use it when you already know the scope.",
|
|
838
|
+
],
|
|
839
|
+
actions: [
|
|
840
|
+
{
|
|
841
|
+
id: "full",
|
|
842
|
+
label: "Full discussion",
|
|
843
|
+
description: "Scout the codebase, ask open-ended questions, explore deeply",
|
|
844
|
+
recommended: true,
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
id: "fast",
|
|
848
|
+
label: "I have the scope — fast path",
|
|
849
|
+
description: "Treat your first message as authoritative seed context; skip scouting",
|
|
850
|
+
},
|
|
851
|
+
],
|
|
852
|
+
notYetMessage: "Run /gsd discuss when ready.",
|
|
853
|
+
});
|
|
854
|
+
if (mode === "not_yet")
|
|
855
|
+
return;
|
|
856
|
+
fastPath = mode === "fast";
|
|
857
|
+
}
|
|
858
|
+
await dispatchDiscussForMilestone(ctx, pi, basePath, chosen.id, chosen.title, { fastPath });
|
|
716
859
|
}
|
|
717
860
|
/**
|
|
718
861
|
* Dispatch the guided-discuss-milestone prompt for a milestone without
|
|
719
862
|
* setting pendingAutoStart — so discussing a queued milestone does not
|
|
720
863
|
* implicitly activate it when the session ends.
|
|
721
864
|
*/
|
|
722
|
-
async function dispatchDiscussForMilestone(ctx, pi, basePath, mid, milestoneTitle) {
|
|
865
|
+
async function dispatchDiscussForMilestone(ctx, pi, basePath, mid, milestoneTitle, opts = {}) {
|
|
723
866
|
const draftFile = resolveMilestoneFile(basePath, mid, "CONTEXT-DRAFT");
|
|
724
867
|
const draftContent = draftFile ? await loadFile(draftFile) : null;
|
|
868
|
+
const hasSeed = !!(draftContent || opts.fastPath);
|
|
869
|
+
const fastPathInstruction = hasSeed
|
|
870
|
+
? [
|
|
871
|
+
"> **Fast path active — scope provided.**",
|
|
872
|
+
"> Do NOT perform a generic codebase scouting pass.",
|
|
873
|
+
"> Do at most 2 targeted reads to check for obvious conflicts with existing work.",
|
|
874
|
+
"> Treat the seed context or the operator's first message as authoritative.",
|
|
875
|
+
"> Move directly to the depth summary and write step.",
|
|
876
|
+
"> Ask only questions where the answer would materially change scope.",
|
|
877
|
+
].join("\n")
|
|
878
|
+
: "";
|
|
725
879
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
726
880
|
const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
|
|
727
881
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
@@ -730,6 +884,7 @@ async function dispatchDiscussForMilestone(ctx, pi, basePath, mid, milestoneTitl
|
|
|
730
884
|
inlinedTemplates: discussMilestoneTemplates,
|
|
731
885
|
structuredQuestionsAvailable,
|
|
732
886
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
|
|
887
|
+
fastPathInstruction,
|
|
733
888
|
});
|
|
734
889
|
const prompt = draftContent
|
|
735
890
|
? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
|
|
@@ -856,8 +1011,8 @@ async function handleMilestoneActions(ctx, pi, basePath, milestoneId, milestoneT
|
|
|
856
1011
|
const milestoneIds = findMilestoneIds(basePath);
|
|
857
1012
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
858
1013
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
859
|
-
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
860
|
-
await dispatchWorkflow(pi,
|
|
1014
|
+
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1015
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
861
1016
|
return true;
|
|
862
1017
|
}
|
|
863
1018
|
// "back" or null
|
|
@@ -955,14 +1110,35 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
955
1110
|
}
|
|
956
1111
|
}
|
|
957
1112
|
const state = await deriveState(basePath);
|
|
1113
|
+
// Rebuild STATE.md from derived state before any dispatch (#3475).
|
|
1114
|
+
try {
|
|
1115
|
+
const { buildStateMarkdown } = await import("./doctor.js");
|
|
1116
|
+
await saveFile(resolveGsdRootFile(basePath, "STATE"), buildStateMarkdown(state));
|
|
1117
|
+
}
|
|
1118
|
+
catch (err) {
|
|
1119
|
+
logWarning("guided", `STATE.md rebuild failed: ${err.message}`);
|
|
1120
|
+
}
|
|
958
1121
|
if (!state.activeMilestone?.id) {
|
|
959
1122
|
// Guard: if a discuss session is already in flight, don't re-inject the prompt.
|
|
960
1123
|
// Both /gsd and /gsd auto reach this branch when no milestone exists yet.
|
|
961
1124
|
// Without this guard, every subsequent /gsd call overwrites the pending auto-start
|
|
962
1125
|
// and fires another dispatchWorkflow, resetting the conversation mid-interview.
|
|
963
1126
|
if (pendingAutoStartMap.has(basePath)) {
|
|
964
|
-
|
|
965
|
-
|
|
1127
|
+
// #3274: If /clear interrupted the discussion, the pending entry is stale.
|
|
1128
|
+
// Detect staleness: no manifest, no CONTEXT.md, AND entry is older than
|
|
1129
|
+
// 30s (avoids race between .set() and LLM writing first artifact).
|
|
1130
|
+
const entry = pendingAutoStartMap.get(basePath);
|
|
1131
|
+
const ageMs = Date.now() - (entry.createdAt || 0);
|
|
1132
|
+
const manifestExists = existsSync(join(gsdRoot(basePath), "DISCUSSION-MANIFEST.json"));
|
|
1133
|
+
const milestoneHasContext = existsSync(join(gsdRoot(basePath), "milestones", entry.milestoneId, `${entry.milestoneId}-CONTEXT.md`));
|
|
1134
|
+
if (!manifestExists && !milestoneHasContext && ageMs > 30_000) {
|
|
1135
|
+
// Stale entry from an interrupted discussion — clear and continue
|
|
1136
|
+
pendingAutoStartMap.delete(basePath);
|
|
1137
|
+
}
|
|
1138
|
+
else {
|
|
1139
|
+
ctx.ui.notify("Discussion already in progress — answer the question above to continue.", "info");
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
966
1142
|
}
|
|
967
1143
|
const milestoneIds = findMilestoneIds(basePath);
|
|
968
1144
|
// Sanity check (#456): if findMilestoneIds returns [] but the milestones
|
|
@@ -989,8 +1165,8 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
989
1165
|
const isFirst = milestoneIds.length === 0;
|
|
990
1166
|
if (isFirst) {
|
|
991
1167
|
// First ever — skip wizard, just ask directly
|
|
992
|
-
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
993
|
-
await dispatchWorkflow(pi,
|
|
1168
|
+
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1169
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
994
1170
|
}
|
|
995
1171
|
else {
|
|
996
1172
|
const choice = await showNextAction(ctx, {
|
|
@@ -1007,8 +1183,8 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1007
1183
|
notYetMessage: "Run /gsd when ready.",
|
|
1008
1184
|
});
|
|
1009
1185
|
if (choice === "new_milestone") {
|
|
1010
|
-
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1011
|
-
await dispatchWorkflow(pi,
|
|
1186
|
+
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1187
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1012
1188
|
}
|
|
1013
1189
|
}
|
|
1014
1190
|
return;
|
|
@@ -1039,8 +1215,8 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1039
1215
|
const milestoneIds = findMilestoneIds(basePath);
|
|
1040
1216
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1041
1217
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
1042
|
-
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1043
|
-
await dispatchWorkflow(pi,
|
|
1218
|
+
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1219
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1044
1220
|
}
|
|
1045
1221
|
else if (choice === "status") {
|
|
1046
1222
|
const { fireStatusViaCommand } = await import("./commands.js");
|
|
@@ -1081,28 +1257,30 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1081
1257
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
1082
1258
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1083
1259
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
1260
|
+
fastPathInstruction: "",
|
|
1084
1261
|
});
|
|
1085
1262
|
const seed = draftContent
|
|
1086
1263
|
? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
|
|
1087
1264
|
: basePrompt;
|
|
1088
|
-
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
1265
|
+
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
|
|
1089
1266
|
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
|
|
1090
1267
|
}
|
|
1091
1268
|
else if (choice === "discuss_fresh") {
|
|
1092
1269
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1093
1270
|
const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
|
|
1094
|
-
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
1271
|
+
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
|
|
1095
1272
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1096
1273
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1097
1274
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
1275
|
+
fastPathInstruction: "",
|
|
1098
1276
|
}), "gsd-discuss", ctx, "discuss-milestone");
|
|
1099
1277
|
}
|
|
1100
1278
|
else if (choice === "skip_milestone") {
|
|
1101
1279
|
const milestoneIds = findMilestoneIds(basePath);
|
|
1102
1280
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1103
1281
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
1104
|
-
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1105
|
-
await dispatchWorkflow(pi,
|
|
1282
|
+
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1283
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1106
1284
|
}
|
|
1107
1285
|
return;
|
|
1108
1286
|
}
|
|
@@ -1110,7 +1288,18 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1110
1288
|
if (!state.activeSlice) {
|
|
1111
1289
|
const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
1112
1290
|
const hasRoadmap = !!(roadmapFile && await loadFile(roadmapFile));
|
|
1113
|
-
|
|
1291
|
+
// A roadmap file with zero parseable slices (placeholder text) should be
|
|
1292
|
+
// treated the same as no roadmap — offer "Create roadmap" instead of "Go auto"
|
|
1293
|
+
// which would immediately get stuck in blocked state (#3441).
|
|
1294
|
+
let roadmapHasSlices = false;
|
|
1295
|
+
if (hasRoadmap) {
|
|
1296
|
+
const roadmapContent = await loadFile(roadmapFile);
|
|
1297
|
+
if (roadmapContent) {
|
|
1298
|
+
const parsed = parseRoadmapSlices(roadmapContent);
|
|
1299
|
+
roadmapHasSlices = parsed.length > 0;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
if (!hasRoadmap || !roadmapHasSlices) {
|
|
1114
1303
|
// No roadmap → discuss or plan
|
|
1115
1304
|
const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
|
|
1116
1305
|
const hasContext = !!(contextFile && await loadFile(contextFile));
|
|
@@ -1146,7 +1335,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1146
1335
|
notYetMessage: "Run /gsd when ready.",
|
|
1147
1336
|
});
|
|
1148
1337
|
if (choice === "plan") {
|
|
1149
|
-
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
1338
|
+
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
|
|
1150
1339
|
const planMilestoneTemplates = [
|
|
1151
1340
|
inlineTemplate("roadmap", "Roadmap"),
|
|
1152
1341
|
inlineTemplate("plan", "Slice Plan"),
|
|
@@ -1173,14 +1362,15 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1173
1362
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1174
1363
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1175
1364
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
1365
|
+
fastPathInstruction: "",
|
|
1176
1366
|
}), "gsd-run", ctx, "discuss-milestone");
|
|
1177
1367
|
}
|
|
1178
1368
|
else if (choice === "skip_milestone") {
|
|
1179
1369
|
const milestoneIds = findMilestoneIds(basePath);
|
|
1180
1370
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1181
1371
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds);
|
|
1182
|
-
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1183
|
-
await dispatchWorkflow(pi,
|
|
1372
|
+
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode, createdAt: Date.now() });
|
|
1373
|
+
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1184
1374
|
}
|
|
1185
1375
|
else if (choice === "discard_milestone") {
|
|
1186
1376
|
const confirmed = await showConfirm(ctx, {
|
|
@@ -1306,7 +1496,8 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1306
1496
|
}), "gsd-run", ctx, "plan-slice");
|
|
1307
1497
|
}
|
|
1308
1498
|
else if (choice === "discuss") {
|
|
1309
|
-
|
|
1499
|
+
const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
|
|
1500
|
+
await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext, structuredQuestionsAvailable: sqAvail }), "gsd-run", ctx, "discuss-slice");
|
|
1310
1501
|
}
|
|
1311
1502
|
else if (choice === "research") {
|
|
1312
1503
|
const researchTemplates = inlineTemplate("research", "Research");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { isDepthVerified, isQueuePhaseActive, setQueuePhaseActive, shouldBlockContextWrite, shouldBlockQueueExecution, } from "./bootstrap/write-gate.js";
|
|
1
|
+
export { isDepthConfirmationAnswer, isDepthVerified, isQueuePhaseActive, setQueuePhaseActive, shouldBlockContextWrite, shouldBlockQueueExecution, } from "./bootstrap/write-gate.js";
|
|
2
2
|
export default async function registerExtension(pi) {
|
|
3
3
|
const { registerGsdExtension } = await import("./bootstrap/register-extension.js");
|
|
4
4
|
registerGsdExtension(pi);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
3
4
|
/**
|
|
4
5
|
* Load a JSON file with validation, returning a default on failure.
|
|
5
6
|
* Handles missing files, corrupt JSON, and schema mismatches uniformly.
|
|
@@ -45,9 +46,11 @@ export function loadJsonFileOrNull(filePath, validate) {
|
|
|
45
46
|
export function saveJsonFile(filePath, data) {
|
|
46
47
|
try {
|
|
47
48
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
48
|
-
|
|
49
|
+
// Use randomized tmp suffix to prevent concurrent-write data loss
|
|
50
|
+
const tmp = `${filePath}.tmp.${randomBytes(4).toString("hex")}`;
|
|
49
51
|
writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
50
52
|
renameSync(tmp, filePath);
|
|
53
|
+
// No cleanup needed — renameSync atomically removes tmp on success
|
|
51
54
|
}
|
|
52
55
|
catch {
|
|
53
56
|
// Non-fatal — don't let persistence failures break operation
|
|
@@ -60,7 +63,7 @@ export function saveJsonFile(filePath, data) {
|
|
|
60
63
|
export function writeJsonFileAtomic(filePath, data) {
|
|
61
64
|
try {
|
|
62
65
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
63
|
-
const tmp = filePath
|
|
66
|
+
const tmp = `${filePath}.tmp.${randomBytes(4).toString("hex")}`;
|
|
64
67
|
writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
65
68
|
renameSync(tmp, filePath);
|
|
66
69
|
}
|
|
@@ -438,12 +438,6 @@ export function migrateHierarchyToDb(basePath) {
|
|
|
438
438
|
// Ghost milestone: no CONTEXT, ROADMAP, or SUMMARY → skip
|
|
439
439
|
if (!hasRoadmap && !hasContext && !hasSummary)
|
|
440
440
|
continue;
|
|
441
|
-
// Determine milestone status
|
|
442
|
-
let milestoneStatus = 'active';
|
|
443
|
-
if (hasSummary)
|
|
444
|
-
milestoneStatus = 'complete';
|
|
445
|
-
else if (hasParked)
|
|
446
|
-
milestoneStatus = 'parked';
|
|
447
441
|
// Determine milestone title from roadmap H1 or CONTEXT heading
|
|
448
442
|
let milestoneTitle = '';
|
|
449
443
|
let roadmapContent = null;
|
|
@@ -453,6 +447,17 @@ export function migrateHierarchyToDb(basePath) {
|
|
|
453
447
|
roadmap = parseRoadmap(roadmapContent);
|
|
454
448
|
milestoneTitle = roadmap.title;
|
|
455
449
|
}
|
|
450
|
+
// Determine milestone status
|
|
451
|
+
let milestoneStatus = 'active';
|
|
452
|
+
if (hasSummary)
|
|
453
|
+
milestoneStatus = 'complete';
|
|
454
|
+
else if (hasParked)
|
|
455
|
+
milestoneStatus = 'parked';
|
|
456
|
+
// Import milestones with all-done roadmap slices as complete (#3390, #3379)
|
|
457
|
+
// even when SUMMARY.md is missing — the roadmap checkboxes are authoritative.
|
|
458
|
+
else if (roadmap && roadmap.slices.length > 0 && roadmap.slices.every(s => s.done)) {
|
|
459
|
+
milestoneStatus = 'complete';
|
|
460
|
+
}
|
|
456
461
|
if (!milestoneTitle && hasContext) {
|
|
457
462
|
const contextContent = readFileSync(contextPath, 'utf-8');
|
|
458
463
|
const h1Match = contextContent.match(/^#\s+(.+)/m);
|
|
@@ -492,7 +497,8 @@ export function migrateHierarchyToDb(basePath) {
|
|
|
492
497
|
// Parse roadmap for slices
|
|
493
498
|
if (!roadmap)
|
|
494
499
|
continue;
|
|
495
|
-
for (
|
|
500
|
+
for (let si = 0; si < roadmap.slices.length; si++) {
|
|
501
|
+
const sliceEntry = roadmap.slices[si];
|
|
496
502
|
// Per K002: use 'complete' not 'done'
|
|
497
503
|
const sliceStatus = sliceEntry.done ? 'complete' : 'pending';
|
|
498
504
|
// Parse slice plan early so goal is available for insertSlice planning column
|
|
@@ -510,6 +516,7 @@ export function migrateHierarchyToDb(basePath) {
|
|
|
510
516
|
risk: sliceEntry.risk,
|
|
511
517
|
depends: sliceEntry.depends,
|
|
512
518
|
demo: sliceEntry.demo,
|
|
519
|
+
sequence: si + 1, // Preserve roadmap parse order (#3356)
|
|
513
520
|
planning: {
|
|
514
521
|
goal: plan?.goal ?? '',
|
|
515
522
|
},
|