gsd-pi 2.58.0 → 2.59.0-dev.023bd39
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 +1 -1
- package/dist/cli.js +60 -35
- package/dist/headless-ui.d.ts +17 -0
- package/dist/headless-ui.js +97 -3
- package/dist/headless.js +67 -6
- package/dist/help-text.js +1 -0
- package/dist/onboarding.js +44 -0
- package/dist/resource-loader.js +16 -1
- package/dist/resources/agents/researcher.md +1 -1
- package/dist/resources/extensions/ask-user-questions.js +16 -3
- package/dist/resources/extensions/async-jobs/extension-manifest.json +1 -1
- package/dist/resources/extensions/bg-shell/extension-manifest.json +1 -1
- package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +14 -6
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +59 -36
- package/dist/resources/extensions/context7/extension-manifest.json +1 -1
- package/dist/resources/extensions/get-secrets-from-user.js +8 -5
- package/dist/resources/extensions/google-search/extension-manifest.json +1 -1
- package/dist/resources/extensions/google-search/index.js +2 -1
- package/dist/resources/extensions/gsd/auto/phases.js +25 -21
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
- package/dist/resources/extensions/gsd/auto-dashboard.js +37 -20
- package/dist/resources/extensions/gsd/auto-dispatch.js +17 -2
- package/dist/resources/extensions/gsd/auto-model-selection.js +26 -3
- package/dist/resources/extensions/gsd/auto-post-unit.js +16 -4
- package/dist/resources/extensions/gsd/auto-prompts.js +1 -1
- package/dist/resources/extensions/gsd/auto-recovery.js +13 -5
- package/dist/resources/extensions/gsd/auto-start.js +35 -22
- package/dist/resources/extensions/gsd/auto-worktree.js +199 -12
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +32 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +80 -8
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +32 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +42 -34
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +66 -12
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -0
- package/dist/resources/extensions/gsd/captures.js +56 -4
- package/dist/resources/extensions/gsd/codebase-generator.js +279 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +10 -1
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
- package/dist/resources/extensions/gsd/commands-codebase.js +115 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +41 -4
- package/dist/resources/extensions/gsd/complexity-classifier.js +8 -6
- package/dist/resources/extensions/gsd/db-writer.js +116 -8
- package/dist/resources/extensions/gsd/doctor-git-checks.js +76 -1
- package/dist/resources/extensions/gsd/doctor-proactive.js +34 -1
- package/dist/resources/extensions/gsd/doctor-providers.js +2 -1
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +5 -4
- package/dist/resources/extensions/gsd/doctor.js +3 -1
- package/dist/resources/extensions/gsd/error-classifier.js +12 -10
- package/dist/resources/extensions/gsd/extension-manifest.json +16 -1
- package/dist/resources/extensions/gsd/forensics.js +123 -20
- package/dist/resources/extensions/gsd/git-service.js +105 -2
- package/dist/resources/extensions/gsd/gitignore.js +33 -0
- package/dist/resources/extensions/gsd/gsd-db.js +36 -9
- package/dist/resources/extensions/gsd/guided-flow.js +106 -44
- package/dist/resources/extensions/gsd/health-widget-core.js +31 -0
- package/dist/resources/extensions/gsd/health-widget.js +17 -0
- package/dist/resources/extensions/gsd/index.js +1 -1
- package/dist/resources/extensions/gsd/memory-extractor.js +7 -0
- package/dist/resources/extensions/gsd/migrate-external.js +8 -1
- package/dist/resources/extensions/gsd/milestone-validation-gates.js +45 -0
- package/dist/resources/extensions/gsd/model-cost-table.js +18 -0
- package/dist/resources/extensions/gsd/model-router.js +35 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +39 -0
- package/dist/resources/extensions/gsd/notifications.js +16 -1
- package/dist/resources/extensions/gsd/parallel-eligibility.js +13 -2
- package/dist/resources/extensions/gsd/parallel-merge.js +78 -5
- package/dist/resources/extensions/gsd/parsers-legacy.js +20 -3
- package/dist/resources/extensions/gsd/paths.js +45 -0
- package/dist/resources/extensions/gsd/preferences-models.js +14 -1
- package/dist/resources/extensions/gsd/preferences-types.js +3 -1
- package/dist/resources/extensions/gsd/preferences.js +13 -16
- package/dist/resources/extensions/gsd/prompt-loader.js +4 -1
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -2
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss.md +1 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -1
- package/dist/resources/extensions/gsd/prompts/forensics.md +2 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -0
- package/dist/resources/extensions/gsd/prompts/rethink.md +1 -1
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -0
- package/dist/resources/extensions/gsd/repo-identity.js +205 -11
- package/dist/resources/extensions/gsd/rethink.js +5 -0
- package/dist/resources/extensions/gsd/roadmap-slices.js +5 -4
- package/dist/resources/extensions/gsd/state.js +85 -27
- package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +20 -1
- package/dist/resources/extensions/gsd/tools/complete-task.js +34 -71
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +12 -2
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +29 -1
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +14 -3
- package/dist/resources/extensions/gsd/triage-resolution.js +22 -7
- package/dist/resources/extensions/gsd/undo.js +2 -2
- package/dist/resources/extensions/gsd/unit-ownership.js +164 -33
- package/dist/resources/extensions/gsd/verdict-parser.js +20 -8
- package/dist/resources/extensions/gsd/watch/header-renderer.js +241 -0
- package/dist/resources/extensions/gsd/workflow-manifest.js +24 -5
- package/dist/resources/extensions/gsd/workflow-projections.js +95 -63
- package/dist/resources/extensions/gsd/workflow-reconcile.js +35 -5
- package/dist/resources/extensions/gsd/workspace-index.js +24 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +105 -1
- package/dist/resources/extensions/gsd/worktree-resolver.js +20 -3
- package/dist/resources/extensions/mcp-client/index.js +11 -7
- package/dist/resources/extensions/ollama/index.js +112 -0
- package/dist/resources/extensions/ollama/model-capabilities.js +115 -0
- package/dist/resources/extensions/ollama/ollama-client.js +168 -0
- package/dist/resources/extensions/ollama/ollama-commands.js +194 -0
- package/dist/resources/extensions/ollama/ollama-discovery.js +69 -0
- package/dist/resources/extensions/ollama/ollama-tool.js +184 -0
- package/dist/resources/extensions/ollama/types.js +2 -0
- package/dist/resources/extensions/search-the-web/extension-manifest.json +1 -1
- package/dist/resources/extensions/search-the-web/url-utils.js +17 -0
- package/dist/resources/extensions/shared/interview-ui.js +11 -1
- package/dist/resources/skills/btw/SKILL.md +42 -0
- package/dist/resources/skills/create-gsd-extension/SKILL.md +5 -3
- package/dist/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +5 -4
- package/dist/resources/skills/create-gsd-extension/workflows/add-capability.md +2 -2
- package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +4 -4
- package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +5 -3
- package/dist/security-overrides.d.ts +11 -0
- package/dist/security-overrides.js +41 -0
- package/dist/startup-model-validation.d.ts +39 -0
- package/dist/startup-model-validation.js +50 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
- package/dist/web/standalone/.next/build-manifest.json +4 -4
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/required-server-files.json +4 -4
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +4 -4
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +5 -5
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +5 -5
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
- package/dist/web/standalone/.next/server/chunks/2229.js +2 -2
- package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/6502.7593d7797a4b3999.js +9 -0
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-61d3afac6d0f0ce7.js → webpack-a1c1e452c6b32d04.js} +1 -1
- package/dist/web/standalone/.next/static/css/f6e8833d46e738d8.css +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/dist/web-mode.js +2 -1
- package/dist/welcome-screen.d.ts +1 -0
- package/dist/welcome-screen.js +32 -6
- package/package.json +2 -2
- package/packages/daemon/src/daemon.ts +1 -1
- package/packages/daemon/src/discord-bot.ts +11 -0
- package/packages/daemon/src/event-bridge.ts +15 -9
- package/packages/daemon/src/event-formatter.ts +30 -2
- package/packages/daemon/src/message-batcher.test.ts +2 -2
- package/packages/daemon/src/message-batcher.ts +9 -3
- package/packages/daemon/src/orchestrator.test.ts +1 -0
- package/packages/daemon/src/orchestrator.ts +106 -2
- package/packages/native/dist/ast/index.js +9 -5
- package/packages/native/dist/ast/types.js +2 -1
- package/packages/native/dist/clipboard/index.js +12 -7
- package/packages/native/dist/clipboard/types.js +2 -1
- package/packages/native/dist/diff/index.js +12 -7
- package/packages/native/dist/diff/types.js +2 -1
- package/packages/native/dist/fd/index.js +6 -3
- package/packages/native/dist/fd/types.js +2 -1
- package/packages/native/dist/glob/index.js +9 -5
- package/packages/native/dist/glob/types.js +2 -1
- package/packages/native/dist/grep/index.js +9 -5
- package/packages/native/dist/grep/types.js +2 -1
- package/packages/native/dist/gsd-parser/index.js +18 -11
- package/packages/native/dist/gsd-parser/types.js +2 -1
- package/packages/native/dist/highlight/index.js +12 -7
- package/packages/native/dist/highlight/types.js +2 -1
- package/packages/native/dist/html/index.js +6 -3
- package/packages/native/dist/html/types.js +2 -1
- package/packages/native/dist/image/index.js +10 -5
- package/packages/native/dist/image/types.js +7 -4
- package/packages/native/dist/index.js +70 -17
- package/packages/native/dist/json-parse/index.js +13 -8
- package/packages/native/dist/native.js +47 -10
- package/packages/native/dist/ps/index.js +15 -9
- package/packages/native/dist/ps/types.js +2 -1
- package/packages/native/dist/stream-process/index.js +12 -7
- package/packages/native/dist/text/index.js +24 -14
- package/packages/native/dist/text/types.js +5 -2
- package/packages/native/dist/truncate/index.js +12 -7
- package/packages/native/dist/ttsr/index.js +12 -7
- package/packages/native/dist/ttsr/types.js +2 -1
- package/packages/native/dist/xxhash/index.js +9 -5
- package/packages/native/package.json +19 -19
- package/packages/native/src/__tests__/module-compat.test.mjs +91 -0
- package/packages/native/src/native.ts +9 -8
- package/packages/pi-agent-core/dist/agent-loop.js +3 -2
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/proxy.d.ts +1 -1
- package/packages/pi-agent-core/dist/proxy.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/proxy.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +45 -0
- package/packages/pi-agent-core/src/agent-loop.ts +3 -2
- package/packages/pi-agent-core/src/proxy.ts +1 -1
- package/packages/pi-ai/dist/env-api-keys.js +1 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/index.d.ts +1 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +19 -2
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.test.js +25 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.test.js.map +1 -0
- package/packages/pi-ai/dist/types.d.ts +3 -3
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/dist/utils/json-parse.d.ts +3 -0
- package/packages/pi-ai/dist/utils/json-parse.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/json-parse.js +24 -1
- package/packages/pi-ai/dist/utils/json-parse.js.map +1 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.d.ts +37 -0
- package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/repair-tool-json.js +75 -0
- package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -0
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.d.ts +2 -0
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +73 -0
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -0
- package/packages/pi-ai/src/env-api-keys.ts +1 -0
- package/packages/pi-ai/src/index.ts +1 -0
- package/packages/pi-ai/src/providers/anthropic-shared.test.ts +29 -0
- package/packages/pi-ai/src/providers/anthropic-shared.ts +17 -2
- package/packages/pi-ai/src/types.ts +3 -2
- package/packages/pi-ai/src/utils/json-parse.ts +28 -1
- package/packages/pi-ai/src/utils/repair-tool-json.ts +88 -0
- package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +102 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +4 -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 +31 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +17 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +62 -2
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.test.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.test.js +176 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/exec.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/exec.js +3 -1
- package/packages/pi-coding-agent/dist/core/exec.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.d.ts +28 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.js +37 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.test.js +63 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-manifest.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-sort.d.ts +19 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-sort.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-sort.js +115 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-sort.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-sort.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-sort.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-sort.test.js +109 -0
- package/packages/pi-coding-agent/dist/core/extensions/extension-sort.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.js +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/image-overflow-recovery.d.ts +44 -0
- package/packages/pi-coding-agent/dist/core/image-overflow-recovery.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/image-overflow-recovery.js +97 -0
- package/packages/pi-coding-agent/dist/core/image-overflow-recovery.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/image-overflow-recovery.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/image-overflow-recovery.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/image-overflow-recovery.test.js +181 -0
- package/packages/pi-coding-agent/dist/core/image-overflow-recovery.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/index.js +1 -1
- package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +3 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +3 -0
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/messages.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/messages.js +31 -2
- package/packages/pi-coding-agent/dist/core/messages.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/messages.test.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/messages.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/messages.test.js +86 -0
- package/packages/pi-coding-agent/dist/core/messages.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/resolve-config-value.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.js +23 -2
- package/packages/pi-coding-agent/dist/core/resolve-config-value.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +89 -2
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +10 -0
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +12 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +48 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js +193 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager-security.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/settings-manager-security.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager-security.test.js +83 -0
- package/packages/pi-coding-agent/dist/core/settings-manager-security.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +14 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +36 -3
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/hashline-read.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/hashline-read.js +10 -3
- package/packages/pi-coding-agent/dist/core/tools/hashline-read.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/read.js +13 -4
- package/packages/pi-coding-agent/dist/core/tools/read.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/spawn-shell-windows.test.d.ts +16 -0
- package/packages/pi-coding-agent/dist/core/tools/spawn-shell-windows.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/spawn-shell-windows.test.js +80 -0
- package/packages/pi-coding-agent/dist/core/tools/spawn-shell-windows.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts +3 -2
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +2 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/armin.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/armin.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/armin.js +9 -8
- package/packages/pi-coding-agent/dist/modes/interactive/components/armin.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +0 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bordered-loader.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/config-selector.js +5 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/countdown-timer.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/countdown-timer.js +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/countdown-timer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/custom-message.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/daxnuts.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/daxnuts.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/daxnuts.js +4 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/daxnuts.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js +8 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.js +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +26 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/oauth-selector.js +4 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +46 -14
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js +2 -8
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js +4 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +8 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message-selector.js +3 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +19 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +22 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +122 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
- 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 +57 -4
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/remote-terminal.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/rpc/remote-terminal.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/remote-terminal.js +5 -0
- package/packages/pi-coding-agent/dist/modes/rpc/remote-terminal.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +38 -1
- package/packages/pi-coding-agent/src/core/compaction/compaction.test.ts +236 -0
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +94 -1
- package/packages/pi-coding-agent/src/core/exec.ts +3 -1
- package/packages/pi-coding-agent/src/core/extensions/extension-manifest.test.ts +77 -0
- package/packages/pi-coding-agent/src/core/extensions/extension-manifest.ts +62 -0
- package/packages/pi-coding-agent/src/core/extensions/extension-sort.test.ts +134 -0
- package/packages/pi-coding-agent/src/core/extensions/extension-sort.ts +137 -0
- package/packages/pi-coding-agent/src/core/extensions/index.ts +4 -0
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +5 -0
- package/packages/pi-coding-agent/src/core/image-overflow-recovery.test.ts +228 -0
- package/packages/pi-coding-agent/src/core/image-overflow-recovery.ts +118 -0
- package/packages/pi-coding-agent/src/core/index.ts +6 -0
- package/packages/pi-coding-agent/src/core/lsp/index.ts +3 -0
- package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +3 -0
- package/packages/pi-coding-agent/src/core/messages.test.ts +114 -0
- package/packages/pi-coding-agent/src/core/messages.ts +29 -2
- package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
- package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +111 -1
- package/packages/pi-coding-agent/src/core/resolve-config-value.ts +26 -2
- package/packages/pi-coding-agent/src/core/resource-loader.ts +20 -1
- package/packages/pi-coding-agent/src/core/retry-handler.test.ts +255 -0
- package/packages/pi-coding-agent/src/core/retry-handler.ts +52 -1
- package/packages/pi-coding-agent/src/core/settings-manager-security.test.ts +102 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +44 -3
- package/packages/pi-coding-agent/src/core/tools/hashline-read.ts +11 -3
- package/packages/pi-coding-agent/src/core/tools/read.ts +14 -4
- package/packages/pi-coding-agent/src/core/tools/spawn-shell-windows.test.ts +92 -0
- package/packages/pi-coding-agent/src/index.ts +11 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/armin.ts +9 -9
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +0 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +3 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/bordered-loader.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/branch-summary-message.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/compaction-summary-message.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/config-selector.ts +7 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/countdown-timer.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/custom-message.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/daxnuts.ts +4 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +2 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.ts +3 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/extension-input.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/extension-selector.ts +4 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +27 -13
- package/packages/pi-coding-agent/src/modes/interactive/components/oauth-selector.ts +4 -4
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +45 -14
- package/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts +2 -7
- package/packages/pi-coding-agent/src/modes/interactive/components/session-selector.ts +4 -4
- package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +2 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +8 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/user-message-selector.ts +3 -2
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +24 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +156 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +21 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +73 -3
- package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +1 -1
- package/packages/pi-coding-agent/src/modes/rpc/remote-terminal.ts +6 -0
- package/packages/pi-tui/dist/terminal.d.ts +2 -0
- package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal.js +9 -0
- package/packages/pi-tui/dist/terminal.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +9 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/terminal.ts +14 -0
- package/packages/pi-tui/src/tui.ts +8 -0
- package/pkg/dist/modes/interactive/theme/themes.js +1 -1
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/scripts/ensure-workspace-builds.cjs +45 -14
- package/src/resources/agents/researcher.md +1 -1
- package/src/resources/extensions/ask-user-questions.ts +21 -3
- package/src/resources/extensions/async-jobs/extension-manifest.json +1 -1
- package/src/resources/extensions/bg-shell/extension-manifest.json +1 -1
- package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
- package/src/resources/extensions/claude-code-cli/partial-builder.ts +13 -6
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +63 -35
- package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +28 -0
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +108 -1
- package/src/resources/extensions/context7/extension-manifest.json +1 -1
- package/src/resources/extensions/get-secrets-from-user.ts +8 -5
- package/src/resources/extensions/google-search/extension-manifest.json +1 -1
- package/src/resources/extensions/google-search/index.ts +2 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/phases.ts +43 -34
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -2
- package/src/resources/extensions/gsd/auto-dashboard.ts +37 -19
- package/src/resources/extensions/gsd/auto-dispatch.ts +18 -2
- package/src/resources/extensions/gsd/auto-model-selection.ts +26 -5
- package/src/resources/extensions/gsd/auto-post-unit.ts +18 -4
- package/src/resources/extensions/gsd/auto-prompts.ts +1 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +12 -5
- package/src/resources/extensions/gsd/auto-start.ts +35 -26
- package/src/resources/extensions/gsd/auto-worktree.ts +193 -9
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +31 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +85 -8
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +38 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +41 -35
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +72 -12
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +75 -0
- package/src/resources/extensions/gsd/captures.ts +63 -3
- package/src/resources/extensions/gsd/codebase-generator.ts +351 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +10 -1
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
- package/src/resources/extensions/gsd/commands-codebase.ts +164 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +46 -4
- package/src/resources/extensions/gsd/complexity-classifier.ts +8 -6
- package/src/resources/extensions/gsd/db-writer.ts +140 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +75 -1
- package/src/resources/extensions/gsd/doctor-proactive.ts +35 -1
- package/src/resources/extensions/gsd/doctor-providers.ts +2 -1
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +5 -4
- package/src/resources/extensions/gsd/doctor-types.ts +2 -0
- package/src/resources/extensions/gsd/doctor.ts +3 -1
- package/src/resources/extensions/gsd/error-classifier.ts +13 -11
- package/src/resources/extensions/gsd/extension-manifest.json +16 -1
- package/src/resources/extensions/gsd/forensics.ts +144 -20
- package/src/resources/extensions/gsd/git-service.ts +119 -3
- package/src/resources/extensions/gsd/gitignore.ts +33 -0
- package/src/resources/extensions/gsd/gsd-db.ts +43 -7
- package/src/resources/extensions/gsd/guided-flow.ts +114 -45
- package/src/resources/extensions/gsd/health-widget-core.ts +34 -0
- package/src/resources/extensions/gsd/health-widget.ts +17 -0
- package/src/resources/extensions/gsd/index.ts +1 -0
- package/src/resources/extensions/gsd/memory-extractor.ts +8 -0
- package/src/resources/extensions/gsd/migrate-external.ts +9 -1
- package/src/resources/extensions/gsd/milestone-validation-gates.ts +56 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +19 -0
- package/src/resources/extensions/gsd/model-router.ts +35 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +41 -0
- package/src/resources/extensions/gsd/notifications.ts +16 -0
- package/src/resources/extensions/gsd/parallel-eligibility.ts +15 -2
- package/src/resources/extensions/gsd/parallel-merge.ts +87 -4
- package/src/resources/extensions/gsd/parsers-legacy.ts +22 -3
- package/src/resources/extensions/gsd/paths.ts +44 -0
- package/src/resources/extensions/gsd/preferences-models.ts +14 -1
- package/src/resources/extensions/gsd/preferences-types.ts +10 -1
- package/src/resources/extensions/gsd/preferences.ts +13 -15
- package/src/resources/extensions/gsd/prompt-loader.ts +4 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -2
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss.md +1 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -1
- package/src/resources/extensions/gsd/prompts/forensics.md +2 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -0
- package/src/resources/extensions/gsd/prompts/rethink.md +1 -1
- package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -0
- package/src/resources/extensions/gsd/repo-identity.ts +186 -11
- package/src/resources/extensions/gsd/rethink.ts +6 -0
- package/src/resources/extensions/gsd/roadmap-slices.ts +5 -4
- package/src/resources/extensions/gsd/state.ts +84 -32
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/auto-mode-interactive-guard.test.ts +71 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +71 -1
- package/src/resources/extensions/gsd/tests/captures.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/cli-provider-rate-limit.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +488 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/completion-hierarchy-guards.test.ts +192 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +131 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +7 -12
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +78 -5
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/discord-invite-links.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/discuss-empty-db-fallback.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/discuss-queued-milestones.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/dist-redirect.mjs +20 -1
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/dynamic-routing-default.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +74 -0
- package/src/resources/extensions/gsd/tests/event-replay-idempotency.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +129 -0
- package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +96 -0
- package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +125 -12
- package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +164 -0
- package/src/resources/extensions/gsd/tests/guided-flow-dynamic-routing.test.ts +135 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +97 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +67 -0
- package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +111 -1
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-false-positives.test.ts +243 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +72 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/integration/gitignore-staging-2570.test.ts +150 -0
- package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +959 -0
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +85 -2
- package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/milestone-status-authoritative.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/model-router.test.ts +68 -3
- package/src/resources/extensions/gsd/tests/model-unittype-mapping.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/notifications.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/parallel-commit-scope.test.ts +159 -0
- package/src/resources/extensions/gsd/tests/parallel-eligibility-ghost.test.ts +150 -0
- package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/project-relocation-recovery.test.ts +297 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/prompt-loader-replacement.test.ts +178 -0
- package/src/resources/extensions/gsd/tests/prompt-tool-names.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/queue-execution-guard.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/quick-turn-end-cleanup.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/reconciliation-edge-cases.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +97 -0
- package/src/resources/extensions/gsd/tests/secure-env-collect.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/slice-disk-reconcile.test.ts +233 -0
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +305 -0
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +405 -0
- package/src/resources/extensions/gsd/tests/state-derivation-parity.test.ts +257 -0
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +1628 -0
- package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +174 -0
- package/src/resources/extensions/gsd/tests/summary-render-parity.test.ts +221 -0
- package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/uat-stuck-loop-orphaned-worktree.test.ts +289 -0
- package/src/resources/extensions/gsd/tests/unit-ownership.test.ts +100 -17
- package/src/resources/extensions/gsd/tests/vacuum-recovery.test.ts +154 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/verdict-parser.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +82 -0
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/worktree-db-respawn-truncation.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +101 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +48 -1
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +29 -5
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +95 -0
- package/src/resources/extensions/gsd/tools/complete-task.ts +36 -74
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +13 -1
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +36 -0
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +20 -2
- package/src/resources/extensions/gsd/triage-resolution.ts +23 -6
- package/src/resources/extensions/gsd/types.ts +4 -2
- package/src/resources/extensions/gsd/undo.ts +2 -2
- package/src/resources/extensions/gsd/unit-ownership.ts +206 -35
- package/src/resources/extensions/gsd/verdict-parser.ts +21 -6
- package/src/resources/extensions/gsd/watch/header-renderer.ts +275 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
- package/src/resources/extensions/gsd/workflow-manifest.ts +22 -5
- package/src/resources/extensions/gsd/workflow-projections.ts +97 -64
- package/src/resources/extensions/gsd/workflow-reconcile.ts +39 -10
- package/src/resources/extensions/gsd/workspace-index.ts +30 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +120 -1
- package/src/resources/extensions/gsd/worktree-resolver.ts +22 -3
- package/src/resources/extensions/mcp-client/index.ts +13 -7
- package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +55 -0
- package/src/resources/extensions/ollama/index.ts +130 -0
- package/src/resources/extensions/ollama/model-capabilities.ts +145 -0
- package/src/resources/extensions/ollama/ollama-client.ts +196 -0
- package/src/resources/extensions/ollama/ollama-commands.ts +248 -0
- package/src/resources/extensions/ollama/ollama-discovery.ts +106 -0
- package/src/resources/extensions/ollama/ollama-tool.ts +218 -0
- package/src/resources/extensions/ollama/tests/model-capabilities.test.ts +162 -0
- package/src/resources/extensions/ollama/tests/ollama-client.test.ts +38 -0
- package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +28 -0
- package/src/resources/extensions/ollama/types.ts +130 -0
- package/src/resources/extensions/search-the-web/extension-manifest.json +1 -1
- package/src/resources/extensions/search-the-web/url-utils.ts +19 -0
- package/src/resources/extensions/shared/interview-ui.ts +12 -1
- package/src/resources/extensions/shared/tests/ask-user-freetext.test.ts +156 -0
- package/src/resources/skills/btw/SKILL.md +42 -0
- package/src/resources/skills/create-gsd-extension/SKILL.md +5 -3
- package/src/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +5 -4
- package/src/resources/skills/create-gsd-extension/workflows/add-capability.md +2 -2
- package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +4 -4
- package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +5 -3
- package/dist/web/standalone/.next/static/chunks/6502.8b732f67a11b11b4.js +0 -9
- package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- package/dist/web/standalone/.next/static/css/a58ef8a151aa0493.css +0 -1
- package/src/resources/extensions/gsd/tests/empty-db-reconciliation.test.ts +0 -79
- /package/dist/web/standalone/.next/static/{IoheXIe-5DH7ieX8AUo8U → QlWL-8CXgQpzV3ehkNMzh}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{IoheXIe-5DH7ieX8AUo8U → QlWL-8CXgQpzV3ehkNMzh}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,1628 @@
|
|
|
1
|
+
// GSD State Machine — Comprehensive Phase-by-Phase Walkthrough Tests
|
|
2
|
+
// Verifies all 16 phases, reconciliation, edge cases, and cross-validation.
|
|
3
|
+
|
|
4
|
+
import { describe, test, afterEach } from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
deriveState,
|
|
12
|
+
deriveStateFromDb,
|
|
13
|
+
isValidationTerminal,
|
|
14
|
+
isGhostMilestone,
|
|
15
|
+
invalidateStateCache,
|
|
16
|
+
} from "../state.ts";
|
|
17
|
+
import {
|
|
18
|
+
openDatabase,
|
|
19
|
+
closeDatabase,
|
|
20
|
+
insertMilestone,
|
|
21
|
+
insertSlice,
|
|
22
|
+
insertTask,
|
|
23
|
+
updateTaskStatus,
|
|
24
|
+
getAllMilestones,
|
|
25
|
+
insertGateRow,
|
|
26
|
+
getPendingSliceGateCount,
|
|
27
|
+
} from "../gsd-db.ts";
|
|
28
|
+
import { isClosedStatus } from "../status-guards.ts";
|
|
29
|
+
import { clearPathCache } from "../paths.ts";
|
|
30
|
+
|
|
31
|
+
// ─── Fixture Helpers ─────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
const tempDirs: string[] = [];
|
|
34
|
+
|
|
35
|
+
function createFixtureBase(): string {
|
|
36
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-walkthrough-"));
|
|
37
|
+
mkdirSync(join(base, ".gsd", "milestones"), { recursive: true });
|
|
38
|
+
tempDirs.push(base);
|
|
39
|
+
return base;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
for (const dir of tempDirs.splice(0)) {
|
|
44
|
+
try {
|
|
45
|
+
rmSync(dir, { recursive: true, force: true });
|
|
46
|
+
} catch { /* best effort */ }
|
|
47
|
+
}
|
|
48
|
+
try { closeDatabase(); } catch { /* may not be open */ }
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function writeContext(base: string, mid: string, content: string): void {
|
|
52
|
+
const dir = join(base, ".gsd", "milestones", mid);
|
|
53
|
+
mkdirSync(dir, { recursive: true });
|
|
54
|
+
writeFileSync(join(dir, `${mid}-CONTEXT.md`), content);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function writeContextDraft(base: string, mid: string, content: string): void {
|
|
58
|
+
const dir = join(base, ".gsd", "milestones", mid);
|
|
59
|
+
mkdirSync(dir, { recursive: true });
|
|
60
|
+
writeFileSync(join(dir, `${mid}-CONTEXT-DRAFT.md`), content);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function writeRoadmap(base: string, mid: string, content: string): void {
|
|
64
|
+
const dir = join(base, ".gsd", "milestones", mid);
|
|
65
|
+
mkdirSync(dir, { recursive: true });
|
|
66
|
+
writeFileSync(join(dir, `${mid}-ROADMAP.md`), content);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function writePlan(base: string, mid: string, sid: string, content: string): void {
|
|
70
|
+
const dir = join(base, ".gsd", "milestones", mid, "slices", sid);
|
|
71
|
+
const tasksDir = join(dir, "tasks");
|
|
72
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
73
|
+
writeFileSync(join(dir, `${sid}-PLAN.md`), content);
|
|
74
|
+
// Create stub task plan files so deriveState doesn't fall back to planning
|
|
75
|
+
const taskMatches = content.matchAll(/\*\*(T\d+):/g);
|
|
76
|
+
for (const m of taskMatches) {
|
|
77
|
+
const tid = m[1];
|
|
78
|
+
writeFileSync(join(tasksDir, `${tid}-PLAN.md`), `# ${tid} Plan\n\nStub.\n`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function writeTaskSummary(base: string, mid: string, sid: string, tid: string): void {
|
|
83
|
+
const tasksDir = join(base, ".gsd", "milestones", mid, "slices", sid, "tasks");
|
|
84
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
85
|
+
writeFileSync(join(tasksDir, `${tid}-SUMMARY.md`), [
|
|
86
|
+
`# ${tid} Summary`,
|
|
87
|
+
"",
|
|
88
|
+
"Task completed successfully.",
|
|
89
|
+
].join("\n"));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function writeTaskSummaryWithBlocker(base: string, mid: string, sid: string, tid: string): void {
|
|
93
|
+
const tasksDir = join(base, ".gsd", "milestones", mid, "slices", sid, "tasks");
|
|
94
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
95
|
+
writeFileSync(join(tasksDir, `${tid}-SUMMARY.md`), [
|
|
96
|
+
"---",
|
|
97
|
+
"blocker_discovered: true",
|
|
98
|
+
"---",
|
|
99
|
+
"",
|
|
100
|
+
`# ${tid} Summary`,
|
|
101
|
+
"",
|
|
102
|
+
"Blocker found during execution.",
|
|
103
|
+
].join("\n"));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function writeSliceSummary(base: string, mid: string, sid: string): void {
|
|
107
|
+
const dir = join(base, ".gsd", "milestones", mid, "slices", sid);
|
|
108
|
+
mkdirSync(dir, { recursive: true });
|
|
109
|
+
writeFileSync(join(dir, `${sid}-SUMMARY.md`), `# ${sid} Summary\n\nSlice done.\n`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function writeMilestoneSummary(base: string, mid: string): void {
|
|
113
|
+
const dir = join(base, ".gsd", "milestones", mid);
|
|
114
|
+
mkdirSync(dir, { recursive: true });
|
|
115
|
+
writeFileSync(join(dir, `${mid}-SUMMARY.md`), `# ${mid} Summary\n\nMilestone complete.\n`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function writeMilestoneValidation(base: string, mid: string, verdict: string = "pass"): void {
|
|
119
|
+
const dir = join(base, ".gsd", "milestones", mid);
|
|
120
|
+
mkdirSync(dir, { recursive: true });
|
|
121
|
+
writeFileSync(join(dir, `${mid}-VALIDATION.md`), [
|
|
122
|
+
"---",
|
|
123
|
+
`verdict: ${verdict}`,
|
|
124
|
+
"remediation_round: 0",
|
|
125
|
+
"---",
|
|
126
|
+
"",
|
|
127
|
+
"# Validation",
|
|
128
|
+
"Validated.",
|
|
129
|
+
].join("\n"));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function writeReplanTrigger(base: string, mid: string, sid: string): void {
|
|
133
|
+
const dir = join(base, ".gsd", "milestones", mid, "slices", sid);
|
|
134
|
+
mkdirSync(dir, { recursive: true });
|
|
135
|
+
writeFileSync(join(dir, `${sid}-REPLAN-TRIGGER.md`), "Triage replan triggered.\n");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function writeReplan(base: string, mid: string, sid: string): void {
|
|
139
|
+
const dir = join(base, ".gsd", "milestones", mid, "slices", sid);
|
|
140
|
+
mkdirSync(dir, { recursive: true });
|
|
141
|
+
writeFileSync(join(dir, `${sid}-REPLAN.md`), "# Replan\n\nReplan completed.\n");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function writeContinue(base: string, mid: string, sid: string): void {
|
|
145
|
+
const dir = join(base, ".gsd", "milestones", mid, "slices", sid);
|
|
146
|
+
mkdirSync(dir, { recursive: true });
|
|
147
|
+
writeFileSync(join(dir, `${sid}-CONTINUE.md`), [
|
|
148
|
+
"---",
|
|
149
|
+
"milestone: " + mid,
|
|
150
|
+
"slice: " + sid,
|
|
151
|
+
"task: T01",
|
|
152
|
+
"status: interrupted",
|
|
153
|
+
"---",
|
|
154
|
+
"",
|
|
155
|
+
"# Continue",
|
|
156
|
+
"Resume from step 2.",
|
|
157
|
+
].join("\n"));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Standard roadmap with one incomplete slice */
|
|
161
|
+
function standardRoadmap(): string {
|
|
162
|
+
return [
|
|
163
|
+
"# M001: Test Milestone",
|
|
164
|
+
"",
|
|
165
|
+
"**Vision:** Test state machine.",
|
|
166
|
+
"",
|
|
167
|
+
"## Slices",
|
|
168
|
+
"",
|
|
169
|
+
"- [ ] **S01: First Slice** `risk:low` `depends:[]`",
|
|
170
|
+
" > After this: slice done.",
|
|
171
|
+
].join("\n");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Roadmap with one done slice */
|
|
175
|
+
function doneSliceRoadmap(): string {
|
|
176
|
+
return [
|
|
177
|
+
"# M001: Test Milestone",
|
|
178
|
+
"",
|
|
179
|
+
"**Vision:** Test state machine.",
|
|
180
|
+
"",
|
|
181
|
+
"## Slices",
|
|
182
|
+
"",
|
|
183
|
+
"- [x] **S01: Done Slice** `risk:low` `depends:[]`",
|
|
184
|
+
" > After this: slice done.",
|
|
185
|
+
].join("\n");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Standard plan with two incomplete tasks */
|
|
189
|
+
function standardPlan(): string {
|
|
190
|
+
return [
|
|
191
|
+
"# S01: First Slice",
|
|
192
|
+
"",
|
|
193
|
+
"**Goal:** Test.",
|
|
194
|
+
"**Demo:** Tests pass.",
|
|
195
|
+
"",
|
|
196
|
+
"## Tasks",
|
|
197
|
+
"",
|
|
198
|
+
"- [ ] **T01: First Task** `est:10m`",
|
|
199
|
+
" First task description.",
|
|
200
|
+
"",
|
|
201
|
+
"- [ ] **T02: Second Task** `est:10m`",
|
|
202
|
+
" Second task description.",
|
|
203
|
+
].join("\n");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Plan with all tasks done */
|
|
207
|
+
function allDonePlan(): string {
|
|
208
|
+
return [
|
|
209
|
+
"# S01: First Slice",
|
|
210
|
+
"",
|
|
211
|
+
"**Goal:** Test.",
|
|
212
|
+
"**Demo:** Tests pass.",
|
|
213
|
+
"",
|
|
214
|
+
"## Tasks",
|
|
215
|
+
"",
|
|
216
|
+
"- [x] **T01: First Task** `est:10m`",
|
|
217
|
+
" First task done.",
|
|
218
|
+
"",
|
|
219
|
+
"- [x] **T02: Second Task** `est:10m`",
|
|
220
|
+
" Second task done.",
|
|
221
|
+
].join("\n");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Plan with one done, one incomplete task */
|
|
225
|
+
function partialDonePlan(): string {
|
|
226
|
+
return [
|
|
227
|
+
"# S01: First Slice",
|
|
228
|
+
"",
|
|
229
|
+
"**Goal:** Test.",
|
|
230
|
+
"**Demo:** Tests pass.",
|
|
231
|
+
"",
|
|
232
|
+
"## Tasks",
|
|
233
|
+
"",
|
|
234
|
+
"- [x] **T01: First Task** `est:10m`",
|
|
235
|
+
" First task done.",
|
|
236
|
+
"",
|
|
237
|
+
"- [ ] **T02: Second Task** `est:10m`",
|
|
238
|
+
" Second task pending.",
|
|
239
|
+
].join("\n");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
243
|
+
// PHASE 1: pre-planning
|
|
244
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
245
|
+
|
|
246
|
+
describe("state-machine-full-walkthrough", () => {
|
|
247
|
+
|
|
248
|
+
describe("Phase 1: pre-planning", () => {
|
|
249
|
+
test("empty milestones dir → pre-planning", async () => {
|
|
250
|
+
const base = createFixtureBase();
|
|
251
|
+
invalidateStateCache();
|
|
252
|
+
const state = await deriveState(base);
|
|
253
|
+
|
|
254
|
+
assert.equal(state.phase, "pre-planning");
|
|
255
|
+
assert.equal(state.activeMilestone, null);
|
|
256
|
+
assert.equal(state.activeSlice, null);
|
|
257
|
+
assert.equal(state.activeTask, null);
|
|
258
|
+
assert.deepStrictEqual(state.registry, []);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("milestone with CONTEXT but no ROADMAP → pre-planning", async () => {
|
|
262
|
+
const base = createFixtureBase();
|
|
263
|
+
writeContext(base, "M001", "# M001: Test\n\nSome context.");
|
|
264
|
+
invalidateStateCache();
|
|
265
|
+
const state = await deriveState(base);
|
|
266
|
+
|
|
267
|
+
assert.equal(state.phase, "pre-planning");
|
|
268
|
+
assert.ok(state.activeMilestone !== null, "activeMilestone should be set");
|
|
269
|
+
assert.equal(state.activeMilestone?.id, "M001");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("roadmap with zero slices → pre-planning (not validating-milestone)", async () => {
|
|
273
|
+
const base = createFixtureBase();
|
|
274
|
+
writeContext(base, "M001", "# M001: Test\n\nContext.");
|
|
275
|
+
// Roadmap exists but has no slice entries
|
|
276
|
+
writeRoadmap(base, "M001", [
|
|
277
|
+
"# M001: Test Milestone",
|
|
278
|
+
"",
|
|
279
|
+
"**Vision:** Test.",
|
|
280
|
+
"",
|
|
281
|
+
"## Slices",
|
|
282
|
+
"",
|
|
283
|
+
"No slices defined yet.",
|
|
284
|
+
].join("\n"));
|
|
285
|
+
invalidateStateCache();
|
|
286
|
+
const state = await deriveState(base);
|
|
287
|
+
|
|
288
|
+
assert.equal(state.phase, "pre-planning", "zero slices must NOT trigger validating-milestone (#2667)");
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
293
|
+
// PHASE 2: needs-discussion
|
|
294
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
295
|
+
|
|
296
|
+
describe("Phase 2: needs-discussion", () => {
|
|
297
|
+
test("CONTEXT-DRAFT exists, no CONTEXT → needs-discussion", async () => {
|
|
298
|
+
const base = createFixtureBase();
|
|
299
|
+
writeContextDraft(base, "M001", "# M001: Draft\n\nDraft context.");
|
|
300
|
+
invalidateStateCache();
|
|
301
|
+
const state = await deriveState(base);
|
|
302
|
+
|
|
303
|
+
assert.equal(state.phase, "needs-discussion");
|
|
304
|
+
assert.ok(state.activeMilestone !== null);
|
|
305
|
+
assert.equal(state.activeMilestone?.id, "M001");
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("both CONTEXT-DRAFT and CONTEXT exist → NOT needs-discussion", async () => {
|
|
309
|
+
const base = createFixtureBase();
|
|
310
|
+
writeContext(base, "M001", "# M001: Real\n\nReal context.");
|
|
311
|
+
writeContextDraft(base, "M001", "# M001: Draft\n\nDraft context.");
|
|
312
|
+
invalidateStateCache();
|
|
313
|
+
const state = await deriveState(base);
|
|
314
|
+
|
|
315
|
+
assert.notEqual(state.phase, "needs-discussion", "CONTEXT should win over CONTEXT-DRAFT");
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
320
|
+
// PHASE 3: discussing (auto-mode only)
|
|
321
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
322
|
+
|
|
323
|
+
describe("Phase 3: discussing (auto-mode only)", () => {
|
|
324
|
+
test("discussing is NOT reachable from deriveState", async () => {
|
|
325
|
+
// discussing is set only by auto-mode, never by state derivation.
|
|
326
|
+
// Verify that CONTEXT-DRAFT → needs-discussion (not discussing).
|
|
327
|
+
const base = createFixtureBase();
|
|
328
|
+
writeContextDraft(base, "M001", "# M001: Draft\n\nDraft.");
|
|
329
|
+
invalidateStateCache();
|
|
330
|
+
const state = await deriveState(base);
|
|
331
|
+
assert.notEqual(state.phase, "discussing");
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
336
|
+
// PHASE 4: researching (auto-mode only)
|
|
337
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
338
|
+
|
|
339
|
+
describe("Phase 4: researching (auto-mode only)", () => {
|
|
340
|
+
test("researching is NOT reachable from deriveState", async () => {
|
|
341
|
+
const base = createFixtureBase();
|
|
342
|
+
writeContext(base, "M001", "# M001: Test\n\nContext.");
|
|
343
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
344
|
+
invalidateStateCache();
|
|
345
|
+
const state = await deriveState(base);
|
|
346
|
+
assert.notEqual(state.phase, "researching");
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
351
|
+
// PHASE 5: planning
|
|
352
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
353
|
+
|
|
354
|
+
describe("Phase 5: planning", () => {
|
|
355
|
+
test("roadmap with slice, no PLAN file → planning", async () => {
|
|
356
|
+
const base = createFixtureBase();
|
|
357
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
358
|
+
invalidateStateCache();
|
|
359
|
+
const state = await deriveState(base);
|
|
360
|
+
|
|
361
|
+
assert.equal(state.phase, "planning");
|
|
362
|
+
assert.ok(state.activeSlice !== null);
|
|
363
|
+
assert.equal(state.activeSlice?.id, "S01");
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("PLAN exists but zero tasks → planning", async () => {
|
|
367
|
+
const base = createFixtureBase();
|
|
368
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
369
|
+
// Plan file with no task entries
|
|
370
|
+
const dir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
371
|
+
mkdirSync(dir, { recursive: true });
|
|
372
|
+
writeFileSync(join(dir, "S01-PLAN.md"), [
|
|
373
|
+
"# S01: First Slice",
|
|
374
|
+
"",
|
|
375
|
+
"**Goal:** Test.",
|
|
376
|
+
"**Demo:** Tests pass.",
|
|
377
|
+
"",
|
|
378
|
+
"## Tasks",
|
|
379
|
+
"",
|
|
380
|
+
"No tasks defined yet.",
|
|
381
|
+
].join("\n"));
|
|
382
|
+
invalidateStateCache();
|
|
383
|
+
const state = await deriveState(base);
|
|
384
|
+
|
|
385
|
+
assert.equal(state.phase, "planning", "plan with zero tasks should remain in planning");
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("PLAN with tasks but missing T##-PLAN.md files → planning", async () => {
|
|
389
|
+
const base = createFixtureBase();
|
|
390
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
391
|
+
// Write plan file WITH tasks but WITHOUT stub T##-PLAN.md files
|
|
392
|
+
const dir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
393
|
+
mkdirSync(join(dir, "tasks"), { recursive: true });
|
|
394
|
+
writeFileSync(join(dir, "S01-PLAN.md"), standardPlan());
|
|
395
|
+
// Intentionally do NOT create T01-PLAN.md or T02-PLAN.md
|
|
396
|
+
invalidateStateCache();
|
|
397
|
+
const state = await deriveState(base);
|
|
398
|
+
|
|
399
|
+
assert.equal(state.phase, "planning", "missing task plan files should stay in planning");
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("PLAN with all task plan files → NOT planning", async () => {
|
|
403
|
+
const base = createFixtureBase();
|
|
404
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
405
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
406
|
+
invalidateStateCache();
|
|
407
|
+
const state = await deriveState(base);
|
|
408
|
+
|
|
409
|
+
assert.notEqual(state.phase, "planning", "complete plan should advance past planning");
|
|
410
|
+
// Should be executing since there are incomplete tasks
|
|
411
|
+
assert.equal(state.phase, "executing");
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
416
|
+
// PHASE 6: evaluating-gates (DB path only)
|
|
417
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
418
|
+
|
|
419
|
+
describe("Phase 6: evaluating-gates", () => {
|
|
420
|
+
test("DB path: pending quality gates → evaluating-gates", async () => {
|
|
421
|
+
const base = createFixtureBase();
|
|
422
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
423
|
+
openDatabase(dbPath);
|
|
424
|
+
|
|
425
|
+
// Set up milestone + slice + task in DB
|
|
426
|
+
insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
|
|
427
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
|
|
428
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: Task", status: "pending" });
|
|
429
|
+
|
|
430
|
+
// Write plan on disk (needed for state derivation)
|
|
431
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
432
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
433
|
+
|
|
434
|
+
// Insert a pending quality gate
|
|
435
|
+
insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice", status: "pending" });
|
|
436
|
+
|
|
437
|
+
const pending = getPendingSliceGateCount("M001", "S01");
|
|
438
|
+
assert.ok(pending > 0, "should have pending gates");
|
|
439
|
+
|
|
440
|
+
invalidateStateCache();
|
|
441
|
+
const state = await deriveStateFromDb(base);
|
|
442
|
+
|
|
443
|
+
assert.equal(state.phase, "evaluating-gates");
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test("DB path: no pending gates → NOT evaluating-gates", async () => {
|
|
447
|
+
const base = createFixtureBase();
|
|
448
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
449
|
+
openDatabase(dbPath);
|
|
450
|
+
|
|
451
|
+
insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
|
|
452
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
|
|
453
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: Task", status: "pending" });
|
|
454
|
+
|
|
455
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
456
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
457
|
+
|
|
458
|
+
// No gate rows → getPendingSliceGateCount returns 0
|
|
459
|
+
const pending = getPendingSliceGateCount("M001", "S01");
|
|
460
|
+
assert.equal(pending, 0, "should have no pending gates");
|
|
461
|
+
|
|
462
|
+
invalidateStateCache();
|
|
463
|
+
const state = await deriveStateFromDb(base);
|
|
464
|
+
|
|
465
|
+
assert.notEqual(state.phase, "evaluating-gates");
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
470
|
+
// PHASE 7: executing
|
|
471
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
472
|
+
|
|
473
|
+
describe("Phase 7: executing", () => {
|
|
474
|
+
test("active task, no blockers → executing", async () => {
|
|
475
|
+
const base = createFixtureBase();
|
|
476
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
477
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
478
|
+
invalidateStateCache();
|
|
479
|
+
const state = await deriveState(base);
|
|
480
|
+
|
|
481
|
+
assert.equal(state.phase, "executing");
|
|
482
|
+
assert.ok(state.activeTask !== null);
|
|
483
|
+
assert.equal(state.activeTask?.id, "T01");
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test("active task with CONTINUE.md → executing with resume message", async () => {
|
|
487
|
+
const base = createFixtureBase();
|
|
488
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
489
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
490
|
+
writeContinue(base, "M001", "S01");
|
|
491
|
+
invalidateStateCache();
|
|
492
|
+
const state = await deriveState(base);
|
|
493
|
+
|
|
494
|
+
assert.equal(state.phase, "executing");
|
|
495
|
+
assert.ok(
|
|
496
|
+
state.nextAction.toLowerCase().includes("resume") || state.nextAction.toLowerCase().includes("continue"),
|
|
497
|
+
"nextAction should mention resume/continue",
|
|
498
|
+
);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test("one task remaining among completed → executing (not summarizing)", async () => {
|
|
502
|
+
const base = createFixtureBase();
|
|
503
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
504
|
+
writePlan(base, "M001", "S01", partialDonePlan());
|
|
505
|
+
invalidateStateCache();
|
|
506
|
+
const state = await deriveState(base);
|
|
507
|
+
|
|
508
|
+
assert.equal(state.phase, "executing", "should be executing while tasks remain");
|
|
509
|
+
assert.equal(state.activeTask?.id, "T02", "active task should be T02");
|
|
510
|
+
assert.equal(state.progress?.tasks?.done, 1);
|
|
511
|
+
assert.equal(state.progress?.tasks?.total, 2);
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
516
|
+
// PHASE 8: verifying (auto-mode only)
|
|
517
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
518
|
+
|
|
519
|
+
describe("Phase 8: verifying (auto-mode only)", () => {
|
|
520
|
+
test("verifying is NOT reachable from deriveState", async () => {
|
|
521
|
+
// verifying is set only by auto-mode verification gates.
|
|
522
|
+
const base = createFixtureBase();
|
|
523
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
524
|
+
writePlan(base, "M001", "S01", allDonePlan());
|
|
525
|
+
invalidateStateCache();
|
|
526
|
+
const state = await deriveState(base);
|
|
527
|
+
assert.notEqual(state.phase, "verifying");
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
532
|
+
// PHASE 9: summarizing
|
|
533
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
534
|
+
|
|
535
|
+
describe("Phase 9: summarizing", () => {
|
|
536
|
+
test("all tasks done, slice not complete → summarizing", async () => {
|
|
537
|
+
const base = createFixtureBase();
|
|
538
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
539
|
+
writePlan(base, "M001", "S01", allDonePlan());
|
|
540
|
+
invalidateStateCache();
|
|
541
|
+
const state = await deriveState(base);
|
|
542
|
+
|
|
543
|
+
assert.equal(state.phase, "summarizing");
|
|
544
|
+
assert.ok(state.activeSlice !== null);
|
|
545
|
+
assert.equal(state.activeSlice?.id, "S01");
|
|
546
|
+
assert.equal(state.activeTask, null, "no active task when all done");
|
|
547
|
+
assert.equal(state.progress?.tasks?.done, 2);
|
|
548
|
+
assert.equal(state.progress?.tasks?.total, 2);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
test("tasks reconciled via SUMMARY on disk → summarizing", async () => {
|
|
552
|
+
const base = createFixtureBase();
|
|
553
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
554
|
+
// Plan says tasks incomplete (headings, no checkboxes) ...
|
|
555
|
+
const planContent = [
|
|
556
|
+
"# S01: First Slice",
|
|
557
|
+
"",
|
|
558
|
+
"**Goal:** Test.",
|
|
559
|
+
"**Demo:** Tests pass.",
|
|
560
|
+
"",
|
|
561
|
+
"## Tasks",
|
|
562
|
+
"",
|
|
563
|
+
"### T01: First Task",
|
|
564
|
+
"First task.",
|
|
565
|
+
"",
|
|
566
|
+
"### T02: Second Task",
|
|
567
|
+
"Second task.",
|
|
568
|
+
].join("\n");
|
|
569
|
+
const dir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
570
|
+
const tasksDir = join(dir, "tasks");
|
|
571
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
572
|
+
writeFileSync(join(dir, "S01-PLAN.md"), planContent);
|
|
573
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\nStub.\n");
|
|
574
|
+
writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan\nStub.\n");
|
|
575
|
+
|
|
576
|
+
// ... but SUMMARY files exist on disk (reconciliation trigger)
|
|
577
|
+
writeTaskSummary(base, "M001", "S01", "T01");
|
|
578
|
+
writeTaskSummary(base, "M001", "S01", "T02");
|
|
579
|
+
|
|
580
|
+
invalidateStateCache();
|
|
581
|
+
const state = await deriveState(base);
|
|
582
|
+
|
|
583
|
+
// Reconciliation should mark both tasks done → summarizing
|
|
584
|
+
assert.equal(state.phase, "summarizing", "SUMMARY reconciliation should advance to summarizing");
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
589
|
+
// PHASE 10: advancing (auto-mode only)
|
|
590
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
591
|
+
|
|
592
|
+
describe("Phase 10: advancing (auto-mode only)", () => {
|
|
593
|
+
test("advancing is NOT reachable from deriveState", async () => {
|
|
594
|
+
// advancing is an internal auto-mode transition marker
|
|
595
|
+
const base = createFixtureBase();
|
|
596
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
597
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
598
|
+
invalidateStateCache();
|
|
599
|
+
const state = await deriveState(base);
|
|
600
|
+
assert.notEqual(state.phase, "advancing");
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
605
|
+
// PHASE 11: validating-milestone
|
|
606
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
607
|
+
|
|
608
|
+
describe("Phase 11: validating-milestone", () => {
|
|
609
|
+
test("all slices done, no VALIDATION file → validating-milestone", async () => {
|
|
610
|
+
const base = createFixtureBase();
|
|
611
|
+
writeRoadmap(base, "M001", doneSliceRoadmap());
|
|
612
|
+
invalidateStateCache();
|
|
613
|
+
const state = await deriveState(base);
|
|
614
|
+
|
|
615
|
+
assert.equal(state.phase, "validating-milestone");
|
|
616
|
+
assert.ok(state.activeMilestone !== null);
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
test("all slices done, VALIDATION with unparseable verdict → validating-milestone", async () => {
|
|
620
|
+
const base = createFixtureBase();
|
|
621
|
+
writeRoadmap(base, "M001", doneSliceRoadmap());
|
|
622
|
+
// Write a validation file with no parseable verdict
|
|
623
|
+
const dir = join(base, ".gsd", "milestones", "M001");
|
|
624
|
+
mkdirSync(dir, { recursive: true });
|
|
625
|
+
writeFileSync(join(dir, "M001-VALIDATION.md"), "Just some text with no frontmatter verdict.");
|
|
626
|
+
invalidateStateCache();
|
|
627
|
+
const state = await deriveState(base);
|
|
628
|
+
|
|
629
|
+
assert.equal(state.phase, "validating-milestone", "unparseable verdict should stay in validating");
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
test("all slices done, terminal verdict → NOT validating-milestone", async () => {
|
|
633
|
+
const base = createFixtureBase();
|
|
634
|
+
writeRoadmap(base, "M001", doneSliceRoadmap());
|
|
635
|
+
writeMilestoneValidation(base, "M001", "pass");
|
|
636
|
+
invalidateStateCache();
|
|
637
|
+
const state = await deriveState(base);
|
|
638
|
+
|
|
639
|
+
assert.notEqual(state.phase, "validating-milestone");
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
644
|
+
// PHASE 12: completing-milestone
|
|
645
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
646
|
+
|
|
647
|
+
describe("Phase 12: completing-milestone", () => {
|
|
648
|
+
test("all slices done, validation terminal, no SUMMARY → completing-milestone", async () => {
|
|
649
|
+
const base = createFixtureBase();
|
|
650
|
+
writeRoadmap(base, "M001", doneSliceRoadmap());
|
|
651
|
+
writeMilestoneValidation(base, "M001", "pass");
|
|
652
|
+
invalidateStateCache();
|
|
653
|
+
const state = await deriveState(base);
|
|
654
|
+
|
|
655
|
+
assert.equal(state.phase, "completing-milestone");
|
|
656
|
+
assert.ok(state.activeMilestone !== null);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
test("all slices done, validation terminal, SUMMARY exists → NOT completing-milestone", async () => {
|
|
660
|
+
const base = createFixtureBase();
|
|
661
|
+
writeRoadmap(base, "M001", doneSliceRoadmap());
|
|
662
|
+
writeMilestoneValidation(base, "M001", "pass");
|
|
663
|
+
writeMilestoneSummary(base, "M001");
|
|
664
|
+
invalidateStateCache();
|
|
665
|
+
const state = await deriveState(base);
|
|
666
|
+
|
|
667
|
+
assert.notEqual(state.phase, "completing-milestone", "should be complete, not completing");
|
|
668
|
+
assert.equal(state.phase, "complete");
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
673
|
+
// PHASE 13: replanning-slice
|
|
674
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
675
|
+
|
|
676
|
+
describe("Phase 13: replanning-slice", () => {
|
|
677
|
+
test("filesystem: task with blocker_discovered, no REPLAN.md → replanning-slice", async () => {
|
|
678
|
+
const base = createFixtureBase();
|
|
679
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
680
|
+
// T01 is done with blocker, T02 is pending
|
|
681
|
+
writePlan(base, "M001", "S01", partialDonePlan());
|
|
682
|
+
writeTaskSummaryWithBlocker(base, "M001", "S01", "T01");
|
|
683
|
+
invalidateStateCache();
|
|
684
|
+
const state = await deriveState(base);
|
|
685
|
+
|
|
686
|
+
assert.equal(state.phase, "replanning-slice");
|
|
687
|
+
assert.ok(state.blockers.length > 0, "should have blocker details");
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
test("filesystem: REPLAN-TRIGGER.md exists, no REPLAN.md → replanning-slice", async () => {
|
|
691
|
+
const base = createFixtureBase();
|
|
692
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
693
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
694
|
+
writeReplanTrigger(base, "M001", "S01");
|
|
695
|
+
invalidateStateCache();
|
|
696
|
+
const state = await deriveState(base);
|
|
697
|
+
|
|
698
|
+
assert.equal(state.phase, "replanning-slice");
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
test("filesystem: REPLAN-TRIGGER + REPLAN.md exists → NOT replanning-slice (loop guard)", async () => {
|
|
702
|
+
const base = createFixtureBase();
|
|
703
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
704
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
705
|
+
writeReplanTrigger(base, "M001", "S01");
|
|
706
|
+
writeReplan(base, "M001", "S01");
|
|
707
|
+
invalidateStateCache();
|
|
708
|
+
const state = await deriveState(base);
|
|
709
|
+
|
|
710
|
+
assert.notEqual(state.phase, "replanning-slice", "REPLAN.md loop guard should prevent re-entering replanning");
|
|
711
|
+
// Should fall through to executing
|
|
712
|
+
assert.equal(state.phase, "executing");
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
717
|
+
// PHASE 14: complete
|
|
718
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
719
|
+
|
|
720
|
+
describe("Phase 14: complete", () => {
|
|
721
|
+
test("single milestone with SUMMARY + VALIDATION → complete", async () => {
|
|
722
|
+
const base = createFixtureBase();
|
|
723
|
+
writeRoadmap(base, "M001", doneSliceRoadmap());
|
|
724
|
+
writeMilestoneValidation(base, "M001", "pass");
|
|
725
|
+
writeMilestoneSummary(base, "M001");
|
|
726
|
+
invalidateStateCache();
|
|
727
|
+
const state = await deriveState(base);
|
|
728
|
+
|
|
729
|
+
assert.equal(state.phase, "complete");
|
|
730
|
+
assert.equal(state.registry.length, 1);
|
|
731
|
+
assert.equal(state.registry[0]?.status, "complete");
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
test("all milestones complete → complete", async () => {
|
|
735
|
+
const base = createFixtureBase();
|
|
736
|
+
// M001: complete
|
|
737
|
+
writeRoadmap(base, "M001", doneSliceRoadmap());
|
|
738
|
+
writeMilestoneValidation(base, "M001", "pass");
|
|
739
|
+
writeMilestoneSummary(base, "M001");
|
|
740
|
+
|
|
741
|
+
// M002: also complete
|
|
742
|
+
writeRoadmap(base, "M002", [
|
|
743
|
+
"# M002: Second Milestone",
|
|
744
|
+
"",
|
|
745
|
+
"**Vision:** Test.",
|
|
746
|
+
"",
|
|
747
|
+
"## Slices",
|
|
748
|
+
"",
|
|
749
|
+
"- [x] **S01: Done** `risk:low` `depends:[]`",
|
|
750
|
+
" > After this: done.",
|
|
751
|
+
].join("\n"));
|
|
752
|
+
writeMilestoneValidation(base, "M002", "pass");
|
|
753
|
+
writeMilestoneSummary(base, "M002");
|
|
754
|
+
|
|
755
|
+
invalidateStateCache();
|
|
756
|
+
const state = await deriveState(base);
|
|
757
|
+
|
|
758
|
+
assert.equal(state.phase, "complete");
|
|
759
|
+
assert.equal(state.registry.length, 2);
|
|
760
|
+
assert.ok(state.registry.every(e => e.status === "complete"), "all registry entries should be complete");
|
|
761
|
+
});
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
765
|
+
// PHASE 15: paused (auto-mode only)
|
|
766
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
767
|
+
|
|
768
|
+
describe("Phase 15: paused (auto-mode only)", () => {
|
|
769
|
+
test("paused is NOT reachable from deriveState", async () => {
|
|
770
|
+
const base = createFixtureBase();
|
|
771
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
772
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
773
|
+
invalidateStateCache();
|
|
774
|
+
const state = await deriveState(base);
|
|
775
|
+
assert.notEqual(state.phase, "paused");
|
|
776
|
+
});
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
780
|
+
// PHASE 16: blocked
|
|
781
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
782
|
+
|
|
783
|
+
describe("Phase 16: blocked", () => {
|
|
784
|
+
test("milestone with unmet dependency → blocked", async () => {
|
|
785
|
+
const base = createFixtureBase();
|
|
786
|
+
// M001 depends on M000 which doesn't exist — uses YAML frontmatter
|
|
787
|
+
writeContext(base, "M001", [
|
|
788
|
+
"---",
|
|
789
|
+
"depends_on:",
|
|
790
|
+
" - M000",
|
|
791
|
+
"---",
|
|
792
|
+
"",
|
|
793
|
+
"# M001: Test",
|
|
794
|
+
"",
|
|
795
|
+
"Context.",
|
|
796
|
+
].join("\n"));
|
|
797
|
+
writeRoadmap(base, "M001", [
|
|
798
|
+
"# M001: Test Milestone",
|
|
799
|
+
"",
|
|
800
|
+
"**Vision:** Test blocked.",
|
|
801
|
+
"",
|
|
802
|
+
"## Slices",
|
|
803
|
+
"",
|
|
804
|
+
"- [ ] **S01: Slice** `risk:low` `depends:[]`",
|
|
805
|
+
" > After this: done.",
|
|
806
|
+
].join("\n"));
|
|
807
|
+
invalidateStateCache();
|
|
808
|
+
const state = await deriveState(base);
|
|
809
|
+
|
|
810
|
+
assert.equal(state.phase, "blocked");
|
|
811
|
+
assert.ok(state.blockers.length > 0, "should have blockers");
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
test("no eligible slice (all deps unmet) → blocked at slice level", async () => {
|
|
815
|
+
const base = createFixtureBase();
|
|
816
|
+
// S01 depends on S00 which doesn't exist
|
|
817
|
+
writeRoadmap(base, "M001", [
|
|
818
|
+
"# M001: Test Milestone",
|
|
819
|
+
"",
|
|
820
|
+
"**Vision:** Test blocked slices.",
|
|
821
|
+
"",
|
|
822
|
+
"## Slices",
|
|
823
|
+
"",
|
|
824
|
+
"- [ ] **S01: First** `risk:low` `depends:[S00]`",
|
|
825
|
+
" > After this: done.",
|
|
826
|
+
].join("\n"));
|
|
827
|
+
invalidateStateCache();
|
|
828
|
+
const state = await deriveState(base);
|
|
829
|
+
|
|
830
|
+
assert.equal(state.phase, "blocked");
|
|
831
|
+
assert.ok(
|
|
832
|
+
state.blockers.some(b => b.includes("dependency") || b.includes("eligible")),
|
|
833
|
+
"blockers should mention dependency or eligibility",
|
|
834
|
+
);
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
839
|
+
// RECONCILIATION
|
|
840
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
841
|
+
|
|
842
|
+
describe("Reconciliation", () => {
|
|
843
|
+
test("DB: task with SUMMARY on disk but DB says pending → reconciliation fixes status (#2514)", async () => {
|
|
844
|
+
const base = createFixtureBase();
|
|
845
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
846
|
+
openDatabase(dbPath);
|
|
847
|
+
|
|
848
|
+
insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
|
|
849
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
|
|
850
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: Task", status: "pending" });
|
|
851
|
+
insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", title: "T02: Task", status: "pending" });
|
|
852
|
+
|
|
853
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
854
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
855
|
+
|
|
856
|
+
// Write SUMMARY files on disk for both tasks (simulating session disconnect)
|
|
857
|
+
writeTaskSummary(base, "M001", "S01", "T01");
|
|
858
|
+
writeTaskSummary(base, "M001", "S01", "T02");
|
|
859
|
+
|
|
860
|
+
invalidateStateCache();
|
|
861
|
+
const state = await deriveStateFromDb(base);
|
|
862
|
+
|
|
863
|
+
// Reconciliation should detect SUMMARY→DB mismatch and update
|
|
864
|
+
// All tasks done → summarizing (not executing)
|
|
865
|
+
assert.equal(state.phase, "summarizing", "reconciliation should advance past pending tasks");
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
test("empty DB with disk milestones → disk-to-DB sync (#2631)", async () => {
|
|
869
|
+
const base = createFixtureBase();
|
|
870
|
+
writeContext(base, "M001", "# M001: Test\n\nContext.");
|
|
871
|
+
|
|
872
|
+
// Open DB — milestones table starts empty
|
|
873
|
+
openDatabase(":memory:");
|
|
874
|
+
const before = getAllMilestones();
|
|
875
|
+
assert.equal(before.length, 0, "DB should start empty");
|
|
876
|
+
|
|
877
|
+
invalidateStateCache();
|
|
878
|
+
const state = await deriveState(base);
|
|
879
|
+
|
|
880
|
+
// After deriveState, DB should have the disk milestone
|
|
881
|
+
const after = getAllMilestones();
|
|
882
|
+
assert.ok(after.length > 0, "DB should have milestones after reconciliation");
|
|
883
|
+
assert.equal(after[0]!.id, "M001");
|
|
884
|
+
assert.ok(state.activeMilestone !== null);
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
test("ghost milestone (empty dir) → NOT in registry", async () => {
|
|
888
|
+
const base = createFixtureBase();
|
|
889
|
+
// Create empty milestone dir (ghost — no CONTEXT, ROADMAP, SUMMARY)
|
|
890
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
891
|
+
// Create a real milestone too
|
|
892
|
+
writeContext(base, "M002", "# M002: Real\n\nContext.");
|
|
893
|
+
invalidateStateCache();
|
|
894
|
+
const state = await deriveState(base);
|
|
895
|
+
|
|
896
|
+
// M001 (ghost) should not appear in registry
|
|
897
|
+
const m001 = state.registry.find(e => e.id === "M001");
|
|
898
|
+
assert.equal(m001, undefined, "ghost milestone should not appear in registry");
|
|
899
|
+
// M002 should be there
|
|
900
|
+
const m002 = state.registry.find(e => e.id === "M002");
|
|
901
|
+
assert.ok(m002 !== undefined, "real milestone should appear in registry");
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
test("ghost milestone detection helper", () => {
|
|
905
|
+
const base = createFixtureBase();
|
|
906
|
+
// Ghost: empty dir
|
|
907
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
908
|
+
clearPathCache();
|
|
909
|
+
assert.equal(isGhostMilestone(base, "M001"), true, "empty dir is ghost");
|
|
910
|
+
|
|
911
|
+
// Not ghost: has CONTEXT
|
|
912
|
+
writeContext(base, "M002", "# M002\n\nContext.");
|
|
913
|
+
clearPathCache();
|
|
914
|
+
assert.equal(isGhostMilestone(base, "M002"), false, "dir with CONTEXT is not ghost");
|
|
915
|
+
});
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
919
|
+
// CROSS-VALIDATION
|
|
920
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
921
|
+
|
|
922
|
+
describe("Cross-validation: DB vs filesystem", () => {
|
|
923
|
+
test("executing scenario produces same phase on both paths", async () => {
|
|
924
|
+
const base = createFixtureBase();
|
|
925
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
926
|
+
openDatabase(dbPath);
|
|
927
|
+
|
|
928
|
+
insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
|
|
929
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
|
|
930
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: First", status: "pending" });
|
|
931
|
+
insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", title: "T02: Second", status: "pending" });
|
|
932
|
+
|
|
933
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
934
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
935
|
+
|
|
936
|
+
invalidateStateCache();
|
|
937
|
+
const dbState = await deriveStateFromDb(base);
|
|
938
|
+
|
|
939
|
+
closeDatabase();
|
|
940
|
+
|
|
941
|
+
invalidateStateCache();
|
|
942
|
+
const fsState = await deriveState(base);
|
|
943
|
+
|
|
944
|
+
assert.equal(dbState.phase, "executing", "DB path should produce executing");
|
|
945
|
+
assert.equal(fsState.phase, "executing", "filesystem path should produce executing");
|
|
946
|
+
assert.equal(dbState.activeTask?.id, fsState.activeTask?.id, "active task should match");
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
test("summarizing scenario produces same phase on both paths", async () => {
|
|
950
|
+
const base = createFixtureBase();
|
|
951
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
952
|
+
openDatabase(dbPath);
|
|
953
|
+
|
|
954
|
+
insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
|
|
955
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
|
|
956
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: First", status: "complete" });
|
|
957
|
+
insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", title: "T02: Second", status: "complete" });
|
|
958
|
+
|
|
959
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
960
|
+
writePlan(base, "M001", "S01", allDonePlan());
|
|
961
|
+
|
|
962
|
+
invalidateStateCache();
|
|
963
|
+
const dbState = await deriveStateFromDb(base);
|
|
964
|
+
|
|
965
|
+
closeDatabase();
|
|
966
|
+
|
|
967
|
+
invalidateStateCache();
|
|
968
|
+
const fsState = await deriveState(base);
|
|
969
|
+
|
|
970
|
+
assert.equal(dbState.phase, "summarizing", "DB path should produce summarizing");
|
|
971
|
+
assert.equal(fsState.phase, "summarizing", "filesystem path should produce summarizing");
|
|
972
|
+
});
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
976
|
+
// EDGE CASES
|
|
977
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
978
|
+
|
|
979
|
+
describe("Edge cases", () => {
|
|
980
|
+
test("isValidationTerminal: terminal verdicts", () => {
|
|
981
|
+
assert.equal(isValidationTerminal("---\nverdict: pass\n---\n"), true, "pass is terminal");
|
|
982
|
+
assert.equal(isValidationTerminal("---\nverdict: fail\n---\n"), true, "fail is terminal");
|
|
983
|
+
assert.equal(isValidationTerminal("---\nverdict: needs-remediation\n---\n"), true, "needs-remediation is terminal");
|
|
984
|
+
assert.equal(isValidationTerminal("---\nverdict: needs-attention\n---\n"), true, "needs-attention is terminal");
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
test("isValidationTerminal: non-terminal content", () => {
|
|
988
|
+
assert.equal(isValidationTerminal("No frontmatter at all"), false, "no frontmatter is not terminal");
|
|
989
|
+
assert.equal(isValidationTerminal(""), false, "empty string is not terminal");
|
|
990
|
+
assert.equal(isValidationTerminal("---\n---\n"), false, "empty frontmatter is not terminal");
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
test("isClosedStatus boundary", () => {
|
|
994
|
+
assert.equal(isClosedStatus("complete"), true);
|
|
995
|
+
assert.equal(isClosedStatus("done"), true);
|
|
996
|
+
assert.equal(isClosedStatus("pending"), false);
|
|
997
|
+
assert.equal(isClosedStatus("in-progress"), false);
|
|
998
|
+
assert.equal(isClosedStatus("blocked"), false);
|
|
999
|
+
assert.equal(isClosedStatus("active"), false);
|
|
1000
|
+
assert.equal(isClosedStatus(""), false);
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
test("multiple milestones: M001 complete, M002 active → M002 is activeMilestone", async () => {
|
|
1004
|
+
const base = createFixtureBase();
|
|
1005
|
+
// M001: complete
|
|
1006
|
+
writeRoadmap(base, "M001", doneSliceRoadmap());
|
|
1007
|
+
writeMilestoneValidation(base, "M001", "pass");
|
|
1008
|
+
writeMilestoneSummary(base, "M001");
|
|
1009
|
+
|
|
1010
|
+
// M002: active, in planning phase
|
|
1011
|
+
writeContext(base, "M002", "# M002: Next Milestone\n\nContext for M002.");
|
|
1012
|
+
writeRoadmap(base, "M002", [
|
|
1013
|
+
"# M002: Next Milestone",
|
|
1014
|
+
"",
|
|
1015
|
+
"**Vision:** Next phase.",
|
|
1016
|
+
"",
|
|
1017
|
+
"## Slices",
|
|
1018
|
+
"",
|
|
1019
|
+
"- [ ] **S01: New Slice** `risk:low` `depends:[]`",
|
|
1020
|
+
" > After this: done.",
|
|
1021
|
+
].join("\n"));
|
|
1022
|
+
|
|
1023
|
+
invalidateStateCache();
|
|
1024
|
+
const state = await deriveState(base);
|
|
1025
|
+
|
|
1026
|
+
assert.equal(state.activeMilestone?.id, "M002", "active milestone should be M002");
|
|
1027
|
+
assert.notEqual(state.phase, "complete", "should not be complete while M002 is active");
|
|
1028
|
+
// M001 in registry as complete
|
|
1029
|
+
const m001 = state.registry.find(e => e.id === "M001");
|
|
1030
|
+
assert.ok(m001 !== undefined, "M001 should be in registry");
|
|
1031
|
+
assert.equal(m001?.status, "complete", "M001 should be complete");
|
|
1032
|
+
// M002 in registry as active
|
|
1033
|
+
const m002 = state.registry.find(e => e.id === "M002");
|
|
1034
|
+
assert.ok(m002 !== undefined, "M002 should be in registry");
|
|
1035
|
+
assert.equal(m002?.status, "active", "M002 should be active");
|
|
1036
|
+
});
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1040
|
+
// FAILURE MODES: What happens when things go wrong
|
|
1041
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1042
|
+
|
|
1043
|
+
describe("Failure: DB has slice but no task rows (partial migration)", () => {
|
|
1044
|
+
test("DB tasks empty but PLAN on disk has tasks → wrong phase (planning)", async () => {
|
|
1045
|
+
const base = createFixtureBase();
|
|
1046
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
1047
|
+
openDatabase(dbPath);
|
|
1048
|
+
|
|
1049
|
+
insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
|
|
1050
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
|
|
1051
|
+
// NO insertTask() — simulates partial migration / failed write
|
|
1052
|
+
|
|
1053
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
1054
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
1055
|
+
|
|
1056
|
+
invalidateStateCache();
|
|
1057
|
+
const state = await deriveStateFromDb(base);
|
|
1058
|
+
|
|
1059
|
+
// BUG: Returns "planning" because getSliceTasks() returns []
|
|
1060
|
+
// and line 703 treats empty tasks as "no tasks defined".
|
|
1061
|
+
// PLAN file on disk has T01/T02 but DB doesn't know about them.
|
|
1062
|
+
assert.equal(state.phase, "planning",
|
|
1063
|
+
"KNOWN ISSUE: DB empty tasks → planning even though PLAN has tasks on disk");
|
|
1064
|
+
});
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
describe("Failure: partial SUMMARY reconciliation", () => {
|
|
1068
|
+
test("only one task has SUMMARY, other still pending → executing next task", async () => {
|
|
1069
|
+
const base = createFixtureBase();
|
|
1070
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
1071
|
+
openDatabase(dbPath);
|
|
1072
|
+
|
|
1073
|
+
insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
|
|
1074
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
|
|
1075
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: Task", status: "pending" });
|
|
1076
|
+
insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", title: "T02: Task", status: "pending" });
|
|
1077
|
+
|
|
1078
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
1079
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
1080
|
+
// Only T01 has SUMMARY, T02 does not
|
|
1081
|
+
writeTaskSummary(base, "M001", "S01", "T01");
|
|
1082
|
+
|
|
1083
|
+
invalidateStateCache();
|
|
1084
|
+
const state = await deriveStateFromDb(base);
|
|
1085
|
+
|
|
1086
|
+
// T01 reconciled to complete, T02 still pending → executing T02
|
|
1087
|
+
assert.equal(state.phase, "executing");
|
|
1088
|
+
assert.equal(state.activeTask?.id, "T02", "should advance to next pending task");
|
|
1089
|
+
});
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
describe("Failure: 0-byte files", () => {
|
|
1093
|
+
test("0-byte SUMMARY file triggers reconciliation (existsSync-only check)", async () => {
|
|
1094
|
+
const base = createFixtureBase();
|
|
1095
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
1096
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
1097
|
+
// Write 0-byte SUMMARY — existsSync returns true for empty files
|
|
1098
|
+
const tasksDir = join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
1099
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
1100
|
+
writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "");
|
|
1101
|
+
|
|
1102
|
+
invalidateStateCache();
|
|
1103
|
+
clearPathCache();
|
|
1104
|
+
const state = await deriveState(base);
|
|
1105
|
+
|
|
1106
|
+
// The reconciler checks existsSync(summaryPath) at line 1328
|
|
1107
|
+
// — it does NOT read content. So 0-byte file counts as "done".
|
|
1108
|
+
// This is a known gap: empty SUMMARY treated as completion.
|
|
1109
|
+
assert.equal(state.phase, "executing",
|
|
1110
|
+
"0-byte SUMMARY marks T01 done via reconciliation, T02 becomes active");
|
|
1111
|
+
assert.equal(state.activeTask?.id, "T02");
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
test("0-byte VALIDATION file → stays in validating-milestone", async () => {
|
|
1115
|
+
const base = createFixtureBase();
|
|
1116
|
+
writeRoadmap(base, "M001", doneSliceRoadmap());
|
|
1117
|
+
const dir = join(base, ".gsd", "milestones", "M001");
|
|
1118
|
+
mkdirSync(dir, { recursive: true });
|
|
1119
|
+
writeFileSync(join(dir, "M001-VALIDATION.md"), "");
|
|
1120
|
+
|
|
1121
|
+
invalidateStateCache();
|
|
1122
|
+
const state = await deriveState(base);
|
|
1123
|
+
|
|
1124
|
+
assert.equal(state.phase, "validating-milestone",
|
|
1125
|
+
"0-byte VALIDATION should not be treated as terminal");
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
test("0-byte PLAN file → planning phase", async () => {
|
|
1129
|
+
const base = createFixtureBase();
|
|
1130
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
1131
|
+
const dir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
1132
|
+
mkdirSync(dir, { recursive: true });
|
|
1133
|
+
writeFileSync(join(dir, "S01-PLAN.md"), "");
|
|
1134
|
+
|
|
1135
|
+
invalidateStateCache();
|
|
1136
|
+
const state = await deriveState(base);
|
|
1137
|
+
|
|
1138
|
+
assert.equal(state.phase, "planning", "0-byte PLAN should stay in planning");
|
|
1139
|
+
});
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
describe("Failure: DB/filesystem divergence", () => {
|
|
1143
|
+
test("DB says slice complete, no milestone VALIDATION → validating-milestone", async () => {
|
|
1144
|
+
const base = createFixtureBase();
|
|
1145
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
1146
|
+
openDatabase(dbPath);
|
|
1147
|
+
|
|
1148
|
+
insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
|
|
1149
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "complete", depends: [] });
|
|
1150
|
+
|
|
1151
|
+
writeRoadmap(base, "M001", doneSliceRoadmap());
|
|
1152
|
+
|
|
1153
|
+
invalidateStateCache();
|
|
1154
|
+
const state = await deriveStateFromDb(base);
|
|
1155
|
+
|
|
1156
|
+
assert.equal(state.phase, "validating-milestone",
|
|
1157
|
+
"DB-complete slice should trigger milestone validation");
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
test("DB says task complete but SUMMARY missing → no crash, advances to next", async () => {
|
|
1161
|
+
const base = createFixtureBase();
|
|
1162
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
1163
|
+
openDatabase(dbPath);
|
|
1164
|
+
|
|
1165
|
+
insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
|
|
1166
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
|
|
1167
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: Task", status: "complete" });
|
|
1168
|
+
insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", title: "T02: Task", status: "pending" });
|
|
1169
|
+
|
|
1170
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
1171
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
1172
|
+
|
|
1173
|
+
invalidateStateCache();
|
|
1174
|
+
const state = await deriveStateFromDb(base);
|
|
1175
|
+
|
|
1176
|
+
assert.equal(state.phase, "executing");
|
|
1177
|
+
assert.equal(state.activeTask?.id, "T02");
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
test("milestone in DB but directory missing from disk → no crash", async () => {
|
|
1181
|
+
const base = createFixtureBase();
|
|
1182
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
1183
|
+
openDatabase(dbPath);
|
|
1184
|
+
|
|
1185
|
+
insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
|
|
1186
|
+
|
|
1187
|
+
invalidateStateCache();
|
|
1188
|
+
const state = await deriveStateFromDb(base);
|
|
1189
|
+
|
|
1190
|
+
assert.ok(state.phase !== undefined, "should produce a valid phase");
|
|
1191
|
+
});
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
describe("Failure: corrupt frontmatter", () => {
|
|
1195
|
+
test("VALIDATION with broken frontmatter → stays in validating", async () => {
|
|
1196
|
+
const base = createFixtureBase();
|
|
1197
|
+
writeRoadmap(base, "M001", doneSliceRoadmap());
|
|
1198
|
+
const dir = join(base, ".gsd", "milestones", "M001");
|
|
1199
|
+
mkdirSync(dir, { recursive: true });
|
|
1200
|
+
writeFileSync(join(dir, "M001-VALIDATION.md"), [
|
|
1201
|
+
"---",
|
|
1202
|
+
"this is not: valid: yaml: {{{}}}",
|
|
1203
|
+
"---",
|
|
1204
|
+
"",
|
|
1205
|
+
"Some content.",
|
|
1206
|
+
].join("\n"));
|
|
1207
|
+
|
|
1208
|
+
invalidateStateCache();
|
|
1209
|
+
const state = await deriveState(base);
|
|
1210
|
+
|
|
1211
|
+
assert.equal(state.phase, "validating-milestone",
|
|
1212
|
+
"corrupt frontmatter should keep milestone in validating phase");
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
test("CONTEXT with broken depends_on → no crash, deps empty", async () => {
|
|
1216
|
+
const base = createFixtureBase();
|
|
1217
|
+
writeContext(base, "M001", [
|
|
1218
|
+
"---",
|
|
1219
|
+
"depends_on: {{{invalid}}}",
|
|
1220
|
+
"---",
|
|
1221
|
+
"",
|
|
1222
|
+
"# M001: Test",
|
|
1223
|
+
].join("\n"));
|
|
1224
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
1225
|
+
|
|
1226
|
+
invalidateStateCache();
|
|
1227
|
+
const state = await deriveState(base);
|
|
1228
|
+
|
|
1229
|
+
assert.ok(state.phase !== undefined, "should not crash on corrupt depends_on");
|
|
1230
|
+
// With corrupt deps, parseContextDependsOn returns [] → no blocking
|
|
1231
|
+
assert.notEqual(state.phase, "blocked",
|
|
1232
|
+
"corrupt deps should not falsely block milestone");
|
|
1233
|
+
});
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
describe("Failure: missing task plan files in DB path", () => {
|
|
1237
|
+
test("DB has tasks but no T##-PLAN.md files → planning phase", async () => {
|
|
1238
|
+
const base = createFixtureBase();
|
|
1239
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
1240
|
+
openDatabase(dbPath);
|
|
1241
|
+
|
|
1242
|
+
insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
|
|
1243
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
|
|
1244
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: Task", status: "pending" });
|
|
1245
|
+
|
|
1246
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
1247
|
+
const dir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
1248
|
+
mkdirSync(join(dir, "tasks"), { recursive: true });
|
|
1249
|
+
writeFileSync(join(dir, "S01-PLAN.md"), standardPlan());
|
|
1250
|
+
// NO T01-PLAN.md
|
|
1251
|
+
|
|
1252
|
+
invalidateStateCache();
|
|
1253
|
+
const state = await deriveStateFromDb(base);
|
|
1254
|
+
|
|
1255
|
+
assert.equal(state.phase, "planning",
|
|
1256
|
+
"missing T##-PLAN.md files should keep state in planning");
|
|
1257
|
+
});
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
describe("Failure: stale path cache", () => {
|
|
1261
|
+
test("file created after cache populated → must clear path cache", async () => {
|
|
1262
|
+
const base = createFixtureBase();
|
|
1263
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
1264
|
+
|
|
1265
|
+
invalidateStateCache();
|
|
1266
|
+
clearPathCache();
|
|
1267
|
+
const state1 = await deriveState(base);
|
|
1268
|
+
assert.equal(state1.phase, "planning");
|
|
1269
|
+
|
|
1270
|
+
// Write PLAN AFTER first derivation cached paths
|
|
1271
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
1272
|
+
|
|
1273
|
+
// Without clearPathCache, stale cache may miss the new file
|
|
1274
|
+
invalidateStateCache();
|
|
1275
|
+
clearPathCache();
|
|
1276
|
+
const state2 = await deriveState(base);
|
|
1277
|
+
|
|
1278
|
+
assert.equal(state2.phase, "executing",
|
|
1279
|
+
"after cache clear, should see the new PLAN file");
|
|
1280
|
+
});
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
describe("Failure: blocker detection edge cases", () => {
|
|
1284
|
+
test("filesystem: blocker in SUMMARY but task not marked [x] → still detected", async () => {
|
|
1285
|
+
const base = createFixtureBase();
|
|
1286
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
1287
|
+
// T01 marked done in plan, T02 pending
|
|
1288
|
+
writePlan(base, "M001", "S01", partialDonePlan());
|
|
1289
|
+
// T01 SUMMARY has blocker_discovered in frontmatter
|
|
1290
|
+
writeTaskSummaryWithBlocker(base, "M001", "S01", "T01");
|
|
1291
|
+
|
|
1292
|
+
invalidateStateCache();
|
|
1293
|
+
clearPathCache();
|
|
1294
|
+
const state = await deriveState(base);
|
|
1295
|
+
|
|
1296
|
+
assert.equal(state.phase, "replanning-slice",
|
|
1297
|
+
"blocker_discovered in SUMMARY frontmatter should trigger replanning");
|
|
1298
|
+
});
|
|
1299
|
+
});
|
|
1300
|
+
|
|
1301
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1302
|
+
// FAILURE AT EVERY PHASE: What breaks mid-transition
|
|
1303
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1304
|
+
|
|
1305
|
+
describe("Failure at pre-planning: CONTEXT file half-written", () => {
|
|
1306
|
+
test("CONTEXT exists but is garbage → still enters pre-planning (no roadmap)", async () => {
|
|
1307
|
+
const base = createFixtureBase();
|
|
1308
|
+
writeContext(base, "M001", "\x00\x00\x00binary garbage\xff\xfe");
|
|
1309
|
+
invalidateStateCache();
|
|
1310
|
+
clearPathCache();
|
|
1311
|
+
const state = await deriveState(base);
|
|
1312
|
+
|
|
1313
|
+
// File exists so milestone is not ghost, but no roadmap → pre-planning
|
|
1314
|
+
assert.equal(state.phase, "pre-planning");
|
|
1315
|
+
assert.ok(state.activeMilestone !== null);
|
|
1316
|
+
});
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
describe("Failure at needs-discussion: CONTEXT-DRAFT is empty", () => {
|
|
1320
|
+
test("0-byte CONTEXT-DRAFT → should still trigger needs-discussion", async () => {
|
|
1321
|
+
const base = createFixtureBase();
|
|
1322
|
+
const dir = join(base, ".gsd", "milestones", "M001");
|
|
1323
|
+
mkdirSync(dir, { recursive: true });
|
|
1324
|
+
writeFileSync(join(dir, "M001-CONTEXT-DRAFT.md"), "");
|
|
1325
|
+
invalidateStateCache();
|
|
1326
|
+
clearPathCache();
|
|
1327
|
+
const state = await deriveState(base);
|
|
1328
|
+
|
|
1329
|
+
// File exists (even empty) → not a ghost, has draft → needs-discussion
|
|
1330
|
+
assert.equal(state.phase, "needs-discussion",
|
|
1331
|
+
"0-byte draft should still trigger discussion phase");
|
|
1332
|
+
});
|
|
1333
|
+
});
|
|
1334
|
+
|
|
1335
|
+
describe("Failure at planning: ROADMAP exists but is unparseable", () => {
|
|
1336
|
+
test("ROADMAP with no slices section → pre-planning (zero slices)", async () => {
|
|
1337
|
+
const base = createFixtureBase();
|
|
1338
|
+
writeRoadmap(base, "M001", "# M001: Test\n\nJust some text, no ## Slices section.");
|
|
1339
|
+
invalidateStateCache();
|
|
1340
|
+
clearPathCache();
|
|
1341
|
+
const state = await deriveState(base);
|
|
1342
|
+
|
|
1343
|
+
// parseRoadmap finds no slices → empty array → pre-planning
|
|
1344
|
+
assert.equal(state.phase, "pre-planning",
|
|
1345
|
+
"unparseable roadmap with no slices should fall to pre-planning");
|
|
1346
|
+
});
|
|
1347
|
+
|
|
1348
|
+
test("ROADMAP with broken slice syntax → treats as zero slices", async () => {
|
|
1349
|
+
const base = createFixtureBase();
|
|
1350
|
+
writeRoadmap(base, "M001", [
|
|
1351
|
+
"# M001: Test",
|
|
1352
|
+
"",
|
|
1353
|
+
"**Vision:** Test.",
|
|
1354
|
+
"",
|
|
1355
|
+
"## Slices",
|
|
1356
|
+
"",
|
|
1357
|
+
"This is not a valid slice entry at all.",
|
|
1358
|
+
"Neither is this.",
|
|
1359
|
+
].join("\n"));
|
|
1360
|
+
invalidateStateCache();
|
|
1361
|
+
clearPathCache();
|
|
1362
|
+
const state = await deriveState(base);
|
|
1363
|
+
|
|
1364
|
+
// No parseable slice entries → zero slices → pre-planning
|
|
1365
|
+
assert.equal(state.phase, "pre-planning",
|
|
1366
|
+
"broken slice syntax should result in zero slices");
|
|
1367
|
+
});
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
describe("Failure at planning: PLAN file is corrupt", () => {
|
|
1371
|
+
test("PLAN exists but tasks section is garbage → zero tasks → planning", async () => {
|
|
1372
|
+
const base = createFixtureBase();
|
|
1373
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
1374
|
+
const dir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
1375
|
+
mkdirSync(dir, { recursive: true });
|
|
1376
|
+
writeFileSync(join(dir, "S01-PLAN.md"), [
|
|
1377
|
+
"# S01: Slice",
|
|
1378
|
+
"",
|
|
1379
|
+
"## Tasks",
|
|
1380
|
+
"",
|
|
1381
|
+
"random garbage with no task markers",
|
|
1382
|
+
"more garbage",
|
|
1383
|
+
].join("\n"));
|
|
1384
|
+
invalidateStateCache();
|
|
1385
|
+
clearPathCache();
|
|
1386
|
+
const state = await deriveState(base);
|
|
1387
|
+
|
|
1388
|
+
assert.equal(state.phase, "planning",
|
|
1389
|
+
"PLAN with unparseable tasks should stay in planning");
|
|
1390
|
+
});
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
describe("Failure at executing: task plan file is empty", () => {
|
|
1394
|
+
test("T01-PLAN.md exists but is 0-byte → still enters executing", async () => {
|
|
1395
|
+
const base = createFixtureBase();
|
|
1396
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
1397
|
+
const dir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
1398
|
+
const tasksDir = join(dir, "tasks");
|
|
1399
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
1400
|
+
writeFileSync(join(dir, "S01-PLAN.md"), standardPlan());
|
|
1401
|
+
// Create task plan files but make them 0-byte
|
|
1402
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "");
|
|
1403
|
+
writeFileSync(join(tasksDir, "T02-PLAN.md"), "");
|
|
1404
|
+
invalidateStateCache();
|
|
1405
|
+
clearPathCache();
|
|
1406
|
+
const state = await deriveState(base);
|
|
1407
|
+
|
|
1408
|
+
// Task plan file existence check at line 718-730 uses readdirSync
|
|
1409
|
+
// to count .md files. 0-byte files still count.
|
|
1410
|
+
assert.equal(state.phase, "executing",
|
|
1411
|
+
"0-byte task plan files still pass the existence check");
|
|
1412
|
+
});
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
describe("Failure at executing: DB has task but wrong status string", () => {
|
|
1416
|
+
test("task with unexpected status string → not treated as closed", async () => {
|
|
1417
|
+
const base = createFixtureBase();
|
|
1418
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
1419
|
+
openDatabase(dbPath);
|
|
1420
|
+
|
|
1421
|
+
insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
|
|
1422
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
|
|
1423
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01: Task", status: "pending" });
|
|
1424
|
+
|
|
1425
|
+
// Set a garbage status that isn't "complete" or "done"
|
|
1426
|
+
updateTaskStatus("M001", "S01", "T01", "finished");
|
|
1427
|
+
|
|
1428
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
1429
|
+
writePlan(base, "M001", "S01", standardPlan());
|
|
1430
|
+
|
|
1431
|
+
invalidateStateCache();
|
|
1432
|
+
const state = await deriveStateFromDb(base);
|
|
1433
|
+
|
|
1434
|
+
// isClosedStatus("finished") → false → task treated as active
|
|
1435
|
+
assert.equal(state.phase, "executing");
|
|
1436
|
+
assert.equal(state.activeTask?.id, "T01",
|
|
1437
|
+
"non-standard status 'finished' is NOT treated as closed");
|
|
1438
|
+
});
|
|
1439
|
+
});
|
|
1440
|
+
|
|
1441
|
+
describe("Failure at summarizing: slice SUMMARY write fails (file missing)", () => {
|
|
1442
|
+
test("all tasks [x] but no slice SUMMARY → stays in summarizing", async () => {
|
|
1443
|
+
const base = createFixtureBase();
|
|
1444
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
1445
|
+
writePlan(base, "M001", "S01", allDonePlan());
|
|
1446
|
+
// All tasks done but no S01-SUMMARY.md written
|
|
1447
|
+
invalidateStateCache();
|
|
1448
|
+
clearPathCache();
|
|
1449
|
+
const state = await deriveState(base);
|
|
1450
|
+
|
|
1451
|
+
assert.equal(state.phase, "summarizing");
|
|
1452
|
+
// Next derivation still returns summarizing — no infinite loop
|
|
1453
|
+
invalidateStateCache();
|
|
1454
|
+
const state2 = await deriveState(base);
|
|
1455
|
+
assert.equal(state2.phase, "summarizing", "stays in summarizing until SUMMARY written");
|
|
1456
|
+
});
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
describe("Failure at validating-milestone: VALIDATION write crashes", () => {
|
|
1460
|
+
test("all slices done, validation never written → stuck in validating", async () => {
|
|
1461
|
+
const base = createFixtureBase();
|
|
1462
|
+
writeRoadmap(base, "M001", doneSliceRoadmap());
|
|
1463
|
+
// No VALIDATION file at all
|
|
1464
|
+
invalidateStateCache();
|
|
1465
|
+
clearPathCache();
|
|
1466
|
+
const state = await deriveState(base);
|
|
1467
|
+
assert.equal(state.phase, "validating-milestone");
|
|
1468
|
+
|
|
1469
|
+
// Call again — still validating (idempotent, not looping)
|
|
1470
|
+
invalidateStateCache();
|
|
1471
|
+
const state2 = await deriveState(base);
|
|
1472
|
+
assert.equal(state2.phase, "validating-milestone",
|
|
1473
|
+
"stays in validating until VALIDATION file appears");
|
|
1474
|
+
});
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
describe("Failure at completing-milestone: SUMMARY write fails", () => {
|
|
1478
|
+
test("validation terminal but SUMMARY never written → stuck in completing", async () => {
|
|
1479
|
+
const base = createFixtureBase();
|
|
1480
|
+
writeRoadmap(base, "M001", doneSliceRoadmap());
|
|
1481
|
+
writeMilestoneValidation(base, "M001", "pass");
|
|
1482
|
+
// No milestone SUMMARY
|
|
1483
|
+
invalidateStateCache();
|
|
1484
|
+
clearPathCache();
|
|
1485
|
+
const state = await deriveState(base);
|
|
1486
|
+
assert.equal(state.phase, "completing-milestone");
|
|
1487
|
+
|
|
1488
|
+
// Repeated calls stay in completing
|
|
1489
|
+
invalidateStateCache();
|
|
1490
|
+
const state2 = await deriveState(base);
|
|
1491
|
+
assert.equal(state2.phase, "completing-milestone",
|
|
1492
|
+
"stays in completing until SUMMARY written");
|
|
1493
|
+
});
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
describe("Failure at replanning: REPLAN.md never written (loop risk)", () => {
|
|
1497
|
+
test("blocker detected, replan dispatched but REPLAN.md not created → re-enters replanning", async () => {
|
|
1498
|
+
const base = createFixtureBase();
|
|
1499
|
+
writeRoadmap(base, "M001", standardRoadmap());
|
|
1500
|
+
writePlan(base, "M001", "S01", partialDonePlan());
|
|
1501
|
+
writeTaskSummaryWithBlocker(base, "M001", "S01", "T01");
|
|
1502
|
+
// No REPLAN.md — simulates failed replan execution
|
|
1503
|
+
|
|
1504
|
+
invalidateStateCache();
|
|
1505
|
+
clearPathCache();
|
|
1506
|
+
const state1 = await deriveState(base);
|
|
1507
|
+
assert.equal(state1.phase, "replanning-slice");
|
|
1508
|
+
|
|
1509
|
+
// Call again — same result, stuck in replanning until REPLAN.md appears
|
|
1510
|
+
invalidateStateCache();
|
|
1511
|
+
const state2 = await deriveState(base);
|
|
1512
|
+
assert.equal(state2.phase, "replanning-slice",
|
|
1513
|
+
"without REPLAN.md, state stays in replanning (dispatch will retry)");
|
|
1514
|
+
});
|
|
1515
|
+
});
|
|
1516
|
+
|
|
1517
|
+
describe("Failure at complete: SUMMARY exists but VALIDATION missing", () => {
|
|
1518
|
+
test("milestone SUMMARY without VALIDATION → still complete (SUMMARY is terminal artifact)", async () => {
|
|
1519
|
+
const base = createFixtureBase();
|
|
1520
|
+
writeRoadmap(base, "M001", doneSliceRoadmap());
|
|
1521
|
+
// SUMMARY exists but NO VALIDATION
|
|
1522
|
+
writeMilestoneSummary(base, "M001");
|
|
1523
|
+
invalidateStateCache();
|
|
1524
|
+
clearPathCache();
|
|
1525
|
+
const state = await deriveState(base);
|
|
1526
|
+
|
|
1527
|
+
// Per #864: SUMMARY is the terminal artifact, validation optional
|
|
1528
|
+
assert.equal(state.phase, "complete",
|
|
1529
|
+
"SUMMARY alone should mark milestone complete per #864");
|
|
1530
|
+
});
|
|
1531
|
+
});
|
|
1532
|
+
|
|
1533
|
+
describe("Failure at blocked: dependency milestone partially complete", () => {
|
|
1534
|
+
test("M001 has slices done but no SUMMARY → M002 (depends on M001) is blocked", async () => {
|
|
1535
|
+
const base = createFixtureBase();
|
|
1536
|
+
// M001: all slices done but no SUMMARY/VALIDATION
|
|
1537
|
+
writeRoadmap(base, "M001", doneSliceRoadmap());
|
|
1538
|
+
// M001 has no SUMMARY → it's in validating/completing, NOT complete
|
|
1539
|
+
|
|
1540
|
+
// M002: depends on M001
|
|
1541
|
+
writeContext(base, "M002", [
|
|
1542
|
+
"---",
|
|
1543
|
+
"depends_on:",
|
|
1544
|
+
" - M001",
|
|
1545
|
+
"---",
|
|
1546
|
+
"",
|
|
1547
|
+
"# M002: Dependent",
|
|
1548
|
+
].join("\n"));
|
|
1549
|
+
writeRoadmap(base, "M002", [
|
|
1550
|
+
"# M002: Dependent",
|
|
1551
|
+
"",
|
|
1552
|
+
"**Vision:** Test.",
|
|
1553
|
+
"",
|
|
1554
|
+
"## Slices",
|
|
1555
|
+
"",
|
|
1556
|
+
"- [ ] **S01: Slice** `risk:low` `depends:[]`",
|
|
1557
|
+
" > After this: done.",
|
|
1558
|
+
].join("\n"));
|
|
1559
|
+
|
|
1560
|
+
invalidateStateCache();
|
|
1561
|
+
clearPathCache();
|
|
1562
|
+
const state = await deriveState(base);
|
|
1563
|
+
|
|
1564
|
+
// M001 is active (not yet complete), M002 should wait
|
|
1565
|
+
assert.equal(state.activeMilestone?.id, "M001",
|
|
1566
|
+
"M001 should be active (not complete without SUMMARY)");
|
|
1567
|
+
assert.notEqual(state.activeMilestone?.id, "M002",
|
|
1568
|
+
"M002 should not be active while M001 is incomplete");
|
|
1569
|
+
});
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
describe("Failure: multiple reconciliation in single derivation", () => {
|
|
1573
|
+
test("DB has 3 stale tasks, all with SUMMARY on disk → all reconciled in one pass", async () => {
|
|
1574
|
+
const base = createFixtureBase();
|
|
1575
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
1576
|
+
openDatabase(dbPath);
|
|
1577
|
+
|
|
1578
|
+
insertMilestone({ id: "M001", title: "M001: Test", status: "active" });
|
|
1579
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "S01: Slice", status: "active", depends: [] });
|
|
1580
|
+
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "T01", status: "pending" });
|
|
1581
|
+
insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", title: "T02", status: "in-progress" });
|
|
1582
|
+
insertTask({ id: "T03", sliceId: "S01", milestoneId: "M001", title: "T03", status: "pending" });
|
|
1583
|
+
|
|
1584
|
+
const threeTaskRoadmap = [
|
|
1585
|
+
"# M001: Test",
|
|
1586
|
+
"",
|
|
1587
|
+
"**Vision:** Test.",
|
|
1588
|
+
"",
|
|
1589
|
+
"## Slices",
|
|
1590
|
+
"",
|
|
1591
|
+
"- [ ] **S01: Slice** `risk:low` `depends:[]`",
|
|
1592
|
+
" > After this: done.",
|
|
1593
|
+
].join("\n");
|
|
1594
|
+
writeRoadmap(base, "M001", threeTaskRoadmap);
|
|
1595
|
+
|
|
1596
|
+
const threeTaskPlan = [
|
|
1597
|
+
"# S01: Slice",
|
|
1598
|
+
"",
|
|
1599
|
+
"**Goal:** Test.",
|
|
1600
|
+
"**Demo:** Tests pass.",
|
|
1601
|
+
"",
|
|
1602
|
+
"## Tasks",
|
|
1603
|
+
"",
|
|
1604
|
+
"- [ ] **T01: First** `est:10m`",
|
|
1605
|
+
" First.",
|
|
1606
|
+
"",
|
|
1607
|
+
"- [ ] **T02: Second** `est:10m`",
|
|
1608
|
+
" Second.",
|
|
1609
|
+
"",
|
|
1610
|
+
"- [ ] **T03: Third** `est:10m`",
|
|
1611
|
+
" Third.",
|
|
1612
|
+
].join("\n");
|
|
1613
|
+
writePlan(base, "M001", "S01", threeTaskPlan);
|
|
1614
|
+
|
|
1615
|
+
// All 3 tasks have SUMMARY on disk
|
|
1616
|
+
writeTaskSummary(base, "M001", "S01", "T01");
|
|
1617
|
+
writeTaskSummary(base, "M001", "S01", "T02");
|
|
1618
|
+
writeTaskSummary(base, "M001", "S01", "T03");
|
|
1619
|
+
|
|
1620
|
+
invalidateStateCache();
|
|
1621
|
+
const state = await deriveStateFromDb(base);
|
|
1622
|
+
|
|
1623
|
+
// All 3 should be reconciled in one pass → summarizing
|
|
1624
|
+
assert.equal(state.phase, "summarizing",
|
|
1625
|
+
"all 3 stale tasks should be reconciled to complete in one derivation");
|
|
1626
|
+
});
|
|
1627
|
+
});
|
|
1628
|
+
});
|