@opengsd/gsd-pi 1.1.1-dev.a5a2de8 → 1.1.1-dev.b2556262
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/headless-recover.js +56 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +18 -2
- package/dist/resources/extensions/browser-tools/engine/selection.js +1 -1
- package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/dist/resources/extensions/browser-tools/index.js +68 -24
- package/dist/resources/extensions/browser-tools/state.js +12 -0
- package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
- package/dist/resources/extensions/browser-tools/utils.js +3 -3
- package/dist/resources/extensions/browser-tools/web-app-detect.js +52 -0
- package/dist/resources/extensions/gsd/auto/loop.js +4 -2
- package/dist/resources/extensions/gsd/auto/phases.js +87 -12
- package/dist/resources/extensions/gsd/auto/session.js +22 -1
- package/dist/resources/extensions/gsd/auto/workflow-kernel.js +1 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +81 -13
- package/dist/resources/extensions/gsd/auto-model-selection.js +154 -9
- package/dist/resources/extensions/gsd/auto-post-unit.js +19 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +26 -21
- package/dist/resources/extensions/gsd/auto-recovery.js +4 -2
- package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
- package/dist/resources/extensions/gsd/auto-start.js +1 -1
- package/dist/resources/extensions/gsd/auto-timers.js +24 -10
- package/dist/resources/extensions/gsd/auto.js +40 -15
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +192 -77
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
- package/dist/resources/extensions/gsd/closeout-wizard.js +32 -9
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -9
- package/dist/resources/extensions/gsd/commands-maintenance.js +93 -15
- package/dist/resources/extensions/gsd/commands-mcp-status.js +1 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +2 -2
- package/dist/resources/extensions/gsd/config-overlay.js +1 -0
- package/dist/resources/extensions/gsd/context-masker.js +129 -5
- package/dist/resources/extensions/gsd/db-writer.js +35 -0
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +50 -1
- package/dist/resources/extensions/gsd/gsd-db.js +480 -172
- package/dist/resources/extensions/gsd/guided-flow.js +4 -1
- package/dist/resources/extensions/gsd/markdown-renderer.js +37 -53
- package/dist/resources/extensions/gsd/md-importer.js +38 -3
- package/dist/resources/extensions/gsd/migration-auto-check.js +126 -31
- package/dist/resources/extensions/gsd/parsers-legacy.js +23 -0
- package/dist/resources/extensions/gsd/planner-handoff.js +98 -0
- package/dist/resources/extensions/gsd/planning-path-scope.js +22 -4
- package/dist/resources/extensions/gsd/pre-execution-checks.js +10 -2
- package/dist/resources/extensions/gsd/preferences-models.js +111 -43
- package/dist/resources/extensions/gsd/preferences-types.js +13 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +68 -3
- package/dist/resources/extensions/gsd/preferences.js +4 -1
- package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +5 -1
- package/dist/resources/extensions/gsd/safety/content-validator.js +6 -4
- package/dist/resources/extensions/gsd/skill-manifest.js +12 -0
- package/dist/resources/extensions/gsd/source-observations.js +306 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +15 -8
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +33 -5
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +34 -13
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +39 -14
- package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +4 -4
- package/dist/resources/extensions/gsd/state.js +7 -3
- package/dist/resources/extensions/gsd/tool-contract.js +15 -1
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +24 -2
- package/dist/resources/extensions/gsd/tools/complete-slice.js +28 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +42 -11
- package/dist/resources/extensions/gsd/tools/plan-task.js +7 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +62 -406
- package/dist/resources/extensions/gsd/uat-policy.js +130 -0
- package/dist/resources/extensions/gsd/uat-run.js +414 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +3 -4
- package/dist/resources/extensions/gsd/unit-tool-contracts.js +38 -14
- package/dist/resources/extensions/gsd/verdict-parser.js +3 -8
- package/dist/resources/extensions/gsd/workflow-manifest.js +132 -5
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -3
- package/dist/resources/extensions/gsd/workflow-projections.js +8 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +26 -0
- package/dist/resources/extensions/gsd/worktree-reentry.js +96 -0
- package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -17
- package/dist/resources/extensions/shared/gsd-browser-cli.js +6 -0
- package/dist/resources/extensions/subagent/agents.js +1 -0
- package/dist/resources/extensions/subagent/index.js +27 -12
- package/dist/resources/extensions/subagent/launch.js +7 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- 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 +6 -6
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/node_modules/@gsd/native/dist/native.js +22 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/package.json +4 -4
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +3 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +66 -12
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +18 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/dist/native.js +22 -0
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/image-models.generated.d.ts +30 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/image-models.generated.js +30 -0
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +174 -29
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +178 -54
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +8 -1
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/themes.js +1 -1
- package/packages/pi-coding-agent/dist/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/utils.d.ts +11 -0
- package/packages/pi-tui/dist/utils.d.ts.map +1 -1
- package/packages/pi-tui/dist/utils.js +119 -6
- package/packages/pi-tui/dist/utils.js.map +1 -1
- package/packages/pi-tui/package.json +2 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/theme/themes.js +1 -1
- package/pkg/dist/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/scripts/install/handoff.js +16 -3
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +21 -2
- package/src/resources/extensions/browser-tools/engine/selection.ts +1 -1
- package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/src/resources/extensions/browser-tools/index.ts +75 -27
- package/src/resources/extensions/browser-tools/state.ts +13 -0
- package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +2 -2
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +37 -0
- package/src/resources/extensions/browser-tools/tests/web-app-detect.test.mjs +68 -0
- package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
- package/src/resources/extensions/browser-tools/utils.ts +3 -3
- package/src/resources/extensions/browser-tools/web-app-detect.ts +63 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/loop.ts +4 -2
- package/src/resources/extensions/gsd/auto/phases.ts +89 -15
- package/src/resources/extensions/gsd/auto/session.ts +24 -1
- package/src/resources/extensions/gsd/auto/workflow-kernel.ts +1 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +117 -12
- package/src/resources/extensions/gsd/auto-model-selection.ts +190 -12
- package/src/resources/extensions/gsd/auto-post-unit.ts +20 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +25 -22
- package/src/resources/extensions/gsd/auto-recovery.ts +22 -3
- package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
- package/src/resources/extensions/gsd/auto-start.ts +1 -1
- package/src/resources/extensions/gsd/auto-timers.ts +25 -9
- package/src/resources/extensions/gsd/auto.ts +41 -14
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +250 -78
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
- package/src/resources/extensions/gsd/closeout-wizard.ts +47 -13
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -17
- package/src/resources/extensions/gsd/commands-maintenance.ts +124 -13
- package/src/resources/extensions/gsd/commands-mcp-status.ts +1 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -2
- package/src/resources/extensions/gsd/config-overlay.ts +1 -0
- package/src/resources/extensions/gsd/context-masker.ts +152 -5
- package/src/resources/extensions/gsd/db-writer.ts +38 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +50 -1
- package/src/resources/extensions/gsd/gsd-db.ts +564 -186
- package/src/resources/extensions/gsd/guided-flow.ts +4 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +44 -66
- package/src/resources/extensions/gsd/md-importer.ts +49 -2
- package/src/resources/extensions/gsd/migration-auto-check.ts +154 -34
- package/src/resources/extensions/gsd/parsers-legacy.ts +20 -0
- package/src/resources/extensions/gsd/planner-handoff.ts +149 -0
- package/src/resources/extensions/gsd/planning-path-scope.ts +22 -4
- package/src/resources/extensions/gsd/pre-execution-checks.ts +9 -2
- package/src/resources/extensions/gsd/preferences-models.ts +113 -43
- package/src/resources/extensions/gsd/preferences-types.ts +47 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +76 -2
- package/src/resources/extensions/gsd/preferences.ts +5 -0
- package/src/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +2 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +6 -1
- package/src/resources/extensions/gsd/safety/content-validator.ts +8 -5
- package/src/resources/extensions/gsd/skill-manifest.ts +12 -0
- package/src/resources/extensions/gsd/source-observations.ts +402 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +20 -8
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +44 -5
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +39 -11
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +45 -15
- package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +4 -4
- package/src/resources/extensions/gsd/state.ts +7 -4
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +66 -4
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +299 -1
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +75 -3
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +4 -0
- package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/closeout-wizard.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +118 -0
- package/src/resources/extensions/gsd/tests/content-validator.test.ts +74 -0
- package/src/resources/extensions/gsd/tests/context-masker.test.ts +56 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +17 -2
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +1 -11
- package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +62 -1
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +99 -2
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/planner-handoff.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +133 -0
- package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +101 -1
- package/src/resources/extensions/gsd/tests/repository-registry.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +162 -18
- package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/source-observations.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -21
- package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/uat-policy.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +306 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +77 -10
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +260 -5
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +511 -1
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +44 -0
- package/src/resources/extensions/gsd/tool-contract.ts +29 -1
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +41 -6
- package/src/resources/extensions/gsd/tools/complete-slice.ts +29 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +54 -12
- package/src/resources/extensions/gsd/tools/plan-task.ts +8 -1
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +71 -489
- package/src/resources/extensions/gsd/types.ts +1 -0
- package/src/resources/extensions/gsd/uat-policy.ts +191 -0
- package/src/resources/extensions/gsd/uat-run.ts +550 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +3 -4
- package/src/resources/extensions/gsd/unit-tool-contracts.ts +38 -14
- package/src/resources/extensions/gsd/verdict-parser.ts +3 -10
- package/src/resources/extensions/gsd/workflow-manifest.ts +193 -7
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -3
- package/src/resources/extensions/gsd/workflow-projections.ts +9 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +32 -0
- package/src/resources/extensions/gsd/worktree-reentry.ts +103 -0
- package/src/resources/extensions/gsd/worktree-state-projection.ts +22 -22
- package/src/resources/extensions/shared/gsd-browser-cli.ts +6 -0
- package/src/resources/extensions/shared/tests/format-utils.test.ts +8 -3
- package/src/resources/extensions/subagent/agents.ts +4 -0
- package/src/resources/extensions/subagent/index.ts +28 -3
- package/src/resources/extensions/subagent/launch.ts +8 -0
- package/src/resources/extensions/subagent/tests/model-override.test.ts +31 -0
- /package/dist/web/standalone/.next/static/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_ssgManifest.js +0 -0
|
@@ -865,7 +865,10 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType,
|
|
|
865
865
|
? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
|
|
866
866
|
: undefined,
|
|
867
867
|
baseUrl: result.appliedModel?.baseUrl ?? ctx.model?.baseUrl,
|
|
868
|
-
|
|
868
|
+
// Guided flow starts the MCP workflow server as part of dispatch, so the
|
|
869
|
+
// parent session's activeTools doesn't include MCP tools yet. The MCP
|
|
870
|
+
// launch config check (detectWorkflowMcpLaunchConfig) is the right gate
|
|
871
|
+
// here — not whether MCP tools are pre-registered in the parent session.
|
|
869
872
|
});
|
|
870
873
|
if (compatibilityError) {
|
|
871
874
|
ctx.ui.notify(compatibilityError, "error");
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
import { readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
12
12
|
import { logWarning } from "./workflow-logger.js";
|
|
13
13
|
import { isClosedStatus } from "./status-guards.js";
|
|
14
|
-
import { join, relative } from "node:path";
|
|
14
|
+
import { dirname, join, relative } from "node:path";
|
|
15
15
|
import { createRequire } from "node:module";
|
|
16
|
-
import { getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getTask, getSlice,
|
|
16
|
+
import { getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getTask, getSlice, insertArtifact, getGateResults, } from "./gsd-db.js";
|
|
17
17
|
import { resolveDir, resolveFile, resolveSliceFile, resolveSlicePath, gsdProjectionRoot, gsdRoot, buildMilestoneFileName, buildTaskFileName, buildSliceFileName, } from "./paths.js";
|
|
18
18
|
import { saveFile, clearParseCache } from "./files.js";
|
|
19
19
|
import { invalidateStateCache } from "./state.js";
|
|
@@ -78,18 +78,6 @@ function sanitizeInlineRoadmapText(value) {
|
|
|
78
78
|
.replace(/\s+/g, " ")
|
|
79
79
|
.trim();
|
|
80
80
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Load artifact content from the DB. Markdown projections are not authoritative
|
|
83
|
-
* during runtime; when the artifact row is missing, callers regenerate from DB
|
|
84
|
-
* rows instead of patching disk fallback content and storing it back.
|
|
85
|
-
*/
|
|
86
|
-
function loadArtifactContent(artifactPath) {
|
|
87
|
-
const artifact = getArtifact(artifactPath);
|
|
88
|
-
if (artifact && artifact.full_content) {
|
|
89
|
-
return artifact.full_content;
|
|
90
|
-
}
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
81
|
function resolveRoadmapProjectionPath(basePath, milestoneId) {
|
|
94
82
|
const projectionMilestonesDir = join(gsdProjectionRoot(basePath), "milestones");
|
|
95
83
|
const milestoneDirName = resolveDir(projectionMilestonesDir, milestoneId) ?? milestoneId;
|
|
@@ -324,7 +312,7 @@ function renderSlicePlanMarkdown(slice, tasks, gates = []) {
|
|
|
324
312
|
}
|
|
325
313
|
return `${lines.join("\n").trimEnd()}\n`;
|
|
326
314
|
}
|
|
327
|
-
export async function renderPlanFromDb(basePath, milestoneId, sliceId) {
|
|
315
|
+
export async function renderPlanFromDb(basePath, milestoneId, sliceId, outputPath) {
|
|
328
316
|
const slice = getSlice(milestoneId, sliceId);
|
|
329
317
|
if (!slice) {
|
|
330
318
|
throw new Error(`slice ${milestoneId}/${sliceId} not found`);
|
|
@@ -333,9 +321,9 @@ export async function renderPlanFromDb(basePath, milestoneId, sliceId) {
|
|
|
333
321
|
if (tasks.length === 0) {
|
|
334
322
|
throw new Error(`no tasks found for ${milestoneId}/${sliceId}`);
|
|
335
323
|
}
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
324
|
+
const defaultPlanPath = join(gsdProjectionRoot(basePath), "milestones", milestoneId, "slices", sliceId, `${sliceId}-PLAN.md`);
|
|
325
|
+
const absPath = outputPath ?? defaultPlanPath;
|
|
326
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
339
327
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
340
328
|
const sliceGates = getGateResults(milestoneId, sliceId, "slice");
|
|
341
329
|
const content = renderSlicePlanMarkdown(slice, tasks, sliceGates);
|
|
@@ -406,48 +394,24 @@ export async function renderRoadmapCheckboxes(basePath, milestoneId) {
|
|
|
406
394
|
/**
|
|
407
395
|
* Render plan checkbox states from DB.
|
|
408
396
|
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
397
|
+
* Compatibility wrapper for legacy callers that used to patch plan checkboxes
|
|
398
|
+
* in-place. Plans are now fully regenerated from DB rows (mirroring
|
|
399
|
+
* renderRoadmapCheckboxes) so the projection always reflects the complete
|
|
400
|
+
* current task set and statuses. The previous regex-patch approach reused the
|
|
401
|
+
* cached PLAN artifact as the render input, which silently dropped tasks added
|
|
402
|
+
* to the DB after the artifact was first written — producing a lossy
|
|
403
|
+
* projection (the 4S/0T-vs-5S/13T drift class). The artifacts table is an
|
|
404
|
+
* output sink, never a render input.
|
|
411
405
|
*
|
|
412
406
|
* @returns true if the plan was written, false on skip/error
|
|
413
407
|
*/
|
|
414
|
-
export async function renderPlanCheckboxes(basePath, milestoneId, sliceId) {
|
|
408
|
+
export async function renderPlanCheckboxes(basePath, milestoneId, sliceId, outputPath) {
|
|
415
409
|
const tasks = getSliceTasks(milestoneId, sliceId);
|
|
416
410
|
if (tasks.length === 0) {
|
|
417
411
|
process.stderr.write(`markdown-renderer: no tasks found for ${milestoneId}/${sliceId}\n`);
|
|
418
412
|
return false;
|
|
419
413
|
}
|
|
420
|
-
|
|
421
|
-
const artifactPath = absPath ? toArtifactPath(absPath, basePath) : null;
|
|
422
|
-
let content = null;
|
|
423
|
-
if (artifactPath) {
|
|
424
|
-
content = loadArtifactContent(artifactPath);
|
|
425
|
-
}
|
|
426
|
-
if (!content) {
|
|
427
|
-
await renderPlanFromDb(basePath, milestoneId, sliceId);
|
|
428
|
-
return true;
|
|
429
|
-
}
|
|
430
|
-
// Apply checkbox patches for each task
|
|
431
|
-
let updated = content;
|
|
432
|
-
for (const task of tasks) {
|
|
433
|
-
const isDone = isClosedStatus(task.status);
|
|
434
|
-
const tid = task.id;
|
|
435
|
-
if (isDone) {
|
|
436
|
-
// Set [x]
|
|
437
|
-
updated = updated.replace(new RegExp(`^(\\s*-\\s+)\\[ \\]\\s+\\*\\*${tid}:`, "m"), `$1[x] **${tid}:`);
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
// Set [ ]
|
|
441
|
-
updated = updated.replace(new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${tid}:`, "mi"), `$1[ ] **${tid}:`);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
if (!absPath)
|
|
445
|
-
return false;
|
|
446
|
-
await writeAndStore(absPath, artifactPath, updated, {
|
|
447
|
-
artifact_type: "PLAN",
|
|
448
|
-
milestone_id: milestoneId,
|
|
449
|
-
slice_id: sliceId,
|
|
450
|
-
});
|
|
414
|
+
await renderPlanFromDb(basePath, milestoneId, sliceId, outputPath);
|
|
451
415
|
return true;
|
|
452
416
|
}
|
|
453
417
|
// ─── Task Summary Rendering ───────────────────────────────────────────────
|
|
@@ -584,6 +548,18 @@ export async function renderAllFromDb(basePath) {
|
|
|
584
548
|
}
|
|
585
549
|
}
|
|
586
550
|
}
|
|
551
|
+
// Re-project root DECISIONS.md from the authoritative decision records so a
|
|
552
|
+
// full DB → markdown re-projection (recover, rebuild) also heals decisions
|
|
553
|
+
// drift — e.g. a worktree merge that accepted one branch's DECISIONS.md while
|
|
554
|
+
// the DB holds the union of both branches' decisions.
|
|
555
|
+
try {
|
|
556
|
+
const { regenerateDecisionsMarkdown } = await import("./db-writer.js");
|
|
557
|
+
await regenerateDecisionsMarkdown(basePath);
|
|
558
|
+
result.rendered++;
|
|
559
|
+
}
|
|
560
|
+
catch (err) {
|
|
561
|
+
result.errors.push(`decisions: ${err.message}`);
|
|
562
|
+
}
|
|
587
563
|
return result;
|
|
588
564
|
}
|
|
589
565
|
/**
|
|
@@ -659,8 +635,16 @@ export function detectStaleRenders(basePath) {
|
|
|
659
635
|
for (const task of tasks) {
|
|
660
636
|
const isDoneInDb = isClosedStatus(task.status);
|
|
661
637
|
const planTask = parsed.tasks.find((t) => t.id === task.id);
|
|
662
|
-
if (!planTask)
|
|
638
|
+
if (!planTask) {
|
|
639
|
+
// DB has a task the plan markdown lacks: the projection is
|
|
640
|
+
// lossy (e.g. tasks added after the PLAN artifact was first
|
|
641
|
+
// written). Flag it so the plan is re-rendered from DB rows.
|
|
642
|
+
stale.push({
|
|
643
|
+
path: planPath,
|
|
644
|
+
reason: `${task.id} exists in DB but is missing in plan`,
|
|
645
|
+
});
|
|
663
646
|
continue;
|
|
647
|
+
}
|
|
664
648
|
if (isDoneInDb && !planTask.done) {
|
|
665
649
|
stale.push({
|
|
666
650
|
path: planPath,
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
// then upserts everything into the SQLite database.
|
|
4
4
|
//
|
|
5
5
|
// Exports: parseDecisionsTable, parseRequirementsSections, migrateFromMarkdown
|
|
6
|
-
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
6
|
+
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
|
-
import { upsertDecision, upsertRequirement, insertArtifact, insertMilestone, insertSlice, insertTask, openDatabase, transaction, updateSliceStatus, _getAdapter, } from './gsd-db.js';
|
|
8
|
+
import { upsertDecision, upsertRequirement, insertArtifact, insertMilestone, insertSlice, insertTask, openDatabase, transaction, updateMilestoneStatus, updateSliceStatus, _getAdapter, } from './gsd-db.js';
|
|
9
9
|
import { resolveGsdRootFile, resolveMilestoneFile, resolveSliceFile, resolveTasksDir, milestonesDir, gsdRoot, resolveTaskFiles, } from './paths.js';
|
|
10
10
|
import { findMilestoneIds } from './guided-flow.js';
|
|
11
11
|
import { parseRoadmap, parsePlan } from './parsers-legacy.js';
|
|
@@ -13,6 +13,26 @@ import { parseContextDependsOn } from './files.js';
|
|
|
13
13
|
import { logWarning } from './workflow-logger.js';
|
|
14
14
|
// ─── DECISIONS.md Parser ───────────────────────────────────────────────────
|
|
15
15
|
const VALID_MADE_BY = new Set(['human', 'agent', 'collaborative']);
|
|
16
|
+
const IMPORT_COMPLETE_STATUSES = new Set(['complete', 'done']);
|
|
17
|
+
/**
|
|
18
|
+
* Completion timestamp for an entity imported as complete: the SUMMARY.md mtime
|
|
19
|
+
* when one exists (deterministic, matches the completion drift handler), else
|
|
20
|
+
* the import time. Crucially this never returns null, so a complete entity is
|
|
21
|
+
* never left with completed_at=null — which would otherwise be permanent,
|
|
22
|
+
* undetectable drift when no SUMMARY file is present.
|
|
23
|
+
*/
|
|
24
|
+
function importCompletionTimestamp(summaryPath) {
|
|
25
|
+
if (summaryPath && existsSync(summaryPath)) {
|
|
26
|
+
try {
|
|
27
|
+
return statSync(summaryPath).mtime.toISOString();
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
// Fall through to import time.
|
|
31
|
+
logWarning('projection', `summary mtime read failed for ${summaryPath}: ${err.message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return new Date().toISOString();
|
|
35
|
+
}
|
|
16
36
|
/**
|
|
17
37
|
* Parse a DECISIONS.md markdown table into Decision objects (without seq).
|
|
18
38
|
* Detects `(amends DXXX)` in the Decision column to build supersession info.
|
|
@@ -493,6 +513,14 @@ export function migrateHierarchyToDb(basePath) {
|
|
|
493
513
|
boundaryMapMarkdown: boundaryMapSection,
|
|
494
514
|
},
|
|
495
515
|
});
|
|
516
|
+
// insertMilestone never sets completed_at; backfill it now for milestones
|
|
517
|
+
// imported as complete so the DB is internally coherent immediately after
|
|
518
|
+
// import (rather than relying on a later reconciliation pass that can't even
|
|
519
|
+
// see the drift when no SUMMARY file exists).
|
|
520
|
+
if (IMPORT_COMPLETE_STATUSES.has(milestoneStatus)) {
|
|
521
|
+
const summaryPath = resolveMilestoneFile(basePath, milestoneId, 'SUMMARY');
|
|
522
|
+
updateMilestoneStatus(milestoneId, milestoneStatus, importCompletionTimestamp(summaryPath));
|
|
523
|
+
}
|
|
496
524
|
counts.milestones++;
|
|
497
525
|
// Parse roadmap for slices
|
|
498
526
|
if (!roadmap)
|
|
@@ -517,10 +545,17 @@ export function migrateHierarchyToDb(basePath) {
|
|
|
517
545
|
depends: sliceEntry.depends,
|
|
518
546
|
demo: sliceEntry.demo,
|
|
519
547
|
sequence: si + 1, // Preserve roadmap parse order (#3356)
|
|
548
|
+
isSketch: sliceEntry.isSketch ?? false, // ADR-011: preserve the `[sketch]` flag on re-import
|
|
520
549
|
planning: {
|
|
521
550
|
goal: plan?.goal ?? '',
|
|
522
551
|
},
|
|
523
552
|
});
|
|
553
|
+
// insertSlice never sets completed_at; backfill it for slices imported as
|
|
554
|
+
// complete (same rationale as milestones above).
|
|
555
|
+
if (IMPORT_COMPLETE_STATUSES.has(sliceStatus)) {
|
|
556
|
+
const sliceSummary = resolveSliceFile(basePath, milestoneId, sliceEntry.id, 'SUMMARY');
|
|
557
|
+
updateSliceStatus(milestoneId, sliceEntry.id, sliceStatus, importCompletionTimestamp(sliceSummary));
|
|
558
|
+
}
|
|
524
559
|
counts.slices++;
|
|
525
560
|
// Insert tasks from parsed plan
|
|
526
561
|
if (!plan)
|
|
@@ -572,7 +607,7 @@ export function migrateHierarchyToDb(basePath) {
|
|
|
572
607
|
});
|
|
573
608
|
if (allTasksDone && hasSliceSummary) {
|
|
574
609
|
if (_getAdapter()) {
|
|
575
|
-
updateSliceStatus(milestoneId, sliceEntry.id, 'complete');
|
|
610
|
+
updateSliceStatus(milestoneId, sliceEntry.id, 'complete', importCompletionTimestamp(sliceSummaryPath));
|
|
576
611
|
process.stderr.write(`gsd-migrate: ${milestoneId}/${sliceEntry.id} all tasks + slice summary complete — upgrading slice to complete\n`);
|
|
577
612
|
}
|
|
578
613
|
}
|
|
@@ -6,59 +6,117 @@ import { milestonesDir, resolveMilestoneFile, resolveSliceFile, } from "./paths.
|
|
|
6
6
|
function zeroCounts() {
|
|
7
7
|
return { milestones: 0, slices: 0, tasks: 0 };
|
|
8
8
|
}
|
|
9
|
+
function emptyScan() {
|
|
10
|
+
return { counts: zeroCounts(), milestones: new Set(), slices: new Set(), tasks: new Set() };
|
|
11
|
+
}
|
|
9
12
|
function sameCounts(a, b) {
|
|
10
13
|
return a.milestones === b.milestones && a.slices === b.slices && a.tasks === b.tasks;
|
|
11
14
|
}
|
|
12
|
-
|
|
15
|
+
function setsEqual(a, b) {
|
|
16
|
+
if (a.size !== b.size)
|
|
17
|
+
return false;
|
|
18
|
+
for (const value of a)
|
|
19
|
+
if (!b.has(value))
|
|
20
|
+
return false;
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
function scanIdentitiesMatch(a, b) {
|
|
24
|
+
return (setsEqual(a.milestones, b.milestones) &&
|
|
25
|
+
setsEqual(a.slices, b.slices) &&
|
|
26
|
+
setsEqual(a.tasks, b.tasks));
|
|
27
|
+
}
|
|
28
|
+
/** True if any element of `a` is absent from `b`. */
|
|
29
|
+
function hasExtra(a, b) {
|
|
30
|
+
for (const value of a)
|
|
31
|
+
if (!b.has(value))
|
|
32
|
+
return true;
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
function scanHasExtraIdentities(a, b) {
|
|
36
|
+
return (hasExtra(a.milestones, b.milestones) ||
|
|
37
|
+
hasExtra(a.slices, b.slices) ||
|
|
38
|
+
hasExtra(a.tasks, b.tasks));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* True when the DB holds any milestone/slice/task identity the markdown lacks —
|
|
42
|
+
* i.e. a `/gsd recover --confirm` (markdown → DB) would DELETE authoritative DB
|
|
43
|
+
* rows. This is identity-based, so it catches equal-count divergence (e.g. DB
|
|
44
|
+
* slice `S99` vs markdown `S01`) that a cardinality-only check misses. Used by
|
|
45
|
+
* the recover data-loss guard.
|
|
46
|
+
*/
|
|
47
|
+
export function recoverWouldDeleteDbRows(basePath) {
|
|
48
|
+
return scanHasExtraIdentities(scanDbHierarchy(), scanMarkdownHierarchy(basePath));
|
|
49
|
+
}
|
|
50
|
+
export function scanMarkdownHierarchy(basePath) {
|
|
13
51
|
const root = milestonesDir(basePath);
|
|
14
52
|
if (!existsSync(root))
|
|
15
|
-
return
|
|
16
|
-
const
|
|
53
|
+
return emptyScan();
|
|
54
|
+
const scan = emptyScan();
|
|
17
55
|
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
18
56
|
if (!entry.isDirectory() || !/^M\d+/.test(entry.name))
|
|
19
57
|
continue;
|
|
20
|
-
|
|
21
|
-
|
|
58
|
+
// Use the CANONICAL milestone id (e.g. "M001" or "M001-a1b2c3"), matching
|
|
59
|
+
// scanDbHierarchy's milestone.id — not the raw directory name, which may
|
|
60
|
+
// carry a legacy descriptor (e.g. "M001-some-descriptor"). The canonical id
|
|
61
|
+
// both keys the identity sets AND resolves files correctly: resolveFile's
|
|
62
|
+
// prefix/legacy-pattern matching handles the descriptor dir either way.
|
|
63
|
+
const milestoneId = entry.name.match(/^(M\d+(?:-[a-z0-9]{6})?)/)?.[1] ?? entry.name;
|
|
64
|
+
scan.counts.milestones++;
|
|
65
|
+
scan.milestones.add(milestoneId);
|
|
66
|
+
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
22
67
|
if (!roadmapPath || !existsSync(roadmapPath))
|
|
23
68
|
continue;
|
|
24
69
|
const roadmap = parseRoadmap(readFileSync(roadmapPath, "utf-8"));
|
|
25
|
-
counts.slices += roadmap.slices.length;
|
|
70
|
+
scan.counts.slices += roadmap.slices.length;
|
|
26
71
|
for (const slice of roadmap.slices) {
|
|
27
|
-
|
|
72
|
+
scan.slices.add(`${milestoneId}/${slice.id}`);
|
|
73
|
+
const planPath = resolveSliceFile(basePath, milestoneId, slice.id, "PLAN");
|
|
28
74
|
if (!planPath || !existsSync(planPath))
|
|
29
75
|
continue;
|
|
30
76
|
const plan = parsePlan(readFileSync(planPath, "utf-8"));
|
|
31
|
-
counts.tasks += plan.tasks.length;
|
|
77
|
+
scan.counts.tasks += plan.tasks.length;
|
|
78
|
+
for (const task of plan.tasks) {
|
|
79
|
+
scan.tasks.add(`${milestoneId}/${slice.id}/${task.id}`);
|
|
80
|
+
}
|
|
32
81
|
}
|
|
33
82
|
}
|
|
34
|
-
return
|
|
83
|
+
return scan;
|
|
35
84
|
}
|
|
36
|
-
export function
|
|
85
|
+
export function scanDbHierarchy() {
|
|
37
86
|
if (!isDbAvailable())
|
|
38
|
-
return
|
|
39
|
-
const
|
|
87
|
+
return emptyScan();
|
|
88
|
+
const scan = emptyScan();
|
|
40
89
|
const milestones = getAllMilestones();
|
|
41
|
-
counts.milestones = milestones.length;
|
|
90
|
+
scan.counts.milestones = milestones.length;
|
|
42
91
|
for (const milestone of milestones) {
|
|
92
|
+
scan.milestones.add(milestone.id);
|
|
43
93
|
const slices = getMilestoneSlices(milestone.id);
|
|
44
|
-
counts.slices += slices.length;
|
|
94
|
+
scan.counts.slices += slices.length;
|
|
45
95
|
for (const slice of slices) {
|
|
46
|
-
|
|
96
|
+
scan.slices.add(`${milestone.id}/${slice.id}`);
|
|
97
|
+
const tasks = getSliceTasks(milestone.id, slice.id);
|
|
98
|
+
scan.counts.tasks += tasks.length;
|
|
99
|
+
for (const task of tasks) {
|
|
100
|
+
scan.tasks.add(`${milestone.id}/${slice.id}/${task.id}`);
|
|
101
|
+
}
|
|
47
102
|
}
|
|
48
103
|
}
|
|
49
|
-
return
|
|
104
|
+
return scan;
|
|
105
|
+
}
|
|
106
|
+
export function countMarkdownHierarchy(basePath) {
|
|
107
|
+
return scanMarkdownHierarchy(basePath).counts;
|
|
108
|
+
}
|
|
109
|
+
export function countDbHierarchy() {
|
|
110
|
+
return scanDbHierarchy().counts;
|
|
50
111
|
}
|
|
51
112
|
export async function checkMarkdownHierarchyAgainstDb(basePath) {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
afterDb: zeroCounts(),
|
|
60
|
-
};
|
|
61
|
-
}
|
|
113
|
+
const markdownScan = scanMarkdownHierarchy(basePath);
|
|
114
|
+
const markdown = markdownScan.counts;
|
|
115
|
+
// Always open the DB before deciding. An empty markdown tree does NOT imply
|
|
116
|
+
// an empty project — the DB may hold authoritative rows whose markdown was
|
|
117
|
+
// lost, which is itself recoverable drift. The previous early return here
|
|
118
|
+
// skipped the DB entirely and silently hid a populated-DB/empty-markdown
|
|
119
|
+
// project.
|
|
62
120
|
const opened = await ensureDbOpen(basePath);
|
|
63
121
|
if (!opened || !isDbAvailable()) {
|
|
64
122
|
throw new Error(`failed to open or create the GSD database at ${basePath}`);
|
|
@@ -67,11 +125,49 @@ export async function checkMarkdownHierarchyAgainstDb(basePath) {
|
|
|
67
125
|
// server in another process. Reopen before comparing so startup does not
|
|
68
126
|
// warn from a stale long-lived SQLite handle.
|
|
69
127
|
refreshOpenDatabaseFromDisk();
|
|
70
|
-
const
|
|
71
|
-
|
|
128
|
+
const dbScan = scanDbHierarchy();
|
|
129
|
+
const beforeDb = dbScan.counts;
|
|
130
|
+
const markdownEmpty = sameCounts(markdown, zeroCounts());
|
|
131
|
+
const dbEmpty = sameCounts(beforeDb, zeroCounts());
|
|
132
|
+
// Genuinely empty project: nothing on disk, nothing in the DB.
|
|
133
|
+
if (markdownEmpty && dbEmpty) {
|
|
134
|
+
return { action: "none", reason: "no-markdown", markdown, beforeDb, afterDb: beforeDb };
|
|
135
|
+
}
|
|
136
|
+
// In sync only when both cardinalities AND identities agree. Identity
|
|
137
|
+
// comparison catches drift the counts miss (e.g. a slice deleted from the DB
|
|
138
|
+
// and a different one added nets to the same count but is real divergence,
|
|
139
|
+
// and a missing PLAN.md vs DB tasks shows up as a task-identity gap).
|
|
140
|
+
if (sameCounts(markdown, beforeDb) && scanIdentitiesMatch(markdownScan, dbScan)) {
|
|
72
141
|
return { action: "none", reason: "in-sync", markdown, beforeDb, afterDb: beforeDb };
|
|
73
142
|
}
|
|
74
|
-
|
|
143
|
+
// Choose the SAFE repair direction by IDENTITY, not cardinality. Recover
|
|
144
|
+
// imports markdown → DB and DELETES any DB row markdown lacks, so it must
|
|
145
|
+
// never be recommended when the DB holds identities the markdown is missing —
|
|
146
|
+
// including equal-count divergence (DB `S99` vs markdown `S01`), which a
|
|
147
|
+
// count-only check would wrongly route to recover. Whenever the DB holds rows
|
|
148
|
+
// markdown lacks, the correct repair is to re-project from the DB (rebuild).
|
|
149
|
+
const dbHasExtra = scanHasExtraIdentities(dbScan, markdownScan);
|
|
150
|
+
const countsLine = `Markdown planning artifacts (${markdown.milestones}M/${markdown.slices}S/${markdown.tasks}T) ` +
|
|
151
|
+
`do not match the authoritative DB (${beforeDb.milestones}M/${beforeDb.slices}S/${beforeDb.tasks}T). `;
|
|
152
|
+
// The DB holds rows markdown lacks (richer, identity-diverged, or markdown
|
|
153
|
+
// entirely missing): re-project from the DB. Recover here would destroy data.
|
|
154
|
+
if (dbHasExtra) {
|
|
155
|
+
return {
|
|
156
|
+
action: "recovery-required",
|
|
157
|
+
reason: markdownEmpty ? "markdown-missing" : "count-mismatch",
|
|
158
|
+
markdown,
|
|
159
|
+
beforeDb,
|
|
160
|
+
afterDb: beforeDb,
|
|
161
|
+
recoveryCommand: "/gsd rebuild markdown",
|
|
162
|
+
message: countsLine +
|
|
163
|
+
"The DB holds rows the markdown lacks, so the markdown projection is stale. " +
|
|
164
|
+
"Run `/gsd rebuild markdown` to re-project from the authoritative DB. " +
|
|
165
|
+
"Do NOT run `/gsd recover --confirm` here — it would delete the extra DB rows.",
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
// DB is empty (or markdown is strictly richer): markdown is the surviving
|
|
169
|
+
// source to import.
|
|
170
|
+
const reason = dbEmpty ? "db-empty" : "count-mismatch";
|
|
75
171
|
return {
|
|
76
172
|
action: "recovery-required",
|
|
77
173
|
reason,
|
|
@@ -79,8 +175,7 @@ export async function checkMarkdownHierarchyAgainstDb(basePath) {
|
|
|
79
175
|
beforeDb,
|
|
80
176
|
afterDb: beforeDb,
|
|
81
177
|
recoveryCommand: "/gsd recover --confirm",
|
|
82
|
-
message:
|
|
83
|
-
`do not match the authoritative DB (${beforeDb.milestones}M/${beforeDb.slices}S/${beforeDb.tasks}T). ` +
|
|
178
|
+
message: countsLine +
|
|
84
179
|
"Runtime startup will not import markdown automatically; run `/gsd recover --confirm` if markdown should repopulate the database.",
|
|
85
180
|
};
|
|
86
181
|
}
|
|
@@ -46,11 +46,34 @@ registerCacheClearCallback(clearLegacyParseCache);
|
|
|
46
46
|
export function parseRoadmap(content) {
|
|
47
47
|
return cachedParse(content, 'roadmap', _parseRoadmapImpl);
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* ADR-011: the roadmap renderer writes a `[sketch]` badge for sketch slices,
|
|
51
|
+
* but the native parser does not surface it. Re-scan the markdown and set
|
|
52
|
+
* isSketch on the matching slice so the flag survives a markdown → DB re-import
|
|
53
|
+
* (e.g. /gsd recover) instead of being silently dropped.
|
|
54
|
+
*/
|
|
55
|
+
function applySketchFlags(roadmap, content) {
|
|
56
|
+
const sketchIds = new Set();
|
|
57
|
+
for (const line of content.split("\n")) {
|
|
58
|
+
if (!/\[sketch\]/i.test(line))
|
|
59
|
+
continue;
|
|
60
|
+
const m = line.match(/\*\*([\w.]+):/);
|
|
61
|
+
if (m)
|
|
62
|
+
sketchIds.add(m[1]);
|
|
63
|
+
}
|
|
64
|
+
if (sketchIds.size === 0)
|
|
65
|
+
return;
|
|
66
|
+
for (const slice of roadmap.slices) {
|
|
67
|
+
if (sketchIds.has(slice.id))
|
|
68
|
+
slice.isSketch = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
49
71
|
function _parseRoadmapImpl(content) {
|
|
50
72
|
const stopTimer = debugTime("parse-roadmap");
|
|
51
73
|
// Try native parser first for better performance
|
|
52
74
|
const nativeResult = nativeParseRoadmap(content);
|
|
53
75
|
if (nativeResult) {
|
|
76
|
+
applySketchFlags(nativeResult, content);
|
|
54
77
|
stopTimer({ native: true, slices: nativeResult.slices.length, boundaryEntries: nativeResult.boundaryMap.length });
|
|
55
78
|
debugCount("parseRoadmapCalls");
|
|
56
79
|
return nativeResult;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Optional gsd-planner handoff after milestone planning.
|
|
3
|
+
import { spawn as spawnChild } from "node:child_process";
|
|
4
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { gsdRoot } from "./paths.js";
|
|
7
|
+
export const PLANNER_HANDOFF_RULE_NAME = "planning review handoff -> gsd-planner";
|
|
8
|
+
export const GSD_PLANNER_COMMAND = "gsd-planner";
|
|
9
|
+
function handoffDir(basePath) {
|
|
10
|
+
return join(gsdRoot(basePath), "runtime", "planner-handoffs");
|
|
11
|
+
}
|
|
12
|
+
function safeMilestoneFileSegment(milestoneId) {
|
|
13
|
+
return milestoneId.replace(/[^A-Za-z0-9._-]/g, "_") || "unknown";
|
|
14
|
+
}
|
|
15
|
+
function handoffMarkerPath(basePath, milestoneId) {
|
|
16
|
+
return join(handoffDir(basePath), `${safeMilestoneFileSegment(milestoneId)}.json`);
|
|
17
|
+
}
|
|
18
|
+
export function hasPlannerHandoffBeenOffered(basePath, milestoneId) {
|
|
19
|
+
return existsSync(handoffMarkerPath(basePath, milestoneId));
|
|
20
|
+
}
|
|
21
|
+
export function markPlannerHandoffOffered(basePath, milestoneId, source = "auto") {
|
|
22
|
+
mkdirSync(handoffDir(basePath), { recursive: true });
|
|
23
|
+
writeFileSync(handoffMarkerPath(basePath, milestoneId), JSON.stringify({
|
|
24
|
+
milestoneId,
|
|
25
|
+
source,
|
|
26
|
+
offeredAt: new Date().toISOString(),
|
|
27
|
+
}, null, 2) + "\n", "utf-8");
|
|
28
|
+
}
|
|
29
|
+
export function buildGsdPlannerSpawnPlan(input) {
|
|
30
|
+
const args = ["--project", input.basePath];
|
|
31
|
+
const milestoneId = input.milestoneId?.trim();
|
|
32
|
+
if (milestoneId)
|
|
33
|
+
args.push("--milestone", milestoneId);
|
|
34
|
+
args.push(...(input.extraArgs ?? []));
|
|
35
|
+
return {
|
|
36
|
+
command: GSD_PLANNER_COMMAND,
|
|
37
|
+
args,
|
|
38
|
+
cwd: input.basePath,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function quoteArg(arg) {
|
|
42
|
+
return /^[A-Za-z0-9_./:=@+-]+$/.test(arg) ? arg : JSON.stringify(arg);
|
|
43
|
+
}
|
|
44
|
+
export function formatGsdPlannerCommand(plan) {
|
|
45
|
+
return [plan.command, ...plan.args].map(quoteArg).join(" ");
|
|
46
|
+
}
|
|
47
|
+
export async function launchGsdPlanner(input, deps = {}) {
|
|
48
|
+
const plan = buildGsdPlannerSpawnPlan(input);
|
|
49
|
+
const spawn = deps.spawn ?? spawnChild;
|
|
50
|
+
let child;
|
|
51
|
+
try {
|
|
52
|
+
child = spawn(plan.command, plan.args, {
|
|
53
|
+
cwd: plan.cwd,
|
|
54
|
+
detached: true,
|
|
55
|
+
stdio: "ignore",
|
|
56
|
+
windowsHide: true,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
return {
|
|
61
|
+
status: "failed",
|
|
62
|
+
plan,
|
|
63
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return new Promise((resolve) => {
|
|
67
|
+
let settled = false;
|
|
68
|
+
const settle = (result) => {
|
|
69
|
+
if (settled)
|
|
70
|
+
return;
|
|
71
|
+
settled = true;
|
|
72
|
+
resolve(result);
|
|
73
|
+
};
|
|
74
|
+
child.once("error", (err) => {
|
|
75
|
+
settle({
|
|
76
|
+
status: "failed",
|
|
77
|
+
plan,
|
|
78
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
child.once("spawn", () => {
|
|
82
|
+
child.unref();
|
|
83
|
+
settle({ status: "launched", plan });
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
export function formatPlannerHandoffPauseReason(milestoneId) {
|
|
88
|
+
return [
|
|
89
|
+
`Milestone ${milestoneId} is planned. Review or customize the plan before implementation if needed.`,
|
|
90
|
+
`Run /gsd planner to launch ${GSD_PLANNER_COMMAND}, or run /gsd auto to continue without planner changes.`,
|
|
91
|
+
].join(" ");
|
|
92
|
+
}
|
|
93
|
+
export function formatPlannerLaunchUnavailable(plan, error) {
|
|
94
|
+
return [
|
|
95
|
+
`Could not launch ${GSD_PLANNER_COMMAND}: ${error.message}`,
|
|
96
|
+
`Install ${GSD_PLANNER_COMMAND} or run it manually: ${formatGsdPlannerCommand(plan)}`,
|
|
97
|
+
].join("\n");
|
|
98
|
+
}
|
|
@@ -10,6 +10,12 @@ function isInsideBase(basePath, candidate) {
|
|
|
10
10
|
function isInsideAnyBase(bases, candidate) {
|
|
11
11
|
return bases.some((base) => isInsideBase(base, candidate));
|
|
12
12
|
}
|
|
13
|
+
function resolvesInsideRoot(root, candidate) {
|
|
14
|
+
const resolvedCandidate = isAbsolute(candidate)
|
|
15
|
+
? resolve(candidate)
|
|
16
|
+
: resolve(root, candidate);
|
|
17
|
+
return isInsideBase(root, resolvedCandidate);
|
|
18
|
+
}
|
|
13
19
|
/**
|
|
14
20
|
* Planning IO fields are execution contracts. Absolute paths are only safe when
|
|
15
21
|
* they stay inside the active working directory; in worktree mode, an absolute
|
|
@@ -25,13 +31,25 @@ export function validatePlanningPathScope(basePath, fields, allowedAbsoluteRoots
|
|
|
25
31
|
if (!shouldValidatePlanningPathReference(trimmed))
|
|
26
32
|
continue;
|
|
27
33
|
const candidate = normalizePlannedFileReference(raw);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
if (isAbsolute(candidate)) {
|
|
35
|
+
if (isInsideAnyBase(absoluteRoots, resolve(candidate)))
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
else if (absoluteRoots.some((root) => resolvesInsideRoot(root, candidate))) {
|
|
32
39
|
continue;
|
|
40
|
+
}
|
|
33
41
|
return `${field} contains path outside allowed repository roots: ${candidate}. Use a path within one of: ${absoluteRoots.join(", ")}.`;
|
|
34
42
|
}
|
|
35
43
|
}
|
|
36
44
|
return null;
|
|
37
45
|
}
|
|
46
|
+
export function validatePathOnlyPlanningFields(fields) {
|
|
47
|
+
for (const { field, values } of fields) {
|
|
48
|
+
for (const raw of values) {
|
|
49
|
+
if (shouldValidatePlanningPathReference(raw))
|
|
50
|
+
continue;
|
|
51
|
+
return `${field} must contain only file paths; invalid entry: ${raw}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
@@ -405,6 +405,14 @@ export function shouldValidatePlanningPathReference(raw) {
|
|
|
405
405
|
return false;
|
|
406
406
|
return shouldValidateInputAsPath(raw);
|
|
407
407
|
}
|
|
408
|
+
export function extractPlanningPathReference(raw) {
|
|
409
|
+
const trimmed = raw.trim();
|
|
410
|
+
if (!trimmed || NON_PATH_SENTINEL_RE.test(trimmed))
|
|
411
|
+
return null;
|
|
412
|
+
if (!shouldValidateInputAsPath(trimmed))
|
|
413
|
+
return null;
|
|
414
|
+
return extractPathFromAnnotation(trimmed);
|
|
415
|
+
}
|
|
408
416
|
function shouldValidateInputAsPath(raw) {
|
|
409
417
|
const trimmed = raw.trim();
|
|
410
418
|
if (!trimmed)
|
|
@@ -421,7 +429,8 @@ function shouldValidateInputAsPath(raw) {
|
|
|
421
429
|
return false;
|
|
422
430
|
if (SCP_PATTERN.test(candidate))
|
|
423
431
|
return false;
|
|
424
|
-
|
|
432
|
+
const explicitlyWrappedPath = /^`+[^`]+`+/.test(trimmed) || /^(["'])([^"']+)\1$/.test(trimmed);
|
|
433
|
+
if (explicitlyWrappedPath && looksLikePathOrUrl(candidate)) {
|
|
425
434
|
return true;
|
|
426
435
|
}
|
|
427
436
|
if (!/\s/.test(candidate)) {
|
|
@@ -431,7 +440,6 @@ function shouldValidateInputAsPath(raw) {
|
|
|
431
440
|
candidate.startsWith("./") ||
|
|
432
441
|
candidate.startsWith("../") ||
|
|
433
442
|
candidate.startsWith("~/") ||
|
|
434
|
-
/[\\/]/.test(candidate) ||
|
|
435
443
|
/[*?[\]{}]/.test(candidate));
|
|
436
444
|
}
|
|
437
445
|
function isRuntimeOnlyInput(raw) {
|