gsd-pi 2.38.0-dev.eeb3520 → 2.39.0-dev.20aba06
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/README.md +15 -11
- package/dist/app-paths.js +1 -1
- package/dist/cli.js +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- package/dist/extension-registry.js +2 -2
- package/dist/remote-questions-config.js +2 -2
- package/dist/resource-loader.js +100 -3
- package/dist/resources/extensions/async-jobs/index.js +10 -0
- package/dist/resources/extensions/browser-tools/index.js +3 -1
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/get-secrets-from-user.js +5 -24
- package/dist/resources/extensions/github-sync/cli.js +284 -0
- package/dist/resources/extensions/github-sync/index.js +73 -0
- package/dist/resources/extensions/github-sync/mapping.js +67 -0
- package/dist/resources/extensions/github-sync/sync.js +424 -0
- package/dist/resources/extensions/github-sync/templates.js +118 -0
- package/dist/resources/extensions/github-sync/types.js +7 -0
- package/dist/resources/extensions/google-search/package.json +3 -1
- package/dist/resources/extensions/gsd/auto/session.js +6 -23
- package/dist/resources/extensions/gsd/auto-dashboard.js +7 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
- package/dist/resources/extensions/gsd/auto-loop.js +923 -787
- package/dist/resources/extensions/gsd/auto-post-unit.js +107 -70
- package/dist/resources/extensions/gsd/auto-prompts.js +205 -51
- package/dist/resources/extensions/gsd/auto-start.js +19 -3
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
- package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
- package/dist/resources/extensions/gsd/auto.js +149 -100
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +126 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +233 -0
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +59 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +38 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +156 -0
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +46 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +300 -0
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +38 -0
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands/catalog.js +278 -0
- package/dist/resources/extensions/gsd/commands/context.js +84 -0
- package/dist/resources/extensions/gsd/commands/dispatcher.js +21 -0
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +72 -0
- package/dist/resources/extensions/gsd/commands/handlers/core.js +246 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +166 -0
- package/dist/resources/extensions/gsd/commands/handlers/parallel.js +94 -0
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +102 -0
- package/dist/resources/extensions/gsd/commands/index.js +11 -0
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +17 -4
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +8 -1169
- package/dist/resources/extensions/gsd/context-budget.js +2 -10
- package/dist/resources/extensions/gsd/dashboard-overlay.js +9 -0
- package/dist/resources/extensions/gsd/detection.js +1 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +80 -10
- package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
- package/dist/resources/extensions/gsd/doctor.js +234 -12
- package/dist/resources/extensions/gsd/env-utils.js +29 -0
- package/dist/resources/extensions/gsd/exit-command.js +2 -1
- package/dist/resources/extensions/gsd/export-html.js +46 -0
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +48 -9
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-service.js +30 -12
- package/dist/resources/extensions/gsd/gitignore.js +16 -3
- package/dist/resources/extensions/gsd/guided-flow.js +149 -38
- package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
- package/dist/resources/extensions/gsd/health-widget.js +4 -87
- package/dist/resources/extensions/gsd/index.js +4 -1111
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/migrate-external.js +18 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/paths.js +3 -0
- package/dist/resources/extensions/gsd/preferences-models.js +0 -12
- package/dist/resources/extensions/gsd/preferences-types.js +1 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
- package/dist/resources/extensions/gsd/preferences.js +22 -11
- package/dist/resources/extensions/gsd/progress-score.js +20 -1
- package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
- package/dist/resources/extensions/gsd/prompts/forensics.md +121 -46
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.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/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/dist/resources/extensions/gsd/repo-identity.js +21 -4
- package/dist/resources/extensions/gsd/resource-version.js +2 -1
- package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
- package/dist/resources/extensions/gsd/state.js +42 -23
- package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
- package/dist/resources/extensions/gsd/visualizer-data.js +27 -2
- package/dist/resources/extensions/gsd/visualizer-views.js +52 -0
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/mcp-client/index.js +14 -1
- package/dist/resources/extensions/remote-questions/status.js +4 -1
- package/dist/resources/extensions/remote-questions/store.js +4 -1
- package/dist/resources/extensions/search-the-web/provider.js +2 -1
- package/dist/resources/extensions/shared/frontmatter.js +1 -1
- package/dist/resources/extensions/subagent/index.js +12 -3
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +13 -0
- package/dist/welcome-screen.js +97 -0
- package/package.json +1 -1
- package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
- package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
- package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +107 -24
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js +70 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +8 -2
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +244 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +58 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +12 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +54 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +63 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +38 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +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 +15 -457
- 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/agent-session.ts +122 -23
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/packages/pi-coding-agent/src/core/skill-tool.test.ts +89 -0
- package/packages/pi-coding-agent/src/core/skills.ts +11 -2
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +302 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +59 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +68 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +71 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +37 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +18 -510
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/index.ts +11 -0
- package/src/resources/extensions/browser-tools/index.ts +3 -0
- package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
- package/src/resources/extensions/cmux/index.ts +57 -1
- package/src/resources/extensions/get-secrets-from-user.ts +5 -24
- package/src/resources/extensions/github-sync/cli.ts +364 -0
- package/src/resources/extensions/github-sync/index.ts +93 -0
- package/src/resources/extensions/github-sync/mapping.ts +81 -0
- package/src/resources/extensions/github-sync/sync.ts +556 -0
- package/src/resources/extensions/github-sync/templates.ts +183 -0
- package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
- package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
- package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
- package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
- package/src/resources/extensions/github-sync/types.ts +47 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -25
- package/src/resources/extensions/gsd/auto-dashboard.ts +10 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
- package/src/resources/extensions/gsd/auto-loop.ts +1285 -1138
- package/src/resources/extensions/gsd/auto-post-unit.ts +90 -46
- package/src/resources/extensions/gsd/auto-prompts.ts +250 -53
- package/src/resources/extensions/gsd/auto-start.ts +24 -3
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
- package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
- package/src/resources/extensions/gsd/auto.ts +152 -111
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +142 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +238 -0
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +90 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +46 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +167 -0
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +55 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +340 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +51 -0
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands/catalog.ts +301 -0
- package/src/resources/extensions/gsd/commands/context.ts +101 -0
- package/src/resources/extensions/gsd/commands/dispatcher.ts +32 -0
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +74 -0
- package/src/resources/extensions/gsd/commands/handlers/core.ts +274 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +169 -0
- package/src/resources/extensions/gsd/commands/handlers/parallel.ts +118 -0
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +109 -0
- package/src/resources/extensions/gsd/commands/index.ts +14 -0
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +18 -3
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +10 -1307
- package/src/resources/extensions/gsd/context-budget.ts +2 -12
- package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
- package/src/resources/extensions/gsd/detection.ts +2 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +106 -10
- package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +243 -14
- package/src/resources/extensions/gsd/env-utils.ts +31 -0
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/export-html.ts +51 -0
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +51 -11
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +44 -10
- package/src/resources/extensions/gsd/gitignore.ts +17 -3
- package/src/resources/extensions/gsd/guided-flow.ts +177 -44
- package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
- package/src/resources/extensions/gsd/health-widget.ts +4 -89
- package/src/resources/extensions/gsd/index.ts +12 -1307
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/migrate-external.ts +18 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
- package/src/resources/extensions/gsd/paths.ts +4 -0
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +4 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
- package/src/resources/extensions/gsd/preferences.ts +25 -11
- package/src/resources/extensions/gsd/progress-score.ts +23 -0
- package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
- package/src/resources/extensions/gsd/prompts/forensics.md +121 -46
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-resume-task.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/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/src/resources/extensions/gsd/repo-identity.ts +23 -4
- package/src/resources/extensions/gsd/resource-version.ts +3 -1
- package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
- package/src/resources/extensions/gsd/state.ts +39 -21
- package/src/resources/extensions/gsd/templates/runtime.md +21 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +135 -77
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
- package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +16 -16
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +10 -10
- package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
- package/src/resources/extensions/gsd/types.ts +18 -1
- package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +52 -2
- package/src/resources/extensions/gsd/visualizer-views.ts +58 -0
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/mcp-client/index.ts +17 -1
- package/src/resources/extensions/remote-questions/status.ts +5 -1
- package/src/resources/extensions/remote-questions/store.ts +5 -1
- package/src/resources/extensions/search-the-web/provider.ts +2 -1
- package/src/resources/extensions/shared/frontmatter.ts +1 -1
- package/src/resources/extensions/subagent/index.ts +12 -3
- package/src/resources/extensions/subagent/isolation.ts +3 -1
- package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
- package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
- package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
- package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
- package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
- package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
- package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
- package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
- package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
- package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
- package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
- package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { loadFile, parsePlan, parseRoadmap, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
|
|
5
|
-
import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile } from "./paths.js";
|
|
5
|
+
import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile, relMilestonePath } from "./paths.js";
|
|
6
6
|
import { deriveState, isMilestoneComplete } from "./state.js";
|
|
7
7
|
import { invalidateAllCaches } from "./cache.js";
|
|
8
8
|
import { loadEffectiveGSDPreferences, type GSDPreferences } from "./preferences.js";
|
|
9
9
|
|
|
10
|
-
import type { DoctorIssue, DoctorIssueCode } from "./doctor-types.js";
|
|
10
|
+
import type { DoctorIssue, DoctorIssueCode, DoctorReport } from "./doctor-types.js";
|
|
11
11
|
import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
|
|
12
|
+
import type { RoadmapSliceEntry } from "./types.js";
|
|
12
13
|
import { checkGitHealth, checkRuntimeHealth } from "./doctor-checks.js";
|
|
13
14
|
import { checkEnvironmentHealth } from "./doctor-environment.js";
|
|
14
15
|
import { runProviderChecks } from "./doctor-providers.js";
|
|
@@ -17,7 +18,7 @@ import { runProviderChecks } from "./doctor-providers.js";
|
|
|
17
18
|
// All public types and functions from extracted modules are re-exported here
|
|
18
19
|
// so that existing imports from "./doctor.js" continue to work unchanged.
|
|
19
20
|
export type { DoctorSeverity, DoctorIssueCode, DoctorIssue, DoctorReport, DoctorSummary } from "./doctor-types.js";
|
|
20
|
-
export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt } from "./doctor-format.js";
|
|
21
|
+
export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt, formatDoctorReportJson } from "./doctor-format.js";
|
|
21
22
|
export { runEnvironmentChecks, runFullEnvironmentChecks, formatEnvironmentReport, type EnvironmentCheckResult } from "./doctor-environment.js";
|
|
22
23
|
export { computeProgressScore, computeProgressScoreWithContext, formatProgressLine, formatProgressReport, type ProgressScore, type ProgressLevel } from "./progress-score.js";
|
|
23
24
|
|
|
@@ -279,9 +280,24 @@ async function markSliceDoneInRoadmap(basePath: string, milestoneId: string, sli
|
|
|
279
280
|
}
|
|
280
281
|
}
|
|
281
282
|
|
|
283
|
+
async function markSliceUndoneInRoadmap(basePath: string, milestoneId: string, sliceId: string, fixesApplied: string[]): Promise<void> {
|
|
284
|
+
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
285
|
+
if (!roadmapPath) return;
|
|
286
|
+
const content = await loadFile(roadmapPath);
|
|
287
|
+
if (!content) return;
|
|
288
|
+
const updated = content.replace(
|
|
289
|
+
new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${sliceId}:`, "m"),
|
|
290
|
+
`$1[ ] **${sliceId}:`,
|
|
291
|
+
);
|
|
292
|
+
if (updated !== content) {
|
|
293
|
+
await saveFile(roadmapPath, updated);
|
|
294
|
+
fixesApplied.push(`unmarked ${sliceId} in ${roadmapPath} (premature completion)`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
282
298
|
function matchesScope(unitId: string, scope?: string): boolean {
|
|
283
299
|
if (!scope) return true;
|
|
284
|
-
return unitId === scope || unitId.startsWith(`${scope}/`)
|
|
300
|
+
return unitId === scope || unitId.startsWith(`${scope}/`);
|
|
285
301
|
}
|
|
286
302
|
|
|
287
303
|
function auditRequirements(content: string | null): DoctorIssue[] {
|
|
@@ -350,10 +366,104 @@ export async function selectDoctorScope(basePath: string, requestedScope?: strin
|
|
|
350
366
|
return state.registry[0]?.id;
|
|
351
367
|
}
|
|
352
368
|
|
|
353
|
-
|
|
369
|
+
// ── Helper: circular dependency detection ──────────────────────────────────
|
|
370
|
+
function detectCircularDependencies(slices: RoadmapSliceEntry[]): string[][] {
|
|
371
|
+
const known = new Set(slices.map(s => s.id));
|
|
372
|
+
const adj = new Map<string, string[]>();
|
|
373
|
+
for (const s of slices) adj.set(s.id, s.depends.filter(d => known.has(d)));
|
|
374
|
+
const state = new Map<string, "unvisited" | "visiting" | "done">();
|
|
375
|
+
for (const s of slices) state.set(s.id, "unvisited");
|
|
376
|
+
const cycles: string[][] = [];
|
|
377
|
+
function dfs(id: string, path: string[]): void {
|
|
378
|
+
const st = state.get(id);
|
|
379
|
+
if (st === "done") return;
|
|
380
|
+
if (st === "visiting") { cycles.push([...path.slice(path.indexOf(id)), id]); return; }
|
|
381
|
+
state.set(id, "visiting");
|
|
382
|
+
for (const dep of adj.get(id) ?? []) dfs(dep, [...path, id]);
|
|
383
|
+
state.set(id, "done");
|
|
384
|
+
}
|
|
385
|
+
for (const s of slices) if (state.get(s.id) === "unvisited") dfs(s.id, []);
|
|
386
|
+
return cycles;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ── Helper: doctor run history ──────────────────────────────────────────────
|
|
390
|
+
export interface DoctorHistoryEntry {
|
|
391
|
+
ts: string;
|
|
392
|
+
ok: boolean;
|
|
393
|
+
errors: number;
|
|
394
|
+
warnings: number;
|
|
395
|
+
fixes: number;
|
|
396
|
+
codes: string[];
|
|
397
|
+
/** Issue messages with severity and scope (added in Phase 2). */
|
|
398
|
+
issues?: Array<{ severity: string; code: string; message: string; unitId: string }>;
|
|
399
|
+
/** Fix descriptions applied during this run (added in Phase 2). */
|
|
400
|
+
fixDescriptions?: string[];
|
|
401
|
+
/** Milestone/slice scope this doctor run was scoped to (e.g. "M001/S02"). */
|
|
402
|
+
scope?: string;
|
|
403
|
+
/** Human-readable one-line summary of this doctor run. */
|
|
404
|
+
summary?: string;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function appendDoctorHistory(basePath: string, report: DoctorReport): Promise<void> {
|
|
408
|
+
try {
|
|
409
|
+
const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
|
|
410
|
+
const errorCount = report.issues.filter(i => i.severity === "error").length;
|
|
411
|
+
const warningCount = report.issues.filter(i => i.severity === "warning").length;
|
|
412
|
+
const issueDetails = report.issues
|
|
413
|
+
.filter(i => i.severity === "error" || i.severity === "warning")
|
|
414
|
+
.slice(0, 10) // cap to keep JSONL lines bounded
|
|
415
|
+
.map(i => ({ severity: i.severity, code: i.code, message: i.message, unitId: i.unitId }));
|
|
416
|
+
|
|
417
|
+
// Human-readable one-line summary
|
|
418
|
+
const summaryParts: string[] = [];
|
|
419
|
+
if (report.ok) {
|
|
420
|
+
summaryParts.push("Clean");
|
|
421
|
+
} else {
|
|
422
|
+
const counts: string[] = [];
|
|
423
|
+
if (errorCount > 0) counts.push(`${errorCount} error${errorCount > 1 ? "s" : ""}`);
|
|
424
|
+
if (warningCount > 0) counts.push(`${warningCount} warning${warningCount > 1 ? "s" : ""}`);
|
|
425
|
+
summaryParts.push(counts.join(", "));
|
|
426
|
+
}
|
|
427
|
+
if (report.fixesApplied.length > 0) {
|
|
428
|
+
summaryParts.push(`${report.fixesApplied.length} fixed`);
|
|
429
|
+
}
|
|
430
|
+
if (issueDetails.length > 0) {
|
|
431
|
+
const topIssue = issueDetails.find(i => i.severity === "error") ?? issueDetails[0]!;
|
|
432
|
+
summaryParts.push(topIssue.message);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const entry = JSON.stringify({
|
|
436
|
+
ts: new Date().toISOString(),
|
|
437
|
+
ok: report.ok,
|
|
438
|
+
errors: errorCount,
|
|
439
|
+
warnings: warningCount,
|
|
440
|
+
fixes: report.fixesApplied.length,
|
|
441
|
+
codes: [...new Set(report.issues.map(i => i.code))],
|
|
442
|
+
issues: issueDetails.length > 0 ? issueDetails : undefined,
|
|
443
|
+
fixDescriptions: report.fixesApplied.length > 0 ? report.fixesApplied : undefined,
|
|
444
|
+
scope: (report as any).scope as string | undefined,
|
|
445
|
+
summary: summaryParts.join(" · "),
|
|
446
|
+
} satisfies DoctorHistoryEntry);
|
|
447
|
+
const existing = existsSync(historyPath) ? readFileSync(historyPath, "utf-8") : "";
|
|
448
|
+
await saveFile(historyPath, existing + entry + "\n");
|
|
449
|
+
} catch { /* non-fatal */ }
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** Read the last N doctor history entries. Returns most-recent-first. */
|
|
453
|
+
export async function readDoctorHistory(basePath: string, lastN = 50): Promise<DoctorHistoryEntry[]> {
|
|
454
|
+
try {
|
|
455
|
+
const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
|
|
456
|
+
if (!existsSync(historyPath)) return [];
|
|
457
|
+
const lines = readFileSync(historyPath, "utf-8").split("\n").filter(l => l.trim());
|
|
458
|
+
return lines.slice(-lastN).reverse().map(l => JSON.parse(l) as DoctorHistoryEntry);
|
|
459
|
+
} catch { return []; }
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export async function runGSDDoctor(basePath: string, options?: { fix?: boolean; dryRun?: boolean; scope?: string; fixLevel?: "task" | "all"; isolationMode?: "none" | "worktree" | "branch"; includeBuild?: boolean; includeTests?: boolean }): Promise<DoctorReport> {
|
|
354
463
|
const issues: DoctorIssue[] = [];
|
|
355
464
|
const fixesApplied: string[] = [];
|
|
356
465
|
const fix = options?.fix === true;
|
|
466
|
+
const dryRun = options?.dryRun === true;
|
|
357
467
|
const fixLevel = options?.fixLevel ?? "all";
|
|
358
468
|
|
|
359
469
|
// Issue codes that represent completion state transitions — creating summary
|
|
@@ -364,11 +474,18 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
364
474
|
|
|
365
475
|
/** Whether a given issue code should be auto-fixed at the current fixLevel. */
|
|
366
476
|
const shouldFix = (code: DoctorIssueCode): boolean => {
|
|
367
|
-
if (!fix) return false;
|
|
477
|
+
if (!fix || dryRun) return false;
|
|
368
478
|
if (fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code)) return false;
|
|
369
479
|
return true;
|
|
370
480
|
};
|
|
371
481
|
|
|
482
|
+
/** Log a dry-run "would fix" entry when fix=true but dryRun=true. */
|
|
483
|
+
const dryRunCanFix = (code: DoctorIssueCode, message: string): void => {
|
|
484
|
+
if (dryRun && fix && !(fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))) {
|
|
485
|
+
fixesApplied.push(`[dry-run] would fix: ${message}`);
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
372
489
|
const prefs = loadEffectiveGSDPreferences();
|
|
373
490
|
if (prefs) {
|
|
374
491
|
const prefIssues = validatePreferenceShape(prefs.preferences);
|
|
@@ -385,21 +502,33 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
385
502
|
}
|
|
386
503
|
}
|
|
387
504
|
|
|
388
|
-
// Git health checks
|
|
505
|
+
// Git health checks — timed
|
|
506
|
+
const t0git = Date.now();
|
|
389
507
|
const isolationMode: "none" | "worktree" | "branch" = options?.isolationMode ??
|
|
390
508
|
(prefs?.preferences?.git?.isolation === "none" ? "none" :
|
|
391
509
|
prefs?.preferences?.git?.isolation === "branch" ? "branch" : "worktree");
|
|
392
510
|
await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode);
|
|
511
|
+
const gitMs = Date.now() - t0git;
|
|
393
512
|
|
|
394
|
-
// Runtime health checks
|
|
513
|
+
// Runtime health checks — timed
|
|
514
|
+
const t0runtime = Date.now();
|
|
395
515
|
await checkRuntimeHealth(basePath, issues, fixesApplied, shouldFix);
|
|
516
|
+
const runtimeMs = Date.now() - t0runtime;
|
|
396
517
|
|
|
397
|
-
// Environment health checks
|
|
398
|
-
|
|
518
|
+
// Environment health checks — timed
|
|
519
|
+
const t0env = Date.now();
|
|
520
|
+
await checkEnvironmentHealth(basePath, issues, {
|
|
521
|
+
includeRemote: !options?.scope,
|
|
522
|
+
includeBuild: options?.includeBuild,
|
|
523
|
+
includeTests: options?.includeTests,
|
|
524
|
+
});
|
|
525
|
+
const envMs = Date.now() - t0env;
|
|
399
526
|
|
|
400
527
|
const milestonesPath = milestonesDir(basePath);
|
|
401
528
|
if (!existsSync(milestonesPath)) {
|
|
402
|
-
|
|
529
|
+
const report: DoctorReport = { ok: issues.every(i => i.severity !== "error"), basePath, issues, fixesApplied, timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: 0 } };
|
|
530
|
+
await appendDoctorHistory(basePath, report);
|
|
531
|
+
return report;
|
|
403
532
|
}
|
|
404
533
|
|
|
405
534
|
const requirementsPath = resolveGsdRootFile(basePath, "REQUIREMENTS");
|
|
@@ -465,6 +594,43 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
465
594
|
if (!roadmapContent) continue;
|
|
466
595
|
const roadmap = parseRoadmap(roadmapContent);
|
|
467
596
|
|
|
597
|
+
// ── Circular dependency detection ──────────────────────────────────────
|
|
598
|
+
for (const cycle of detectCircularDependencies(roadmap.slices)) {
|
|
599
|
+
issues.push({
|
|
600
|
+
severity: "error",
|
|
601
|
+
code: "circular_slice_dependency",
|
|
602
|
+
scope: "milestone",
|
|
603
|
+
unitId: milestoneId,
|
|
604
|
+
message: `Circular dependency detected: ${cycle.join(" → ")}`,
|
|
605
|
+
file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
|
|
606
|
+
fixable: false,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// ── Orphaned slice directories ─────────────────────────────────────────
|
|
611
|
+
try {
|
|
612
|
+
const slicesDir = join(milestonePath, "slices");
|
|
613
|
+
if (existsSync(slicesDir)) {
|
|
614
|
+
const knownSliceIds = new Set(roadmap.slices.map(s => s.id));
|
|
615
|
+
for (const entry of readdirSync(slicesDir)) {
|
|
616
|
+
try {
|
|
617
|
+
if (!lstatSync(join(slicesDir, entry)).isDirectory()) continue;
|
|
618
|
+
} catch { continue; }
|
|
619
|
+
if (!knownSliceIds.has(entry)) {
|
|
620
|
+
issues.push({
|
|
621
|
+
severity: "warning",
|
|
622
|
+
code: "orphaned_slice_directory",
|
|
623
|
+
scope: "milestone",
|
|
624
|
+
unitId: milestoneId,
|
|
625
|
+
message: `Directory "${entry}" exists in ${milestoneId}/slices/ but is not referenced in the roadmap`,
|
|
626
|
+
file: `${relMilestonePath(basePath, milestoneId)}/slices/${entry}`,
|
|
627
|
+
fixable: false,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
} catch { /* non-fatal */ }
|
|
633
|
+
|
|
468
634
|
for (const slice of roadmap.slices) {
|
|
469
635
|
const unitId = `${milestoneId}/${slice.id}`;
|
|
470
636
|
if (options?.scope && !matchesScope(unitId, options.scope) && options.scope !== milestoneId) continue;
|
|
@@ -539,6 +705,33 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
539
705
|
continue;
|
|
540
706
|
}
|
|
541
707
|
|
|
708
|
+
// ── Duplicate task IDs ───────────────────────────────────────────────
|
|
709
|
+
const taskIdCounts = new Map<string, number>();
|
|
710
|
+
for (const task of plan.tasks) taskIdCounts.set(task.id, (taskIdCounts.get(task.id) ?? 0) + 1);
|
|
711
|
+
for (const [taskId, count] of taskIdCounts) {
|
|
712
|
+
if (count > 1) {
|
|
713
|
+
issues.push({ severity: "error", code: "duplicate_task_id", scope: "slice", unitId,
|
|
714
|
+
message: `Task ID "${taskId}" appears ${count} times in ${slice.id}-PLAN.md — duplicate IDs cause dispatch failures`,
|
|
715
|
+
file: relSliceFile(basePath, milestoneId, slice.id, "PLAN"), fixable: false });
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// ── Task files on disk not in plan ────────────────────────────────────
|
|
720
|
+
try {
|
|
721
|
+
if (tasksDir) {
|
|
722
|
+
const planTaskIds = new Set(plan.tasks.map(t => t.id));
|
|
723
|
+
for (const f of readdirSync(tasksDir)) {
|
|
724
|
+
if (!f.endsWith("-SUMMARY.md")) continue;
|
|
725
|
+
const diskTaskId = f.replace(/-SUMMARY\.md$/, "");
|
|
726
|
+
if (!planTaskIds.has(diskTaskId)) {
|
|
727
|
+
issues.push({ severity: "info", code: "task_file_not_in_plan", scope: "slice", unitId,
|
|
728
|
+
message: `Task summary "${f}" exists on disk but "${diskTaskId}" is not in ${slice.id}-PLAN.md`,
|
|
729
|
+
file: relTaskFile(basePath, milestoneId, slice.id, diskTaskId, "SUMMARY"), fixable: false });
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
} catch { /* non-fatal */ }
|
|
734
|
+
|
|
542
735
|
let allTasksDone = plan.tasks.length > 0;
|
|
543
736
|
for (const task of plan.tasks) {
|
|
544
737
|
const taskUnitId = `${unitId}/${task.id}`;
|
|
@@ -555,6 +748,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
555
748
|
file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"),
|
|
556
749
|
fixable: true,
|
|
557
750
|
});
|
|
751
|
+
dryRunCanFix("task_done_missing_summary", `create stub summary for ${taskUnitId}`);
|
|
558
752
|
if (shouldFix("task_done_missing_summary")) {
|
|
559
753
|
const stubPath = join(
|
|
560
754
|
basePath, ".gsd", "milestones", milestoneId, "slices", slice.id, "tasks",
|
|
@@ -618,6 +812,22 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
618
812
|
}
|
|
619
813
|
}
|
|
620
814
|
|
|
815
|
+
// ── Future timestamp check ─────────────────────────────────────
|
|
816
|
+
if (task.done && hasSummary && summaryPath) {
|
|
817
|
+
try {
|
|
818
|
+
const rawSummary = await loadFile(summaryPath);
|
|
819
|
+
const m = rawSummary?.match(/^completed_at:\s*(.+)$/m);
|
|
820
|
+
if (m) {
|
|
821
|
+
const ts = new Date(m[1].trim());
|
|
822
|
+
if (!isNaN(ts.getTime()) && ts.getTime() > Date.now() + 24 * 60 * 60 * 1000) {
|
|
823
|
+
issues.push({ severity: "warning", code: "future_timestamp", scope: "task", unitId: taskUnitId,
|
|
824
|
+
message: `Task ${task.id} has completed_at "${m[1].trim()}" which is more than 24h in the future`,
|
|
825
|
+
file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"), fixable: false });
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
} catch { /* non-fatal */ }
|
|
829
|
+
}
|
|
830
|
+
|
|
621
831
|
allTasksDone = allTasksDone && task.done;
|
|
622
832
|
}
|
|
623
833
|
|
|
@@ -646,6 +856,13 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
646
856
|
}
|
|
647
857
|
}
|
|
648
858
|
|
|
859
|
+
// ── Stale REPLAN: exists but all tasks done ────────────────────────
|
|
860
|
+
if (replanPath && allTasksDone) {
|
|
861
|
+
issues.push({ severity: "info", code: "stale_replan_file", scope: "slice", unitId,
|
|
862
|
+
message: `${slice.id} has a REPLAN.md but all tasks are done — REPLAN.md may be stale`,
|
|
863
|
+
file: relSliceFile(basePath, milestoneId, slice.id, "REPLAN"), fixable: false });
|
|
864
|
+
}
|
|
865
|
+
|
|
649
866
|
const sliceSummaryPath = resolveSliceFile(basePath, milestoneId, slice.id, "SUMMARY");
|
|
650
867
|
const sliceUatPath = join(slicePath, `${slice.id}-UAT.md`);
|
|
651
868
|
const hasSliceSummary = !!(sliceSummaryPath && await loadFile(sliceSummaryPath));
|
|
@@ -661,6 +878,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
661
878
|
file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
|
|
662
879
|
fixable: true,
|
|
663
880
|
});
|
|
881
|
+
dryRunCanFix("all_tasks_done_missing_slice_summary", `create placeholder summary for ${unitId}`);
|
|
664
882
|
if (shouldFix("all_tasks_done_missing_slice_summary")) await ensureSliceSummaryStub(basePath, milestoneId, slice.id, fixesApplied);
|
|
665
883
|
}
|
|
666
884
|
|
|
@@ -674,6 +892,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
674
892
|
file: `${relSlicePath(basePath, milestoneId, slice.id)}/${slice.id}-UAT.md`,
|
|
675
893
|
fixable: true,
|
|
676
894
|
});
|
|
895
|
+
dryRunCanFix("all_tasks_done_missing_slice_uat", `create placeholder UAT for ${unitId}`);
|
|
677
896
|
if (shouldFix("all_tasks_done_missing_slice_uat")) await ensureSliceUatStub(basePath, milestoneId, slice.id, fixesApplied);
|
|
678
897
|
}
|
|
679
898
|
|
|
@@ -687,6 +906,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
687
906
|
file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
|
|
688
907
|
fixable: true,
|
|
689
908
|
});
|
|
909
|
+
dryRunCanFix("all_tasks_done_roadmap_not_checked", `mark ${slice.id} done in roadmap`);
|
|
690
910
|
if (shouldFix("all_tasks_done_roadmap_not_checked") && (hasSliceSummary || issues.some(issue => issue.code === "all_tasks_done_missing_slice_summary" && issue.unitId === unitId))) {
|
|
691
911
|
await markSliceDoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
692
912
|
}
|
|
@@ -702,6 +922,12 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
702
922
|
file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
|
|
703
923
|
fixable: true,
|
|
704
924
|
});
|
|
925
|
+
if (!allTasksDone) {
|
|
926
|
+
dryRunCanFix("slice_checked_missing_summary", `uncheck ${slice.id} in roadmap (tasks incomplete)`);
|
|
927
|
+
if (shouldFix("slice_checked_missing_summary")) {
|
|
928
|
+
await markSliceUndoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
705
931
|
}
|
|
706
932
|
|
|
707
933
|
if (slice.done && !hasSliceUat) {
|
|
@@ -744,14 +970,17 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
744
970
|
}
|
|
745
971
|
}
|
|
746
972
|
|
|
747
|
-
if (fix && fixesApplied.length > 0) {
|
|
973
|
+
if (fix && !dryRun && fixesApplied.length > 0) {
|
|
748
974
|
await updateStateFile(basePath, fixesApplied);
|
|
749
975
|
}
|
|
750
976
|
|
|
751
|
-
|
|
977
|
+
const report: DoctorReport = {
|
|
752
978
|
ok: issues.every(issue => issue.severity !== "error"),
|
|
753
979
|
basePath,
|
|
754
980
|
issues,
|
|
755
981
|
fixesApplied,
|
|
982
|
+
timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: Math.max(0, Date.now() - t0env - envMs) },
|
|
756
983
|
};
|
|
984
|
+
await appendDoctorHistory(basePath, report);
|
|
985
|
+
return report;
|
|
757
986
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// GSD Extension — Environment variable utilities
|
|
2
|
+
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
3
|
+
//
|
|
4
|
+
// Pure utility for checking existing env keys in .env files and process.env.
|
|
5
|
+
// Extracted from get-secrets-from-user.ts to avoid pulling in @gsd/pi-tui
|
|
6
|
+
// when only env-checking is needed (e.g. from files.ts during report generation).
|
|
7
|
+
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Check which keys already exist in a .env file or process.env.
|
|
12
|
+
* Returns the subset of `keys` that are already set.
|
|
13
|
+
*/
|
|
14
|
+
export async function checkExistingEnvKeys(keys: string[], envFilePath: string): Promise<string[]> {
|
|
15
|
+
let fileContent = "";
|
|
16
|
+
try {
|
|
17
|
+
fileContent = await readFile(envFilePath, "utf8");
|
|
18
|
+
} catch {
|
|
19
|
+
// ENOENT or other read error — proceed with empty content
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const existing: string[] = [];
|
|
23
|
+
for (const key of keys) {
|
|
24
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
25
|
+
const regex = new RegExp(`^${escaped}\\s*=`, "m");
|
|
26
|
+
if (regex.test(fileContent) || key in process.env) {
|
|
27
|
+
existing.push(key);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return existing;
|
|
31
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { importExtensionModule, type ExtensionAPI, type ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
type StopAutoFn = (ctx: ExtensionCommandContext, pi: ExtensionAPI, reason?: string) => Promise<void>;
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ export function registerExitCommand(
|
|
|
10
10
|
description: "Exit GSD gracefully",
|
|
11
11
|
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
12
12
|
// Stop auto-mode first so locks and activity state are cleaned up before shutdown.
|
|
13
|
-
const stopAuto = deps.stopAuto ?? (await import("./auto.js")).stopAuto;
|
|
13
|
+
const stopAuto = deps.stopAuto ?? (await importExtensionModule<typeof import("./auto.js")>(import.meta.url, "./auto.js")).stopAuto;
|
|
14
14
|
await stopAuto(ctx, pi, "Graceful exit");
|
|
15
15
|
ctx.shutdown();
|
|
16
16
|
},
|
|
@@ -296,9 +296,60 @@ function buildHealthSection(data: VisualizerData): string {
|
|
|
296
296
|
</tbody>
|
|
297
297
|
</table>` : '';
|
|
298
298
|
|
|
299
|
+
// Progress score section
|
|
300
|
+
let progressHtml = '';
|
|
301
|
+
if (h.progressScore) {
|
|
302
|
+
const ps = h.progressScore;
|
|
303
|
+
const scoreColor = ps.level === 'green' ? '#22c55e' : ps.level === 'yellow' ? '#eab308' : '#ef4444';
|
|
304
|
+
const signalRows = ps.signals.map(s => {
|
|
305
|
+
const icon = s.kind === 'positive' ? '✓' : s.kind === 'negative' ? '✗' : '·';
|
|
306
|
+
const color = s.kind === 'positive' ? '#22c55e' : s.kind === 'negative' ? '#ef4444' : '#888';
|
|
307
|
+
return `<div style="margin-left:1em;color:${color}">${icon} ${esc(s.label)}</div>`;
|
|
308
|
+
}).join('');
|
|
309
|
+
progressHtml = `
|
|
310
|
+
<h3>Progress Score</h3>
|
|
311
|
+
<div style="font-size:1.1em;font-weight:bold;color:${scoreColor}">● ${esc(ps.summary)}</div>
|
|
312
|
+
${signalRows}`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Doctor history section
|
|
316
|
+
let historyHtml = '';
|
|
317
|
+
const doctorHistory = h.doctorHistory ?? [];
|
|
318
|
+
if (doctorHistory.length > 0) {
|
|
319
|
+
const historyRows = doctorHistory.slice(0, 20).map(entry => {
|
|
320
|
+
const statusIcon = entry.ok ? '✓' : '✗';
|
|
321
|
+
const statusColor = entry.ok ? '#22c55e' : '#ef4444';
|
|
322
|
+
const ts = entry.ts.replace('T', ' ').slice(0, 19);
|
|
323
|
+
const scopeTag = entry.scope ? `<span class="mono" style="color:#888"> [${esc(entry.scope)}]</span>` : '';
|
|
324
|
+
const summaryText = entry.summary ? esc(entry.summary) : `${entry.errors} errors, ${entry.warnings} warnings, ${entry.fixes} fixes`;
|
|
325
|
+
const issueDetails = (entry.issues ?? []).slice(0, 3).map(i => {
|
|
326
|
+
const iColor = i.severity === 'error' ? '#ef4444' : '#eab308';
|
|
327
|
+
return `<div style="margin-left:2em;color:${iColor};font-size:0.85em">${i.severity === 'error' ? '✗' : '⚠'} ${esc(i.message)} <span class="mono" style="color:#888">${esc(i.unitId)}</span></div>`;
|
|
328
|
+
}).join('');
|
|
329
|
+
const fixDetails = (entry.fixDescriptions ?? []).slice(0, 2).map(f =>
|
|
330
|
+
`<div style="margin-left:2em;color:#22c55e;font-size:0.85em">↳ ${esc(f)}</div>`
|
|
331
|
+
).join('');
|
|
332
|
+
return `<tr style="color:${statusColor}">
|
|
333
|
+
<td class="mono">${statusIcon}</td>
|
|
334
|
+
<td class="mono">${esc(ts)}${scopeTag}</td>
|
|
335
|
+
<td>${summaryText}</td>
|
|
336
|
+
</tr>
|
|
337
|
+
${issueDetails || fixDetails ? `<tr><td colspan="3">${issueDetails}${fixDetails}</td></tr>` : ''}`;
|
|
338
|
+
}).join('');
|
|
339
|
+
|
|
340
|
+
historyHtml = `
|
|
341
|
+
<h3>Doctor Run History</h3>
|
|
342
|
+
<table class="tbl">
|
|
343
|
+
<thead><tr><th></th><th>Time</th><th>Summary</th></tr></thead>
|
|
344
|
+
<tbody>${historyRows}</tbody>
|
|
345
|
+
</table>`;
|
|
346
|
+
}
|
|
347
|
+
|
|
299
348
|
return section('health', 'Health', `
|
|
300
349
|
<table class="tbl tbl-kv"><tbody>${rows.join('')}</tbody></table>
|
|
301
350
|
${tierRows}
|
|
351
|
+
${progressHtml}
|
|
352
|
+
${historyHtml}
|
|
302
353
|
`);
|
|
303
354
|
}
|
|
304
355
|
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "./metrics.js";
|
|
12
12
|
import type { UnitMetrics } from "./metrics.js";
|
|
13
13
|
import { gsdRoot } from "./paths.js";
|
|
14
|
-
import { formatDuration, fileLink } from "../shared/
|
|
14
|
+
import { formatDuration, fileLink } from "../shared/format-utils.js";
|
|
15
15
|
import { getErrorMessage } from "./error-utils.js";
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -7,11 +7,11 @@ import { promises as fs } from 'node:fs';
|
|
|
7
7
|
import { resolve } from 'node:path';
|
|
8
8
|
import { atomicWriteAsync } from './atomic-write.js';
|
|
9
9
|
import { resolveMilestoneFile, relMilestoneFile, resolveGsdRootFile } from './paths.js';
|
|
10
|
-
import { milestoneIdSort, findMilestoneIds } from './
|
|
10
|
+
import { milestoneIdSort, findMilestoneIds } from './milestone-ids.js';
|
|
11
11
|
|
|
12
12
|
import type {
|
|
13
13
|
Roadmap, BoundaryMapEntry,
|
|
14
|
-
SlicePlan, TaskPlanEntry,
|
|
14
|
+
SlicePlan, TaskPlanEntry, TaskPlanFile, TaskPlanFrontmatter,
|
|
15
15
|
Summary, SummaryFrontmatter, SummaryRequires, FileModified,
|
|
16
16
|
Continue, ContinueFrontmatter, ContinueStatus,
|
|
17
17
|
RequirementCounts,
|
|
@@ -20,7 +20,7 @@ import type {
|
|
|
20
20
|
ManifestStatus,
|
|
21
21
|
} from './types.js';
|
|
22
22
|
|
|
23
|
-
import { checkExistingEnvKeys } from '
|
|
23
|
+
import { checkExistingEnvKeys } from './env-utils.js';
|
|
24
24
|
import { parseRoadmapSlices } from './roadmap-slices.js';
|
|
25
25
|
import { nativeParseRoadmap, nativeExtractSection, nativeParsePlanFile, nativeParseSummaryFile, NATIVE_UNAVAILABLE } from './native-parser-bridge.js';
|
|
26
26
|
import { debugTime, debugCount } from './debug-logger.js';
|
|
@@ -277,14 +277,52 @@ export function formatSecretsManifest(manifest: SecretsManifest): string {
|
|
|
277
277
|
|
|
278
278
|
// ─── Slice Plan Parser ─────────────────────────────────────────────────────
|
|
279
279
|
|
|
280
|
+
function normalizeTaskPlanFrontmatter(frontmatter: Record<string, unknown>): TaskPlanFrontmatter {
|
|
281
|
+
const estimatedStepsRaw = frontmatter.estimated_steps;
|
|
282
|
+
const estimatedFilesRaw = frontmatter.estimated_files;
|
|
283
|
+
const skillsUsedRaw = frontmatter.skills_used;
|
|
284
|
+
|
|
285
|
+
const parseOptionalNumber = (value: unknown): number | undefined => {
|
|
286
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
287
|
+
if (typeof value === 'string' && value.trim()) {
|
|
288
|
+
const parsed = parseInt(value, 10);
|
|
289
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
290
|
+
}
|
|
291
|
+
return undefined;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const estimated_steps = parseOptionalNumber(estimatedStepsRaw);
|
|
295
|
+
const estimated_files = parseOptionalNumber(estimatedFilesRaw);
|
|
296
|
+
const skills_used = Array.isArray(skillsUsedRaw)
|
|
297
|
+
? skillsUsedRaw.map(v => String(v).trim()).filter(Boolean)
|
|
298
|
+
: typeof skillsUsedRaw === 'string' && skillsUsedRaw.trim()
|
|
299
|
+
? [skillsUsedRaw.trim()]
|
|
300
|
+
: [];
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
...(estimated_steps !== undefined ? { estimated_steps } : {}),
|
|
304
|
+
...(estimated_files !== undefined ? { estimated_files } : {}),
|
|
305
|
+
skills_used,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function parseTaskPlanFile(content: string): TaskPlanFile {
|
|
310
|
+
const [fmLines] = splitFrontmatter(content);
|
|
311
|
+
const fm = fmLines ? parseFrontmatterMap(fmLines) : {};
|
|
312
|
+
return {
|
|
313
|
+
frontmatter: normalizeTaskPlanFrontmatter(fm),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
280
317
|
export function parsePlan(content: string): SlicePlan {
|
|
281
318
|
return cachedParse(content, 'plan', _parsePlanImpl);
|
|
282
319
|
}
|
|
283
320
|
|
|
284
321
|
function _parsePlanImpl(content: string): SlicePlan {
|
|
285
322
|
const stopTimer = debugTime("parse-plan");
|
|
323
|
+
const [, body] = splitFrontmatter(content);
|
|
286
324
|
// Try native parser first for better performance
|
|
287
|
-
const nativeResult = nativeParsePlanFile(
|
|
325
|
+
const nativeResult = nativeParsePlanFile(body);
|
|
288
326
|
if (nativeResult) {
|
|
289
327
|
stopTimer({ native: true });
|
|
290
328
|
return {
|
|
@@ -306,7 +344,7 @@ function _parsePlanImpl(content: string): SlicePlan {
|
|
|
306
344
|
};
|
|
307
345
|
}
|
|
308
346
|
|
|
309
|
-
const lines =
|
|
347
|
+
const lines = body.split('\n');
|
|
310
348
|
|
|
311
349
|
const h1 = lines.find(l => l.startsWith('# '));
|
|
312
350
|
let id = '';
|
|
@@ -321,13 +359,13 @@ function _parsePlanImpl(content: string): SlicePlan {
|
|
|
321
359
|
}
|
|
322
360
|
}
|
|
323
361
|
|
|
324
|
-
const goal = extractBoldField(
|
|
325
|
-
const demo = extractBoldField(
|
|
362
|
+
const goal = extractBoldField(body, 'Goal') || '';
|
|
363
|
+
const demo = extractBoldField(body, 'Demo') || '';
|
|
326
364
|
|
|
327
|
-
const mhSection = extractSection(
|
|
365
|
+
const mhSection = extractSection(body, 'Must-Haves');
|
|
328
366
|
const mustHaves = mhSection ? parseBullets(mhSection) : [];
|
|
329
367
|
|
|
330
|
-
const tasksSection = extractSection(
|
|
368
|
+
const tasksSection = extractSection(body, 'Tasks');
|
|
331
369
|
const tasks: TaskPlanEntry[] = [];
|
|
332
370
|
|
|
333
371
|
if (tasksSection) {
|
|
@@ -375,7 +413,7 @@ function _parsePlanImpl(content: string): SlicePlan {
|
|
|
375
413
|
if (currentTask) tasks.push(currentTask);
|
|
376
414
|
}
|
|
377
415
|
|
|
378
|
-
const filesSection = extractSection(
|
|
416
|
+
const filesSection = extractSection(body, 'Files Likely Touched');
|
|
379
417
|
const filesLikelyTouched = filesSection ? parseBullets(filesSection) : [];
|
|
380
418
|
|
|
381
419
|
const result = { id, title, goal, demo, mustHaves, tasks, filesLikelyTouched };
|
|
@@ -775,7 +813,7 @@ export function parseTaskPlanIO(content: string): { inputFiles: string[]; output
|
|
|
775
813
|
* The four UAT classification types recognised by GSD auto-mode.
|
|
776
814
|
* `undefined` is returned (not this union) when no type can be determined.
|
|
777
815
|
*/
|
|
778
|
-
export type UatType = 'artifact-driven' | 'live-runtime' | 'human-experience' | 'mixed';
|
|
816
|
+
export type UatType = 'artifact-driven' | 'live-runtime' | 'human-experience' | 'mixed' | 'browser-executable' | 'runtime-executable';
|
|
779
817
|
|
|
780
818
|
/**
|
|
781
819
|
* Extract the UAT type from a UAT file's raw content.
|
|
@@ -799,6 +837,8 @@ export function extractUatType(content: string): UatType | undefined {
|
|
|
799
837
|
const rawValue = modeBullet.slice('UAT mode:'.length).trim().toLowerCase();
|
|
800
838
|
|
|
801
839
|
if (rawValue.startsWith('artifact-driven')) return 'artifact-driven';
|
|
840
|
+
if (rawValue.startsWith('browser-executable')) return 'browser-executable';
|
|
841
|
+
if (rawValue.startsWith('runtime-executable')) return 'runtime-executable';
|
|
802
842
|
if (rawValue.startsWith('live-runtime')) return 'live-runtime';
|
|
803
843
|
if (rawValue.startsWith('human-experience')) return 'human-experience';
|
|
804
844
|
if (rawValue.startsWith('mixed')) return 'mixed';
|
|
@@ -27,7 +27,7 @@ import { deriveState } from "./state.js";
|
|
|
27
27
|
import { isAutoActive } from "./auto.js";
|
|
28
28
|
import { loadPrompt } from "./prompt-loader.js";
|
|
29
29
|
import { gsdRoot } from "./paths.js";
|
|
30
|
-
import { formatDuration } from "../shared/
|
|
30
|
+
import { formatDuration } from "../shared/format-utils.js";
|
|
31
31
|
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
32
32
|
|
|
33
33
|
// ─── Types ────────────────────────────────────────────────────────────────────
|