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,7 +1,7 @@
|
|
|
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
|
import { loadFile, parsePlan, parseRoadmap, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
|
|
4
|
-
import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile } from "./paths.js";
|
|
4
|
+
import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile, relMilestonePath } from "./paths.js";
|
|
5
5
|
import { deriveState, isMilestoneComplete } from "./state.js";
|
|
6
6
|
import { invalidateAllCaches } from "./cache.js";
|
|
7
7
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
@@ -9,7 +9,7 @@ import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
|
|
|
9
9
|
import { checkGitHealth, checkRuntimeHealth } from "./doctor-checks.js";
|
|
10
10
|
import { checkEnvironmentHealth } from "./doctor-environment.js";
|
|
11
11
|
import { runProviderChecks } from "./doctor-providers.js";
|
|
12
|
-
export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt } from "./doctor-format.js";
|
|
12
|
+
export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt, formatDoctorReportJson } from "./doctor-format.js";
|
|
13
13
|
export { runEnvironmentChecks, runFullEnvironmentChecks, formatEnvironmentReport } from "./doctor-environment.js";
|
|
14
14
|
export { computeProgressScore, computeProgressScoreWithContext, formatProgressLine, formatProgressReport } from "./progress-score.js";
|
|
15
15
|
/**
|
|
@@ -257,10 +257,23 @@ async function markSliceDoneInRoadmap(basePath, milestoneId, sliceId, fixesAppli
|
|
|
257
257
|
fixesApplied.push(`marked ${sliceId} done in ${roadmapPath}`);
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
|
+
async function markSliceUndoneInRoadmap(basePath, milestoneId, sliceId, fixesApplied) {
|
|
261
|
+
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
262
|
+
if (!roadmapPath)
|
|
263
|
+
return;
|
|
264
|
+
const content = await loadFile(roadmapPath);
|
|
265
|
+
if (!content)
|
|
266
|
+
return;
|
|
267
|
+
const updated = content.replace(new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${sliceId}:`, "m"), `$1[ ] **${sliceId}:`);
|
|
268
|
+
if (updated !== content) {
|
|
269
|
+
await saveFile(roadmapPath, updated);
|
|
270
|
+
fixesApplied.push(`unmarked ${sliceId} in ${roadmapPath} (premature completion)`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
260
273
|
function matchesScope(unitId, scope) {
|
|
261
274
|
if (!scope)
|
|
262
275
|
return true;
|
|
263
|
-
return unitId === scope || unitId.startsWith(`${scope}/`)
|
|
276
|
+
return unitId === scope || unitId.startsWith(`${scope}/`);
|
|
264
277
|
}
|
|
265
278
|
function auditRequirements(content) {
|
|
266
279
|
if (!content)
|
|
@@ -324,10 +337,98 @@ export async function selectDoctorScope(basePath, requestedScope) {
|
|
|
324
337
|
}
|
|
325
338
|
return state.registry[0]?.id;
|
|
326
339
|
}
|
|
340
|
+
// ── Helper: circular dependency detection ──────────────────────────────────
|
|
341
|
+
function detectCircularDependencies(slices) {
|
|
342
|
+
const known = new Set(slices.map(s => s.id));
|
|
343
|
+
const adj = new Map();
|
|
344
|
+
for (const s of slices)
|
|
345
|
+
adj.set(s.id, s.depends.filter(d => known.has(d)));
|
|
346
|
+
const state = new Map();
|
|
347
|
+
for (const s of slices)
|
|
348
|
+
state.set(s.id, "unvisited");
|
|
349
|
+
const cycles = [];
|
|
350
|
+
function dfs(id, path) {
|
|
351
|
+
const st = state.get(id);
|
|
352
|
+
if (st === "done")
|
|
353
|
+
return;
|
|
354
|
+
if (st === "visiting") {
|
|
355
|
+
cycles.push([...path.slice(path.indexOf(id)), id]);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
state.set(id, "visiting");
|
|
359
|
+
for (const dep of adj.get(id) ?? [])
|
|
360
|
+
dfs(dep, [...path, id]);
|
|
361
|
+
state.set(id, "done");
|
|
362
|
+
}
|
|
363
|
+
for (const s of slices)
|
|
364
|
+
if (state.get(s.id) === "unvisited")
|
|
365
|
+
dfs(s.id, []);
|
|
366
|
+
return cycles;
|
|
367
|
+
}
|
|
368
|
+
async function appendDoctorHistory(basePath, report) {
|
|
369
|
+
try {
|
|
370
|
+
const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
|
|
371
|
+
const errorCount = report.issues.filter(i => i.severity === "error").length;
|
|
372
|
+
const warningCount = report.issues.filter(i => i.severity === "warning").length;
|
|
373
|
+
const issueDetails = report.issues
|
|
374
|
+
.filter(i => i.severity === "error" || i.severity === "warning")
|
|
375
|
+
.slice(0, 10) // cap to keep JSONL lines bounded
|
|
376
|
+
.map(i => ({ severity: i.severity, code: i.code, message: i.message, unitId: i.unitId }));
|
|
377
|
+
// Human-readable one-line summary
|
|
378
|
+
const summaryParts = [];
|
|
379
|
+
if (report.ok) {
|
|
380
|
+
summaryParts.push("Clean");
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
const counts = [];
|
|
384
|
+
if (errorCount > 0)
|
|
385
|
+
counts.push(`${errorCount} error${errorCount > 1 ? "s" : ""}`);
|
|
386
|
+
if (warningCount > 0)
|
|
387
|
+
counts.push(`${warningCount} warning${warningCount > 1 ? "s" : ""}`);
|
|
388
|
+
summaryParts.push(counts.join(", "));
|
|
389
|
+
}
|
|
390
|
+
if (report.fixesApplied.length > 0) {
|
|
391
|
+
summaryParts.push(`${report.fixesApplied.length} fixed`);
|
|
392
|
+
}
|
|
393
|
+
if (issueDetails.length > 0) {
|
|
394
|
+
const topIssue = issueDetails.find(i => i.severity === "error") ?? issueDetails[0];
|
|
395
|
+
summaryParts.push(topIssue.message);
|
|
396
|
+
}
|
|
397
|
+
const entry = JSON.stringify({
|
|
398
|
+
ts: new Date().toISOString(),
|
|
399
|
+
ok: report.ok,
|
|
400
|
+
errors: errorCount,
|
|
401
|
+
warnings: warningCount,
|
|
402
|
+
fixes: report.fixesApplied.length,
|
|
403
|
+
codes: [...new Set(report.issues.map(i => i.code))],
|
|
404
|
+
issues: issueDetails.length > 0 ? issueDetails : undefined,
|
|
405
|
+
fixDescriptions: report.fixesApplied.length > 0 ? report.fixesApplied : undefined,
|
|
406
|
+
scope: report.scope,
|
|
407
|
+
summary: summaryParts.join(" · "),
|
|
408
|
+
});
|
|
409
|
+
const existing = existsSync(historyPath) ? readFileSync(historyPath, "utf-8") : "";
|
|
410
|
+
await saveFile(historyPath, existing + entry + "\n");
|
|
411
|
+
}
|
|
412
|
+
catch { /* non-fatal */ }
|
|
413
|
+
}
|
|
414
|
+
/** Read the last N doctor history entries. Returns most-recent-first. */
|
|
415
|
+
export async function readDoctorHistory(basePath, lastN = 50) {
|
|
416
|
+
try {
|
|
417
|
+
const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
|
|
418
|
+
if (!existsSync(historyPath))
|
|
419
|
+
return [];
|
|
420
|
+
const lines = readFileSync(historyPath, "utf-8").split("\n").filter(l => l.trim());
|
|
421
|
+
return lines.slice(-lastN).reverse().map(l => JSON.parse(l));
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
return [];
|
|
425
|
+
}
|
|
426
|
+
}
|
|
327
427
|
export async function runGSDDoctor(basePath, options) {
|
|
328
428
|
const issues = [];
|
|
329
429
|
const fixesApplied = [];
|
|
330
430
|
const fix = options?.fix === true;
|
|
431
|
+
const dryRun = options?.dryRun === true;
|
|
331
432
|
const fixLevel = options?.fixLevel ?? "all";
|
|
332
433
|
// Issue codes that represent completion state transitions — creating summary
|
|
333
434
|
// stubs, marking slices/milestones done in the roadmap. These belong to the
|
|
@@ -336,12 +437,18 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
336
437
|
// detected and reported but never auto-fixed.
|
|
337
438
|
/** Whether a given issue code should be auto-fixed at the current fixLevel. */
|
|
338
439
|
const shouldFix = (code) => {
|
|
339
|
-
if (!fix)
|
|
440
|
+
if (!fix || dryRun)
|
|
340
441
|
return false;
|
|
341
442
|
if (fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))
|
|
342
443
|
return false;
|
|
343
444
|
return true;
|
|
344
445
|
};
|
|
446
|
+
/** Log a dry-run "would fix" entry when fix=true but dryRun=true. */
|
|
447
|
+
const dryRunCanFix = (code, message) => {
|
|
448
|
+
if (dryRun && fix && !(fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))) {
|
|
449
|
+
fixesApplied.push(`[dry-run] would fix: ${message}`);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
345
452
|
const prefs = loadEffectiveGSDPreferences();
|
|
346
453
|
if (prefs) {
|
|
347
454
|
const prefIssues = validatePreferenceShape(prefs.preferences);
|
|
@@ -357,18 +464,30 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
357
464
|
});
|
|
358
465
|
}
|
|
359
466
|
}
|
|
360
|
-
// Git health checks
|
|
467
|
+
// Git health checks — timed
|
|
468
|
+
const t0git = Date.now();
|
|
361
469
|
const isolationMode = options?.isolationMode ??
|
|
362
470
|
(prefs?.preferences?.git?.isolation === "none" ? "none" :
|
|
363
471
|
prefs?.preferences?.git?.isolation === "branch" ? "branch" : "worktree");
|
|
364
472
|
await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode);
|
|
365
|
-
|
|
473
|
+
const gitMs = Date.now() - t0git;
|
|
474
|
+
// Runtime health checks — timed
|
|
475
|
+
const t0runtime = Date.now();
|
|
366
476
|
await checkRuntimeHealth(basePath, issues, fixesApplied, shouldFix);
|
|
367
|
-
|
|
368
|
-
|
|
477
|
+
const runtimeMs = Date.now() - t0runtime;
|
|
478
|
+
// Environment health checks — timed
|
|
479
|
+
const t0env = Date.now();
|
|
480
|
+
await checkEnvironmentHealth(basePath, issues, {
|
|
481
|
+
includeRemote: !options?.scope,
|
|
482
|
+
includeBuild: options?.includeBuild,
|
|
483
|
+
includeTests: options?.includeTests,
|
|
484
|
+
});
|
|
485
|
+
const envMs = Date.now() - t0env;
|
|
369
486
|
const milestonesPath = milestonesDir(basePath);
|
|
370
487
|
if (!existsSync(milestonesPath)) {
|
|
371
|
-
|
|
488
|
+
const report = { ok: issues.every(i => i.severity !== "error"), basePath, issues, fixesApplied, timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: 0 } };
|
|
489
|
+
await appendDoctorHistory(basePath, report);
|
|
490
|
+
return report;
|
|
372
491
|
}
|
|
373
492
|
const requirementsPath = resolveGsdRootFile(basePath, "REQUIREMENTS");
|
|
374
493
|
const requirementsContent = await loadFile(requirementsPath);
|
|
@@ -432,6 +551,46 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
432
551
|
if (!roadmapContent)
|
|
433
552
|
continue;
|
|
434
553
|
const roadmap = parseRoadmap(roadmapContent);
|
|
554
|
+
// ── Circular dependency detection ──────────────────────────────────────
|
|
555
|
+
for (const cycle of detectCircularDependencies(roadmap.slices)) {
|
|
556
|
+
issues.push({
|
|
557
|
+
severity: "error",
|
|
558
|
+
code: "circular_slice_dependency",
|
|
559
|
+
scope: "milestone",
|
|
560
|
+
unitId: milestoneId,
|
|
561
|
+
message: `Circular dependency detected: ${cycle.join(" → ")}`,
|
|
562
|
+
file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
|
|
563
|
+
fixable: false,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
// ── Orphaned slice directories ─────────────────────────────────────────
|
|
567
|
+
try {
|
|
568
|
+
const slicesDir = join(milestonePath, "slices");
|
|
569
|
+
if (existsSync(slicesDir)) {
|
|
570
|
+
const knownSliceIds = new Set(roadmap.slices.map(s => s.id));
|
|
571
|
+
for (const entry of readdirSync(slicesDir)) {
|
|
572
|
+
try {
|
|
573
|
+
if (!lstatSync(join(slicesDir, entry)).isDirectory())
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
catch {
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
if (!knownSliceIds.has(entry)) {
|
|
580
|
+
issues.push({
|
|
581
|
+
severity: "warning",
|
|
582
|
+
code: "orphaned_slice_directory",
|
|
583
|
+
scope: "milestone",
|
|
584
|
+
unitId: milestoneId,
|
|
585
|
+
message: `Directory "${entry}" exists in ${milestoneId}/slices/ but is not referenced in the roadmap`,
|
|
586
|
+
file: `${relMilestonePath(basePath, milestoneId)}/slices/${entry}`,
|
|
587
|
+
fixable: false,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
catch { /* non-fatal */ }
|
|
435
594
|
for (const slice of roadmap.slices) {
|
|
436
595
|
const unitId = `${milestoneId}/${slice.id}`;
|
|
437
596
|
if (options?.scope && !matchesScope(unitId, options.scope) && options.scope !== milestoneId)
|
|
@@ -502,6 +661,34 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
502
661
|
}
|
|
503
662
|
continue;
|
|
504
663
|
}
|
|
664
|
+
// ── Duplicate task IDs ───────────────────────────────────────────────
|
|
665
|
+
const taskIdCounts = new Map();
|
|
666
|
+
for (const task of plan.tasks)
|
|
667
|
+
taskIdCounts.set(task.id, (taskIdCounts.get(task.id) ?? 0) + 1);
|
|
668
|
+
for (const [taskId, count] of taskIdCounts) {
|
|
669
|
+
if (count > 1) {
|
|
670
|
+
issues.push({ severity: "error", code: "duplicate_task_id", scope: "slice", unitId,
|
|
671
|
+
message: `Task ID "${taskId}" appears ${count} times in ${slice.id}-PLAN.md — duplicate IDs cause dispatch failures`,
|
|
672
|
+
file: relSliceFile(basePath, milestoneId, slice.id, "PLAN"), fixable: false });
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
// ── Task files on disk not in plan ────────────────────────────────────
|
|
676
|
+
try {
|
|
677
|
+
if (tasksDir) {
|
|
678
|
+
const planTaskIds = new Set(plan.tasks.map(t => t.id));
|
|
679
|
+
for (const f of readdirSync(tasksDir)) {
|
|
680
|
+
if (!f.endsWith("-SUMMARY.md"))
|
|
681
|
+
continue;
|
|
682
|
+
const diskTaskId = f.replace(/-SUMMARY\.md$/, "");
|
|
683
|
+
if (!planTaskIds.has(diskTaskId)) {
|
|
684
|
+
issues.push({ severity: "info", code: "task_file_not_in_plan", scope: "slice", unitId,
|
|
685
|
+
message: `Task summary "${f}" exists on disk but "${diskTaskId}" is not in ${slice.id}-PLAN.md`,
|
|
686
|
+
file: relTaskFile(basePath, milestoneId, slice.id, diskTaskId, "SUMMARY"), fixable: false });
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
catch { /* non-fatal */ }
|
|
505
692
|
let allTasksDone = plan.tasks.length > 0;
|
|
506
693
|
for (const task of plan.tasks) {
|
|
507
694
|
const taskUnitId = `${unitId}/${task.id}`;
|
|
@@ -517,6 +704,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
517
704
|
file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"),
|
|
518
705
|
fixable: true,
|
|
519
706
|
});
|
|
707
|
+
dryRunCanFix("task_done_missing_summary", `create stub summary for ${taskUnitId}`);
|
|
520
708
|
if (shouldFix("task_done_missing_summary")) {
|
|
521
709
|
const stubPath = join(basePath, ".gsd", "milestones", milestoneId, "slices", slice.id, "tasks", `${task.id}-SUMMARY.md`);
|
|
522
710
|
const stubContent = [
|
|
@@ -575,6 +763,22 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
575
763
|
}
|
|
576
764
|
}
|
|
577
765
|
}
|
|
766
|
+
// ── Future timestamp check ─────────────────────────────────────
|
|
767
|
+
if (task.done && hasSummary && summaryPath) {
|
|
768
|
+
try {
|
|
769
|
+
const rawSummary = await loadFile(summaryPath);
|
|
770
|
+
const m = rawSummary?.match(/^completed_at:\s*(.+)$/m);
|
|
771
|
+
if (m) {
|
|
772
|
+
const ts = new Date(m[1].trim());
|
|
773
|
+
if (!isNaN(ts.getTime()) && ts.getTime() > Date.now() + 24 * 60 * 60 * 1000) {
|
|
774
|
+
issues.push({ severity: "warning", code: "future_timestamp", scope: "task", unitId: taskUnitId,
|
|
775
|
+
message: `Task ${task.id} has completed_at "${m[1].trim()}" which is more than 24h in the future`,
|
|
776
|
+
file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"), fixable: false });
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
catch { /* non-fatal */ }
|
|
781
|
+
}
|
|
578
782
|
allTasksDone = allTasksDone && task.done;
|
|
579
783
|
}
|
|
580
784
|
// Blocker-without-replan detection
|
|
@@ -604,6 +808,12 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
604
808
|
}
|
|
605
809
|
}
|
|
606
810
|
}
|
|
811
|
+
// ── Stale REPLAN: exists but all tasks done ────────────────────────
|
|
812
|
+
if (replanPath && allTasksDone) {
|
|
813
|
+
issues.push({ severity: "info", code: "stale_replan_file", scope: "slice", unitId,
|
|
814
|
+
message: `${slice.id} has a REPLAN.md but all tasks are done — REPLAN.md may be stale`,
|
|
815
|
+
file: relSliceFile(basePath, milestoneId, slice.id, "REPLAN"), fixable: false });
|
|
816
|
+
}
|
|
607
817
|
const sliceSummaryPath = resolveSliceFile(basePath, milestoneId, slice.id, "SUMMARY");
|
|
608
818
|
const sliceUatPath = join(slicePath, `${slice.id}-UAT.md`);
|
|
609
819
|
const hasSliceSummary = !!(sliceSummaryPath && await loadFile(sliceSummaryPath));
|
|
@@ -618,6 +828,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
618
828
|
file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
|
|
619
829
|
fixable: true,
|
|
620
830
|
});
|
|
831
|
+
dryRunCanFix("all_tasks_done_missing_slice_summary", `create placeholder summary for ${unitId}`);
|
|
621
832
|
if (shouldFix("all_tasks_done_missing_slice_summary"))
|
|
622
833
|
await ensureSliceSummaryStub(basePath, milestoneId, slice.id, fixesApplied);
|
|
623
834
|
}
|
|
@@ -631,6 +842,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
631
842
|
file: `${relSlicePath(basePath, milestoneId, slice.id)}/${slice.id}-UAT.md`,
|
|
632
843
|
fixable: true,
|
|
633
844
|
});
|
|
845
|
+
dryRunCanFix("all_tasks_done_missing_slice_uat", `create placeholder UAT for ${unitId}`);
|
|
634
846
|
if (shouldFix("all_tasks_done_missing_slice_uat"))
|
|
635
847
|
await ensureSliceUatStub(basePath, milestoneId, slice.id, fixesApplied);
|
|
636
848
|
}
|
|
@@ -644,6 +856,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
644
856
|
file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
|
|
645
857
|
fixable: true,
|
|
646
858
|
});
|
|
859
|
+
dryRunCanFix("all_tasks_done_roadmap_not_checked", `mark ${slice.id} done in roadmap`);
|
|
647
860
|
if (shouldFix("all_tasks_done_roadmap_not_checked") && (hasSliceSummary || issues.some(issue => issue.code === "all_tasks_done_missing_slice_summary" && issue.unitId === unitId))) {
|
|
648
861
|
await markSliceDoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
649
862
|
}
|
|
@@ -658,6 +871,12 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
658
871
|
file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
|
|
659
872
|
fixable: true,
|
|
660
873
|
});
|
|
874
|
+
if (!allTasksDone) {
|
|
875
|
+
dryRunCanFix("slice_checked_missing_summary", `uncheck ${slice.id} in roadmap (tasks incomplete)`);
|
|
876
|
+
if (shouldFix("slice_checked_missing_summary")) {
|
|
877
|
+
await markSliceUndoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
661
880
|
}
|
|
662
881
|
if (slice.done && !hasSliceUat) {
|
|
663
882
|
issues.push({
|
|
@@ -696,13 +915,16 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
696
915
|
});
|
|
697
916
|
}
|
|
698
917
|
}
|
|
699
|
-
if (fix && fixesApplied.length > 0) {
|
|
918
|
+
if (fix && !dryRun && fixesApplied.length > 0) {
|
|
700
919
|
await updateStateFile(basePath, fixesApplied);
|
|
701
920
|
}
|
|
702
|
-
|
|
921
|
+
const report = {
|
|
703
922
|
ok: issues.every(issue => issue.severity !== "error"),
|
|
704
923
|
basePath,
|
|
705
924
|
issues,
|
|
706
925
|
fixesApplied,
|
|
926
|
+
timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: Math.max(0, Date.now() - t0env - envMs) },
|
|
707
927
|
};
|
|
928
|
+
await appendDoctorHistory(basePath, report);
|
|
929
|
+
return report;
|
|
708
930
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
/**
|
|
9
|
+
* Check which keys already exist in a .env file or process.env.
|
|
10
|
+
* Returns the subset of `keys` that are already set.
|
|
11
|
+
*/
|
|
12
|
+
export async function checkExistingEnvKeys(keys, envFilePath) {
|
|
13
|
+
let fileContent = "";
|
|
14
|
+
try {
|
|
15
|
+
fileContent = await readFile(envFilePath, "utf8");
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// ENOENT or other read error — proceed with empty content
|
|
19
|
+
}
|
|
20
|
+
const existing = [];
|
|
21
|
+
for (const key of keys) {
|
|
22
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
23
|
+
const regex = new RegExp(`^${escaped}\\s*=`, "m");
|
|
24
|
+
if (regex.test(fileContent) || key in process.env) {
|
|
25
|
+
existing.push(key);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return existing;
|
|
29
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { importExtensionModule } from "@gsd/pi-coding-agent";
|
|
1
2
|
export function registerExitCommand(pi, deps = {}) {
|
|
2
3
|
pi.registerCommand("exit", {
|
|
3
4
|
description: "Exit GSD gracefully",
|
|
4
5
|
handler: async (_args, ctx) => {
|
|
5
6
|
// Stop auto-mode first so locks and activity state are cleaned up before shutdown.
|
|
6
|
-
const stopAuto = deps.stopAuto ?? (await import
|
|
7
|
+
const stopAuto = deps.stopAuto ?? (await importExtensionModule(import.meta.url, "./auto.js")).stopAuto;
|
|
7
8
|
await stopAuto(ctx, pi, "Graceful exit");
|
|
8
9
|
ctx.shutdown();
|
|
9
10
|
},
|
|
@@ -236,9 +236,55 @@ function buildHealthSection(data) {
|
|
|
236
236
|
<td>${formatTokenCount(tb.tokens.total)}</td></tr>`).join('')}
|
|
237
237
|
</tbody>
|
|
238
238
|
</table>` : '';
|
|
239
|
+
// Progress score section
|
|
240
|
+
let progressHtml = '';
|
|
241
|
+
if (h.progressScore) {
|
|
242
|
+
const ps = h.progressScore;
|
|
243
|
+
const scoreColor = ps.level === 'green' ? '#22c55e' : ps.level === 'yellow' ? '#eab308' : '#ef4444';
|
|
244
|
+
const signalRows = ps.signals.map(s => {
|
|
245
|
+
const icon = s.kind === 'positive' ? '✓' : s.kind === 'negative' ? '✗' : '·';
|
|
246
|
+
const color = s.kind === 'positive' ? '#22c55e' : s.kind === 'negative' ? '#ef4444' : '#888';
|
|
247
|
+
return `<div style="margin-left:1em;color:${color}">${icon} ${esc(s.label)}</div>`;
|
|
248
|
+
}).join('');
|
|
249
|
+
progressHtml = `
|
|
250
|
+
<h3>Progress Score</h3>
|
|
251
|
+
<div style="font-size:1.1em;font-weight:bold;color:${scoreColor}">● ${esc(ps.summary)}</div>
|
|
252
|
+
${signalRows}`;
|
|
253
|
+
}
|
|
254
|
+
// Doctor history section
|
|
255
|
+
let historyHtml = '';
|
|
256
|
+
const doctorHistory = h.doctorHistory ?? [];
|
|
257
|
+
if (doctorHistory.length > 0) {
|
|
258
|
+
const historyRows = doctorHistory.slice(0, 20).map(entry => {
|
|
259
|
+
const statusIcon = entry.ok ? '✓' : '✗';
|
|
260
|
+
const statusColor = entry.ok ? '#22c55e' : '#ef4444';
|
|
261
|
+
const ts = entry.ts.replace('T', ' ').slice(0, 19);
|
|
262
|
+
const scopeTag = entry.scope ? `<span class="mono" style="color:#888"> [${esc(entry.scope)}]</span>` : '';
|
|
263
|
+
const summaryText = entry.summary ? esc(entry.summary) : `${entry.errors} errors, ${entry.warnings} warnings, ${entry.fixes} fixes`;
|
|
264
|
+
const issueDetails = (entry.issues ?? []).slice(0, 3).map(i => {
|
|
265
|
+
const iColor = i.severity === 'error' ? '#ef4444' : '#eab308';
|
|
266
|
+
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>`;
|
|
267
|
+
}).join('');
|
|
268
|
+
const fixDetails = (entry.fixDescriptions ?? []).slice(0, 2).map(f => `<div style="margin-left:2em;color:#22c55e;font-size:0.85em">↳ ${esc(f)}</div>`).join('');
|
|
269
|
+
return `<tr style="color:${statusColor}">
|
|
270
|
+
<td class="mono">${statusIcon}</td>
|
|
271
|
+
<td class="mono">${esc(ts)}${scopeTag}</td>
|
|
272
|
+
<td>${summaryText}</td>
|
|
273
|
+
</tr>
|
|
274
|
+
${issueDetails || fixDetails ? `<tr><td colspan="3">${issueDetails}${fixDetails}</td></tr>` : ''}`;
|
|
275
|
+
}).join('');
|
|
276
|
+
historyHtml = `
|
|
277
|
+
<h3>Doctor Run History</h3>
|
|
278
|
+
<table class="tbl">
|
|
279
|
+
<thead><tr><th></th><th>Time</th><th>Summary</th></tr></thead>
|
|
280
|
+
<tbody>${historyRows}</tbody>
|
|
281
|
+
</table>`;
|
|
282
|
+
}
|
|
239
283
|
return section('health', 'Health', `
|
|
240
284
|
<table class="tbl tbl-kv"><tbody>${rows.join('')}</tbody></table>
|
|
241
285
|
${tierRows}
|
|
286
|
+
${progressHtml}
|
|
287
|
+
${historyHtml}
|
|
242
288
|
`);
|
|
243
289
|
}
|
|
244
290
|
// ─── Section: Progress ────────────────────────────────────────────────────────
|
|
@@ -5,7 +5,7 @@ import { join, basename } from "node:path";
|
|
|
5
5
|
import { exec } from "node:child_process";
|
|
6
6
|
import { getLedger, getProjectTotals, aggregateByPhase, aggregateBySlice, aggregateByModel, formatCost, formatTokenCount, loadLedgerFromDisk, } from "./metrics.js";
|
|
7
7
|
import { gsdRoot } from "./paths.js";
|
|
8
|
-
import { formatDuration, fileLink } from "../shared/
|
|
8
|
+
import { formatDuration, fileLink } from "../shared/format-utils.js";
|
|
9
9
|
import { getErrorMessage } from "./error-utils.js";
|
|
10
10
|
/**
|
|
11
11
|
* Open a file in the user's default browser.
|
|
@@ -6,8 +6,8 @@ import { promises as fs } from 'node:fs';
|
|
|
6
6
|
import { resolve } from 'node:path';
|
|
7
7
|
import { atomicWriteAsync } from './atomic-write.js';
|
|
8
8
|
import { resolveMilestoneFile, relMilestoneFile, resolveGsdRootFile } from './paths.js';
|
|
9
|
-
import { findMilestoneIds } from './
|
|
10
|
-
import { checkExistingEnvKeys } from '
|
|
9
|
+
import { findMilestoneIds } from './milestone-ids.js';
|
|
10
|
+
import { checkExistingEnvKeys } from './env-utils.js';
|
|
11
11
|
import { parseRoadmapSlices } from './roadmap-slices.js';
|
|
12
12
|
import { nativeParseRoadmap, nativeExtractSection, nativeParsePlanFile, nativeParseSummaryFile, NATIVE_UNAVAILABLE } from './native-parser-bridge.js';
|
|
13
13
|
import { debugTime, debugCount } from './debug-logger.js';
|
|
@@ -222,13 +222,48 @@ export function formatSecretsManifest(manifest) {
|
|
|
222
222
|
return lines.join('\n') + '\n';
|
|
223
223
|
}
|
|
224
224
|
// ─── Slice Plan Parser ─────────────────────────────────────────────────────
|
|
225
|
+
function normalizeTaskPlanFrontmatter(frontmatter) {
|
|
226
|
+
const estimatedStepsRaw = frontmatter.estimated_steps;
|
|
227
|
+
const estimatedFilesRaw = frontmatter.estimated_files;
|
|
228
|
+
const skillsUsedRaw = frontmatter.skills_used;
|
|
229
|
+
const parseOptionalNumber = (value) => {
|
|
230
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
231
|
+
return value;
|
|
232
|
+
if (typeof value === 'string' && value.trim()) {
|
|
233
|
+
const parsed = parseInt(value, 10);
|
|
234
|
+
if (Number.isFinite(parsed))
|
|
235
|
+
return parsed;
|
|
236
|
+
}
|
|
237
|
+
return undefined;
|
|
238
|
+
};
|
|
239
|
+
const estimated_steps = parseOptionalNumber(estimatedStepsRaw);
|
|
240
|
+
const estimated_files = parseOptionalNumber(estimatedFilesRaw);
|
|
241
|
+
const skills_used = Array.isArray(skillsUsedRaw)
|
|
242
|
+
? skillsUsedRaw.map(v => String(v).trim()).filter(Boolean)
|
|
243
|
+
: typeof skillsUsedRaw === 'string' && skillsUsedRaw.trim()
|
|
244
|
+
? [skillsUsedRaw.trim()]
|
|
245
|
+
: [];
|
|
246
|
+
return {
|
|
247
|
+
...(estimated_steps !== undefined ? { estimated_steps } : {}),
|
|
248
|
+
...(estimated_files !== undefined ? { estimated_files } : {}),
|
|
249
|
+
skills_used,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
export function parseTaskPlanFile(content) {
|
|
253
|
+
const [fmLines] = splitFrontmatter(content);
|
|
254
|
+
const fm = fmLines ? parseFrontmatterMap(fmLines) : {};
|
|
255
|
+
return {
|
|
256
|
+
frontmatter: normalizeTaskPlanFrontmatter(fm),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
225
259
|
export function parsePlan(content) {
|
|
226
260
|
return cachedParse(content, 'plan', _parsePlanImpl);
|
|
227
261
|
}
|
|
228
262
|
function _parsePlanImpl(content) {
|
|
229
263
|
const stopTimer = debugTime("parse-plan");
|
|
264
|
+
const [, body] = splitFrontmatter(content);
|
|
230
265
|
// Try native parser first for better performance
|
|
231
|
-
const nativeResult = nativeParsePlanFile(
|
|
266
|
+
const nativeResult = nativeParsePlanFile(body);
|
|
232
267
|
if (nativeResult) {
|
|
233
268
|
stopTimer({ native: true });
|
|
234
269
|
return {
|
|
@@ -249,7 +284,7 @@ function _parsePlanImpl(content) {
|
|
|
249
284
|
filesLikelyTouched: nativeResult.filesLikelyTouched,
|
|
250
285
|
};
|
|
251
286
|
}
|
|
252
|
-
const lines =
|
|
287
|
+
const lines = body.split('\n');
|
|
253
288
|
const h1 = lines.find(l => l.startsWith('# '));
|
|
254
289
|
let id = '';
|
|
255
290
|
let title = '';
|
|
@@ -263,11 +298,11 @@ function _parsePlanImpl(content) {
|
|
|
263
298
|
title = h1.slice(2).trim();
|
|
264
299
|
}
|
|
265
300
|
}
|
|
266
|
-
const goal = extractBoldField(
|
|
267
|
-
const demo = extractBoldField(
|
|
268
|
-
const mhSection = extractSection(
|
|
301
|
+
const goal = extractBoldField(body, 'Goal') || '';
|
|
302
|
+
const demo = extractBoldField(body, 'Demo') || '';
|
|
303
|
+
const mhSection = extractSection(body, 'Must-Haves');
|
|
269
304
|
const mustHaves = mhSection ? parseBullets(mhSection) : [];
|
|
270
|
-
const tasksSection = extractSection(
|
|
305
|
+
const tasksSection = extractSection(body, 'Tasks');
|
|
271
306
|
const tasks = [];
|
|
272
307
|
if (tasksSection) {
|
|
273
308
|
const taskLines = tasksSection.split('\n');
|
|
@@ -315,7 +350,7 @@ function _parsePlanImpl(content) {
|
|
|
315
350
|
if (currentTask)
|
|
316
351
|
tasks.push(currentTask);
|
|
317
352
|
}
|
|
318
|
-
const filesSection = extractSection(
|
|
353
|
+
const filesSection = extractSection(body, 'Files Likely Touched');
|
|
319
354
|
const filesLikelyTouched = filesSection ? parseBullets(filesSection) : [];
|
|
320
355
|
const result = { id, title, goal, demo, mustHaves, tasks, filesLikelyTouched };
|
|
321
356
|
stopTimer({ tasks: tasks.length });
|
|
@@ -692,6 +727,10 @@ export function extractUatType(content) {
|
|
|
692
727
|
const rawValue = modeBullet.slice('UAT mode:'.length).trim().toLowerCase();
|
|
693
728
|
if (rawValue.startsWith('artifact-driven'))
|
|
694
729
|
return 'artifact-driven';
|
|
730
|
+
if (rawValue.startsWith('browser-executable'))
|
|
731
|
+
return 'browser-executable';
|
|
732
|
+
if (rawValue.startsWith('runtime-executable'))
|
|
733
|
+
return 'runtime-executable';
|
|
695
734
|
if (rawValue.startsWith('live-runtime'))
|
|
696
735
|
return 'live-runtime';
|
|
697
736
|
if (rawValue.startsWith('human-experience'))
|
|
@@ -21,7 +21,7 @@ import { deriveState } from "./state.js";
|
|
|
21
21
|
import { isAutoActive } from "./auto.js";
|
|
22
22
|
import { loadPrompt } from "./prompt-loader.js";
|
|
23
23
|
import { gsdRoot } from "./paths.js";
|
|
24
|
-
import { formatDuration } from "../shared/
|
|
24
|
+
import { formatDuration } from "../shared/format-utils.js";
|
|
25
25
|
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
26
26
|
// ─── Entry Point ──────────────────────────────────────────────────────────────
|
|
27
27
|
export async function handleForensics(args, ctx, pi) {
|