@opengsd/gsd-pi 1.1.1-dev.b2556262 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/project-sessions.js +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +17 -9
- package/dist/resources/extensions/gsd/auto/contracts.js +8 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +659 -57
- package/dist/resources/extensions/gsd/auto-prompts.js +110 -1
- package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +5 -0
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +29 -0
- package/dist/resources/extensions/gsd/auto.js +62 -464
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +4 -1
- package/dist/resources/extensions/gsd/debug-logger.js +10 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +7 -2
- package/dist/resources/extensions/gsd/guided-flow.js +2 -2
- package/dist/resources/extensions/gsd/markdown-renderer.js +31 -32
- package/dist/resources/extensions/gsd/mcp-filter.js +6 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +9 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +6 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +3 -5
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +1 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -6
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +2 -2
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +5 -3
- package/dist/resources/extensions/gsd/schemas/parsers.js +6 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +21 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +169 -20
- package/dist/resources/extensions/gsd/user-input-boundary.js +42 -4
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/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 +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +1 -1
- 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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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/mcp-connections/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +1 -1
- 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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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 +1 -1
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- 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.js.nft.json +1 -1
- 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.js.nft.json +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.js.nft.json +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 +4 -4
- 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 +4 -4
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- 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 +6 -6
- package/dist/web/standalone/.next/server/chunks/2842.js +4 -4
- package/dist/web/standalone/.next/server/chunks/5047.js +2 -0
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -0
- package/dist/web/standalone/.next/server/chunks/8357.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +3 -3
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/app/_not-found/page-49f565245e1e4afe.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/{layout-4ae2d68984392bbf.js → layout-b35cbfff38aaf4cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-a48b7c48333b31c8.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{main-app-90d1d8d5e5d2dc6b.js → main-app-590a74400e35f685.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ec3eaffc9785ba48.js +1 -0
- package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- 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/server.js +1 -1
- package/package.json +8 -6
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +0 -34
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +0 -34
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +11 -3
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +18 -8
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +2 -2
- package/src/resources/extensions/gsd/auto/contracts.ts +8 -119
- package/src/resources/extensions/gsd/auto/orchestrator.ts +794 -58
- package/src/resources/extensions/gsd/auto-prompts.ts +114 -1
- package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -0
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +5 -0
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +33 -0
- package/src/resources/extensions/gsd/auto.ts +81 -500
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +4 -0
- package/src/resources/extensions/gsd/debug-logger.ts +11 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +8 -2
- package/src/resources/extensions/gsd/guided-flow.ts +2 -2
- package/src/resources/extensions/gsd/markdown-renderer.ts +38 -19
- package/src/resources/extensions/gsd/mcp-filter.ts +7 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +9 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +6 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +3 -5
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +1 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -6
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +2 -2
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +5 -3
- package/src/resources/extensions/gsd/schemas/parsers.ts +6 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +31 -10
- package/src/resources/extensions/gsd/tests/artifact-db-drift-memo.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/auto-dispatch-baseline-harness.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +590 -855
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +38 -10
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/execute-summary-save-empty-project.test.ts +64 -1
- package/src/resources/extensions/gsd/tests/markdown-renderer-parse-cache.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/orchestrator-legacy-parity.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/parse-project-milestone-bridge.test.ts +77 -0
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +29 -2
- package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +19 -5
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -3
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +183 -21
- package/src/resources/extensions/gsd/user-input-boundary.ts +37 -5
- package/dist/web/standalone/.next/server/chunks/678.js +0 -2
- package/dist/web/standalone/.next/static/chunks/app/_not-found/page-a6fb1847f67f167c.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/page-6644fc6ee8ca1247.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-1115d46ac61b9823.js +0 -1
- /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → TA5o9SHAnCdK6Umm1MYxb}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → TA5o9SHAnCdK6Umm1MYxb}/_ssgManifest.js +0 -0
|
@@ -21,6 +21,14 @@ function runGit(args: string[], cwd: string): void {
|
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function renderOutcomeWidget(widget: unknown): string {
|
|
25
|
+
const component = (widget as any)(
|
|
26
|
+
{ requestRender() {} },
|
|
27
|
+
{ fg: (_color: string, text: string) => text, bold: (text: string) => text },
|
|
28
|
+
);
|
|
29
|
+
return component.render(100).join("\n");
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
test("cleanupAfterLoopExit preserves paused auto badge after provider pause", async () => {
|
|
25
33
|
const base = mkdtempSync(join(tmpdir(), "gsd-paused-cleanup-"));
|
|
26
34
|
const previousCwd = process.cwd();
|
|
@@ -167,13 +175,17 @@ test("cleanupAfterLoopExit preserves completion closeout surface after stopAuto
|
|
|
167
175
|
false,
|
|
168
176
|
"completion cleanup must not replace the closeout surface with a generic outcome card",
|
|
169
177
|
);
|
|
170
|
-
assert.equal(
|
|
178
|
+
assert.equal(
|
|
179
|
+
autoSession.completionStopInProgress,
|
|
180
|
+
true,
|
|
181
|
+
"completion closeout preservation must survive post-loop cleanup until the next agent turn",
|
|
182
|
+
);
|
|
171
183
|
} finally {
|
|
172
184
|
autoSession.reset();
|
|
173
185
|
}
|
|
174
186
|
});
|
|
175
187
|
|
|
176
|
-
test("cleanupAfterLoopExit
|
|
188
|
+
test("cleanupAfterLoopExit preserves completionStopInProgress even when preserveStepSurfaceAfterLoopExit is also set", async () => {
|
|
177
189
|
const statusCalls: unknown[] = [];
|
|
178
190
|
const widgetCalls: unknown[] = [];
|
|
179
191
|
|
|
@@ -196,8 +208,8 @@ test("cleanupAfterLoopExit clears completionStopInProgress even when preserveSte
|
|
|
196
208
|
|
|
197
209
|
assert.equal(
|
|
198
210
|
autoSession.completionStopInProgress,
|
|
199
|
-
|
|
200
|
-
"completionStopInProgress must
|
|
211
|
+
true,
|
|
212
|
+
"completionStopInProgress must survive cleanup even when preserveStepSurfaceAfterLoopExit was also set",
|
|
201
213
|
);
|
|
202
214
|
assert.equal(autoSession.preserveStepSurfaceAfterLoopExit, false);
|
|
203
215
|
} finally {
|
|
@@ -641,6 +653,10 @@ test("stopAuto foreground completion closeout reroots session and preserves the
|
|
|
641
653
|
false,
|
|
642
654
|
"foreground completion stop must not install a replacement roll-up widget over the transcript",
|
|
643
655
|
);
|
|
656
|
+
assert.ok(
|
|
657
|
+
widgetCalls.some(([key, value]) => key === "gsd-progress" && value === undefined),
|
|
658
|
+
"foreground completion stop must clear stale progress controls in the rerooted session",
|
|
659
|
+
);
|
|
644
660
|
assert.ok(
|
|
645
661
|
notifications.every(message => !message.includes("/gsd auto to resume")),
|
|
646
662
|
"completion stop notification must not tell users to resume a finished auto run",
|
|
@@ -649,10 +665,15 @@ test("stopAuto foreground completion closeout reroots session and preserves the
|
|
|
649
665
|
notifications.every(message => !message.includes("Auto-mode stopped") && !message.includes("Session:") && !message.includes("Debug log written")),
|
|
650
666
|
"completion stop must not append generic stop/session/debug notifications after the report",
|
|
651
667
|
);
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
668
|
+
const outcome = widgetCalls.find(([key, value]) => key === "gsd-outcome" && typeof value === "function")?.[1];
|
|
669
|
+
assert.equal(
|
|
670
|
+
typeof outcome,
|
|
671
|
+
"function",
|
|
672
|
+
"foreground completion stop must install a durable closeout outcome in the rerooted session",
|
|
655
673
|
);
|
|
674
|
+
const rendered = renderOutcomeWidget(outcome);
|
|
675
|
+
assert.match(rendered, /Milestone M003 complete/);
|
|
676
|
+
assert.match(rendered, /Review the closeout/);
|
|
656
677
|
|
|
657
678
|
const widgetCallCountBeforePostLoopCleanup = widgetCalls.length;
|
|
658
679
|
await cleanupAfterLoopExit(ctx);
|
|
@@ -822,7 +843,7 @@ test("stopAuto closeout-transcript preservation suppresses generic stop widgets"
|
|
|
822
843
|
}
|
|
823
844
|
});
|
|
824
845
|
|
|
825
|
-
test("stopAuto foreground all-complete closeout
|
|
846
|
+
test("stopAuto foreground all-complete closeout installs a durable terminal outcome", async () => {
|
|
826
847
|
const base = mkdtempSync(join(tmpdir(), "gsd-all-complete-closeout-"));
|
|
827
848
|
const previousCwd = process.cwd();
|
|
828
849
|
const widgetCalls: Array<[string, unknown]> = [];
|
|
@@ -875,8 +896,15 @@ test("stopAuto foreground all-complete closeout preserves the transcript surface
|
|
|
875
896
|
false,
|
|
876
897
|
"foreground all-complete closeout must not replace the visible transcript with a roll-up widget",
|
|
877
898
|
);
|
|
878
|
-
const finalOutcome = widgetCalls.filter(([key]) => key === "gsd-outcome").at(-1);
|
|
879
|
-
assert.equal(
|
|
899
|
+
const finalOutcome = widgetCalls.filter(([key]) => key === "gsd-outcome").at(-1)?.[1];
|
|
900
|
+
assert.equal(
|
|
901
|
+
typeof finalOutcome,
|
|
902
|
+
"function",
|
|
903
|
+
"foreground all-complete closeout must install a durable terminal outcome",
|
|
904
|
+
);
|
|
905
|
+
const rendered = renderOutcomeWidget(finalOutcome);
|
|
906
|
+
assert.match(rendered, /All milestones complete/);
|
|
907
|
+
assert.match(rendered, /start new work/);
|
|
880
908
|
} finally {
|
|
881
909
|
try { closeDatabase(); } catch { /* noop */ }
|
|
882
910
|
autoSession.reset();
|
|
@@ -115,6 +115,21 @@ test('debugCount increments counters', () => {
|
|
|
115
115
|
assert.strictEqual(summary.dispatches, 5);
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
test('debugCount tracks gitInvocations and writeDebugSummary reports it', () => {
|
|
119
|
+
const tmp = createTempGsdDir();
|
|
120
|
+
enableDebug(tmp);
|
|
121
|
+
|
|
122
|
+
debugCount('gitInvocations');
|
|
123
|
+
debugCount('gitInvocations', 13);
|
|
124
|
+
|
|
125
|
+
const logPath = writeDebugSummary()!;
|
|
126
|
+
const lines = readLogLines(logPath);
|
|
127
|
+
|
|
128
|
+
const summary = lines.find(l => l.event === 'debug-summary') as any;
|
|
129
|
+
assert.ok(summary, 'should have debug-summary event');
|
|
130
|
+
assert.strictEqual(summary.gitInvocations, 14);
|
|
131
|
+
});
|
|
132
|
+
|
|
118
133
|
test('debugPeak tracks max values', () => {
|
|
119
134
|
const tmp = createTempGsdDir();
|
|
120
135
|
enableDebug(tmp);
|
|
@@ -6,7 +6,8 @@ import { join } from "node:path";
|
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
|
|
9
|
-
import { openDatabase, closeDatabase, getAllMilestones } from "../gsd-db.ts";
|
|
9
|
+
import { openDatabase, closeDatabase, getAllMilestones, getArtifact } from "../gsd-db.ts";
|
|
10
|
+
import { parseProject } from "../schemas/parsers.ts";
|
|
10
11
|
import { markApprovalGateVerified, clearDiscussionFlowState } from "../bootstrap/write-gate.ts";
|
|
11
12
|
import { executeSummarySave } from "../tools/workflow-tool-executors.ts";
|
|
12
13
|
|
|
@@ -107,3 +108,65 @@ test("executeSummarySave registers milestones when PROJECT.md uses canonical em-
|
|
|
107
108
|
assert.deepEqual(result.details.registeredMilestones, ["M001", "M002"]);
|
|
108
109
|
assert.equal(getAllMilestones().length, 2);
|
|
109
110
|
});
|
|
111
|
+
|
|
112
|
+
test("executeSummarySave self-heals the Milestone Sequence when DB already has milestones but content parses zero", async (t) => {
|
|
113
|
+
const base = setupBase(t);
|
|
114
|
+
|
|
115
|
+
// 1) First save registers M001 as complete; DB now holds the milestone with
|
|
116
|
+
// authoritative status.
|
|
117
|
+
const canonical = [
|
|
118
|
+
"# Project",
|
|
119
|
+
"",
|
|
120
|
+
"## Milestone Sequence",
|
|
121
|
+
"",
|
|
122
|
+
"<!-- Check off milestones as they complete. -->",
|
|
123
|
+
"",
|
|
124
|
+
"- [x] M001: Foo — bar",
|
|
125
|
+
"",
|
|
126
|
+
].join("\n");
|
|
127
|
+
await inProjectDir(base, () => executeSummarySave({ artifact_type: "PROJECT", content: canonical }, base));
|
|
128
|
+
assert.equal(getAllMilestones().length, 1);
|
|
129
|
+
|
|
130
|
+
// 2) A completion re-save reflows the sequence with an en-dash separator the
|
|
131
|
+
// canonical parser rejects → zero parseable lines, but the DB is intact.
|
|
132
|
+
// (A trailing non-bullet section prevents MILESTONE_LINE_RE from bridging
|
|
133
|
+
// onto a later bullet.) The checkbox here is deliberately WRONG (unchecked)
|
|
134
|
+
// to prove the rebuild takes status from the DB, not the malformed content.
|
|
135
|
+
const malformed = [
|
|
136
|
+
"# Project",
|
|
137
|
+
"",
|
|
138
|
+
"## Milestone Sequence",
|
|
139
|
+
"",
|
|
140
|
+
"<!-- Check off milestones as they complete. -->",
|
|
141
|
+
"",
|
|
142
|
+
"- [ ] M001: Foo – shipped bar", // en-dash U+2013, not accepted by MILESTONE_LINE_RE
|
|
143
|
+
"",
|
|
144
|
+
"## Notes",
|
|
145
|
+
"",
|
|
146
|
+
"Trailing prose with no list bullets.",
|
|
147
|
+
"",
|
|
148
|
+
].join("\n");
|
|
149
|
+
|
|
150
|
+
const result = await inProjectDir(base, () => executeSummarySave({ artifact_type: "PROJECT", content: malformed }, base));
|
|
151
|
+
|
|
152
|
+
// Non-fatal: the save succeeds and reports the self-heal.
|
|
153
|
+
assert.notEqual(result.isError, true);
|
|
154
|
+
assert.equal(result.details.milestoneSequenceSelfHealed, true);
|
|
155
|
+
assert.deepEqual(result.details.registeredMilestones, ["M001"]);
|
|
156
|
+
assert.equal(getAllMilestones().length, 1);
|
|
157
|
+
|
|
158
|
+
// The persisted PROJECT.md now parses canonically and preserves status + prose.
|
|
159
|
+
const persisted = getArtifact("PROJECT.md");
|
|
160
|
+
assert.ok(persisted);
|
|
161
|
+
const parsed = parseProject(persisted!.full_content);
|
|
162
|
+
assert.equal(parsed.milestones.length, 1);
|
|
163
|
+
const m001 = parsed.milestones.find(m => m.id === "M001");
|
|
164
|
+
assert.ok(m001);
|
|
165
|
+
// Status comes from the DB (M001 complete), not the unchecked box in the
|
|
166
|
+
// malformed content.
|
|
167
|
+
assert.equal(m001!.done, true);
|
|
168
|
+
assert.equal(m001!.title, "Foo");
|
|
169
|
+
assert.match(m001!.oneLiner, /shipped bar/);
|
|
170
|
+
// The HTML comment hint survives the rebuild.
|
|
171
|
+
assert.match(persisted!.full_content, /Check off milestones as they complete/);
|
|
172
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Performance-contract test for the detectStaleRenders file-identity
|
|
3
|
+
// parse cache (#442 Phase 1.5). Asserts the observable contract: an unchanged
|
|
4
|
+
// projection is NOT re-parsed on the next dispatch (cache hit), while a changed
|
|
5
|
+
// projection IS re-parsed (cache miss on path+mtime+size). Modeled on the
|
|
6
|
+
// prepared-statement caching contract test in db-adapter.test.ts — it asserts
|
|
7
|
+
// external behavior (parse happened / did not), never internal wiring.
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
|
|
15
|
+
import { openDatabase, closeDatabase, insertMilestone, insertSlice } from "../gsd-db.ts";
|
|
16
|
+
import { clearParseCache } from "../files.ts";
|
|
17
|
+
import { clearPathCache } from "../paths.ts";
|
|
18
|
+
import { invalidateStateCache } from "../state.ts";
|
|
19
|
+
import { detectStaleRenders } from "../markdown-renderer.ts";
|
|
20
|
+
import { enableDebug, disableDebug, getDebugCounters } from "../debug-logger.ts";
|
|
21
|
+
|
|
22
|
+
function roadmapMd(slices: Array<{ id: string; title: string; done: boolean }>): string {
|
|
23
|
+
const lines = ["# M001 Roadmap", "", "**Vision:** cache contract", "", "## Slices", ""];
|
|
24
|
+
for (const s of slices) lines.push(`- ${s.done ? "[x]" : "[ ]"} **${s.id}: ${s.title}** \`risk:medium\` \`depends:[]\``);
|
|
25
|
+
lines.push("");
|
|
26
|
+
return lines.join("\n");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test("#442: detectStaleRenders re-parses ROADMAP only when it changes on disk", (t) => {
|
|
30
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-parse-cache-"));
|
|
31
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
32
|
+
mkdirSync(milestoneDir, { recursive: true });
|
|
33
|
+
const roadmapPath = join(milestoneDir, "M001-ROADMAP.md");
|
|
34
|
+
t.after(() => {
|
|
35
|
+
try { closeDatabase(); } catch { /* noop */ }
|
|
36
|
+
disableDebug();
|
|
37
|
+
rmSync(base, { recursive: true, force: true });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
41
|
+
insertMilestone({ id: "M001", title: "Cache", status: "active" });
|
|
42
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "active", risk: "medium", depends: [], sequence: 1 });
|
|
43
|
+
|
|
44
|
+
// Start from a cold cache so counts are attributable to this test.
|
|
45
|
+
clearParseCache();
|
|
46
|
+
clearPathCache();
|
|
47
|
+
invalidateStateCache();
|
|
48
|
+
|
|
49
|
+
writeFileSync(roadmapPath, roadmapMd([{ id: "S01", title: "Slice", done: false }]));
|
|
50
|
+
|
|
51
|
+
enableDebug(base); // resets counters
|
|
52
|
+
|
|
53
|
+
// First dispatch: cold cache → the roadmap is read + parsed.
|
|
54
|
+
detectStaleRenders(base);
|
|
55
|
+
const afterCold = getDebugCounters().parseRoadmapCalls;
|
|
56
|
+
assert.ok(afterCold > 0, "cold dispatch must parse the roadmap");
|
|
57
|
+
|
|
58
|
+
// Second dispatch, file unchanged: cache hit → NO new parse.
|
|
59
|
+
detectStaleRenders(base);
|
|
60
|
+
const afterWarm = getDebugCounters().parseRoadmapCalls;
|
|
61
|
+
assert.equal(afterWarm, afterCold, "unchanged projection must not be re-parsed");
|
|
62
|
+
|
|
63
|
+
// Change the file (different content AND size) WITHOUT clearing caches:
|
|
64
|
+
// the path+mtime+size identity changes, forcing a re-parse.
|
|
65
|
+
writeFileSync(
|
|
66
|
+
roadmapPath,
|
|
67
|
+
roadmapMd([
|
|
68
|
+
{ id: "S01", title: "Slice", done: true },
|
|
69
|
+
{ id: "S02", title: "Added Slice", done: false },
|
|
70
|
+
]),
|
|
71
|
+
);
|
|
72
|
+
detectStaleRenders(base);
|
|
73
|
+
const afterChange = getDebugCounters().parseRoadmapCalls;
|
|
74
|
+
assert.ok(afterChange > afterWarm, "a changed projection must be re-parsed");
|
|
75
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: #442 Phase 3.14 — characterization battery proving the
|
|
3
|
+
// Orchestrator's dispatch DECISION matches what the legacy
|
|
4
|
+
// runPreDispatch/runDispatch path would produce, across representative
|
|
5
|
+
// resolveDispatch outcomes. This is the safety net that licenses deleting the
|
|
6
|
+
// dead legacy `else` branch in auto/loop.ts (loop never reaches it because
|
|
7
|
+
// ensureOrchestrationModule runs unconditionally before autoLoop, so
|
|
8
|
+
// s.orchestration is always set).
|
|
9
|
+
//
|
|
10
|
+
// Both paths delegate to the same resolveDispatch rule engine; the only thing
|
|
11
|
+
// that differs is how each TRANSLATES a resolveDispatch action into a loop
|
|
12
|
+
// outcome. We pin that translation equivalence here:
|
|
13
|
+
// resolveDispatch action | legacy runDispatch -> loop | orchestrator decision
|
|
14
|
+
// ------------------------|----------------------------|----------------------
|
|
15
|
+
// dispatch | IterationData(unit) | { unitType, unitId } (advanced)
|
|
16
|
+
// stop | break -> stopped/blocked | { kind: "blocked", action } | stopped
|
|
17
|
+
// skip | continue -> skipped | { kind: "skipped" }
|
|
18
|
+
// (no rule matches) | stop/no-unit | null -> stopped(no remaining)
|
|
19
|
+
|
|
20
|
+
import test from "node:test";
|
|
21
|
+
import assert from "node:assert/strict";
|
|
22
|
+
|
|
23
|
+
import { decideOrchestratorDispatch } from "../auto/orchestrator.ts";
|
|
24
|
+
import { resolveDispatch, type DispatchContext } from "../auto-dispatch.ts";
|
|
25
|
+
import { RuleRegistry, setRegistry, resetRegistry } from "../rule-registry.ts";
|
|
26
|
+
import type { UnifiedRule } from "../rule-types.ts";
|
|
27
|
+
import type { GSDState } from "../types.ts";
|
|
28
|
+
|
|
29
|
+
function makeState(): GSDState {
|
|
30
|
+
return {
|
|
31
|
+
activeMilestone: { id: "M001", title: "Milestone" },
|
|
32
|
+
activeSlice: null,
|
|
33
|
+
activeTask: null,
|
|
34
|
+
phase: "executing",
|
|
35
|
+
recentDecisions: [],
|
|
36
|
+
blockers: [],
|
|
37
|
+
nextAction: "Execute task",
|
|
38
|
+
registry: [],
|
|
39
|
+
requirements: { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 },
|
|
40
|
+
progress: { milestones: { done: 0, total: 1 } },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const fakeCtx = {
|
|
45
|
+
model: { provider: "anthropic", baseUrl: "https://api.anthropic.com", contextWindow: 200_000 },
|
|
46
|
+
modelRegistry: { getAll: () => [], getProviderAuthMode: (_p: string) => "apiKey" as const },
|
|
47
|
+
} as never;
|
|
48
|
+
const fakePi = { getActiveTools: () => ["read_file", "write_file"] } as never;
|
|
49
|
+
const BASE = "/tmp/orchestrator-legacy-parity";
|
|
50
|
+
|
|
51
|
+
function installRule(where: UnifiedRule["where"]): void {
|
|
52
|
+
setRegistry(new RuleRegistry([{
|
|
53
|
+
name: "parity-rule",
|
|
54
|
+
when: "dispatch",
|
|
55
|
+
evaluation: "first-match",
|
|
56
|
+
where,
|
|
57
|
+
then: (r: unknown) => r,
|
|
58
|
+
}]));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function directCtx(state: GSDState): DispatchContext {
|
|
62
|
+
return {
|
|
63
|
+
basePath: BASE,
|
|
64
|
+
mid: state.activeMilestone!.id,
|
|
65
|
+
midTitle: state.activeMilestone!.title,
|
|
66
|
+
state,
|
|
67
|
+
prefs: undefined,
|
|
68
|
+
structuredQuestionsAvailable: "true",
|
|
69
|
+
sessionContextWindow: 200_000,
|
|
70
|
+
sessionProvider: "anthropic",
|
|
71
|
+
modelRegistry: (fakeCtx as { modelRegistry: unknown }).modelRegistry as never,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
test("#442 characterization: dispatch action → orchestrator picks the same unit the legacy path would", async (t) => {
|
|
76
|
+
t.after(() => resetRegistry());
|
|
77
|
+
const state = makeState();
|
|
78
|
+
installRule(async () => ({ action: "dispatch", unitType: "execute-task", unitId: "T07", prompt: "p" }));
|
|
79
|
+
|
|
80
|
+
const legacy = await resolveDispatch(directCtx(state));
|
|
81
|
+
const decision = await decideOrchestratorDispatch(fakeCtx, fakePi, BASE, undefined, { stateSnapshot: state });
|
|
82
|
+
|
|
83
|
+
assert.equal((legacy as { action: string }).action, "dispatch");
|
|
84
|
+
assert.ok(decision && "unitType" in decision, "orchestrator must produce a unit decision");
|
|
85
|
+
assert.equal(decision.unitType, (legacy as { unitType: string }).unitType);
|
|
86
|
+
assert.equal(decision.unitId, (legacy as { unitId: string }).unitId);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("#442 characterization: stop action → orchestrator blocks with stop, matching legacy break", async (t) => {
|
|
90
|
+
t.after(() => resetRegistry());
|
|
91
|
+
const state = makeState();
|
|
92
|
+
installRule(async () => ({ action: "stop", reason: "milestone blocked" }));
|
|
93
|
+
|
|
94
|
+
const legacy = await resolveDispatch(directCtx(state));
|
|
95
|
+
const decision = await decideOrchestratorDispatch(fakeCtx, fakePi, BASE, undefined, { stateSnapshot: state });
|
|
96
|
+
|
|
97
|
+
assert.equal((legacy as { action: string }).action, "stop");
|
|
98
|
+
assert.ok(decision && "kind" in decision && decision.kind === "blocked", "stop must translate to a blocked decision");
|
|
99
|
+
assert.equal((decision as { action: string }).action, "stop");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("#442 characterization: skip action → orchestrator skips, matching legacy continue", async (t) => {
|
|
103
|
+
t.after(() => resetRegistry());
|
|
104
|
+
const state = makeState();
|
|
105
|
+
installRule(async () => ({ action: "skip", reason: "nothing to do this pass" }));
|
|
106
|
+
|
|
107
|
+
const legacy = await resolveDispatch(directCtx(state));
|
|
108
|
+
const decision = await decideOrchestratorDispatch(fakeCtx, fakePi, BASE, undefined, { stateSnapshot: state });
|
|
109
|
+
|
|
110
|
+
assert.equal((legacy as { action: string }).action, "skip");
|
|
111
|
+
assert.ok(decision && "kind" in decision && decision.kind === "skipped", "skip must translate to a skipped decision");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("#442 characterization: no matching rule → orchestrator yields no unit (legacy 'no remaining units')", async (t) => {
|
|
115
|
+
t.after(() => resetRegistry());
|
|
116
|
+
const state = makeState();
|
|
117
|
+
installRule(async () => null);
|
|
118
|
+
|
|
119
|
+
const legacy = await resolveDispatch(directCtx(state));
|
|
120
|
+
const decision = await decideOrchestratorDispatch(fakeCtx, fakePi, BASE, undefined, { stateSnapshot: state });
|
|
121
|
+
|
|
122
|
+
// resolveDispatch with no match yields no dispatch action; the orchestrator
|
|
123
|
+
// surfaces that as a null decision, which advance() turns into a "stopped:
|
|
124
|
+
// no remaining units" outcome — the same terminal the legacy path reaches.
|
|
125
|
+
assert.ok(legacy == null || (legacy as { action?: string }).action !== "dispatch");
|
|
126
|
+
assert.ok(decision == null || !("unitType" in decision), "no-match must not yield a unit dispatch");
|
|
127
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// gsd-pi / parseProject MILESTONE_LINE_RE bridging regression
|
|
2
|
+
//
|
|
3
|
+
// Guards against a silent data-integrity bug: MILESTONE_LINE_RE used `\s+`
|
|
4
|
+
// around its separator, and `\s` matches newlines. A milestone line lacking a
|
|
5
|
+
// valid internal separator could therefore "bridge" onto the NEXT bullet's
|
|
6
|
+
// `- `, consuming it as the separator and swallowing the following well-formed
|
|
7
|
+
// milestone. The separator gaps are now horizontal-whitespace-only so a line
|
|
8
|
+
// can never span a newline.
|
|
9
|
+
import { describe, test } from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
|
|
12
|
+
import { parseProject } from "../schemas/parsers.ts";
|
|
13
|
+
|
|
14
|
+
function milestoneSection(...lines: string[]): string {
|
|
15
|
+
return ["# Project", "", "## Milestone Sequence", "", ...lines, ""].join("\n");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("parseProject milestone bridging", () => {
|
|
19
|
+
test("a malformed milestone line does not swallow the following well-formed one", () => {
|
|
20
|
+
// M001 uses an invalid " : " separator; M002 is canonical. Before the fix
|
|
21
|
+
// this returned a SINGLE match (M001 with oneLiner "[ ] M002: Baz — qux"),
|
|
22
|
+
// dropping M002 entirely.
|
|
23
|
+
const content = milestoneSection(
|
|
24
|
+
"- [ ] M001: Foo : bar",
|
|
25
|
+
"- [ ] M002: Baz — qux",
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const { milestones } = parseProject(content);
|
|
29
|
+
|
|
30
|
+
// M002 must survive intact — it must NOT be consumed as M001's one-liner.
|
|
31
|
+
const m002 = milestones.find(m => m.id === "M002");
|
|
32
|
+
assert.ok(m002, "M002 must be registered, not swallowed by the malformed M001 line");
|
|
33
|
+
assert.equal(m002!.title, "Baz", "M002 title parsed cleanly");
|
|
34
|
+
assert.equal(m002!.oneLiner, "qux", "M002 one-liner parsed cleanly");
|
|
35
|
+
|
|
36
|
+
// No milestone may have bridged the M002 bullet into its own one-liner.
|
|
37
|
+
assert.ok(
|
|
38
|
+
!milestones.some(m => m.oneLiner.includes("M002")),
|
|
39
|
+
"no milestone may bridge across the newline and consume the M002 bullet",
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// M001 has no valid separator, so it is skipped — consistent with the
|
|
43
|
+
// empty-parse hard-fail in execute-summary-save-empty-project.test.ts.
|
|
44
|
+
// The fixture therefore yields exactly the one well-formed milestone.
|
|
45
|
+
assert.deepEqual(milestones.map(m => m.id), ["M002"], "only the well-formed milestone parses");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("two well-formed milestones on adjacent lines both parse", () => {
|
|
49
|
+
const content = milestoneSection(
|
|
50
|
+
"- [x] M001: Foo — bar",
|
|
51
|
+
"- [ ] M002: Baz — qux",
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const { milestones } = parseProject(content);
|
|
55
|
+
assert.deepEqual(
|
|
56
|
+
milestones.map(m => ({ id: m.id, title: m.title, oneLiner: m.oneLiner, done: m.done })),
|
|
57
|
+
[
|
|
58
|
+
{ id: "M001", title: "Foo", oneLiner: "bar", done: true },
|
|
59
|
+
{ id: "M002", title: "Baz", oneLiner: "qux", done: false },
|
|
60
|
+
],
|
|
61
|
+
"adjacent canonical milestone lines are parsed independently",
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("a trailing malformed line cannot bridge onto a following bullet of any kind", () => {
|
|
66
|
+
// The last milestone is malformed and is followed by a non-milestone list
|
|
67
|
+
// bullet. The malformed line must be skipped without consuming the bullet.
|
|
68
|
+
const content = milestoneSection(
|
|
69
|
+
"- [ ] M001: Foo — bar",
|
|
70
|
+
"- [ ] M002: Baz : qux",
|
|
71
|
+
"- some unrelated bullet",
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const { milestones } = parseProject(content);
|
|
75
|
+
assert.deepEqual(milestones.map(m => m.id), ["M001"], "malformed M002 is skipped, not bridged onto the unrelated bullet");
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -76,8 +76,10 @@ test("plan-slice prompt: compact planning gates survive template substitution",
|
|
|
76
76
|
assert.ok(result.includes("Bias toward \"roadmap is fine.\""), "roadmap reassessment brake should remain visible");
|
|
77
77
|
assert.ok(result.includes("Self-audit before finishing"), "self-audit gate should remain visible");
|
|
78
78
|
assert.ok(result.includes("Quality gates: non-trivial slices/tasks include specific Q3-Q7 coverage where applicable."));
|
|
79
|
-
assert.ok(result.includes("
|
|
80
|
-
assert.ok(result.includes("
|
|
79
|
+
assert.ok(result.includes("Use the inlined Output Template sections already present in this prompt."));
|
|
80
|
+
assert.ok(result.includes("Do not read template files from disk."));
|
|
81
|
+
assert.ok(!result.includes("C:\\Users\\Test\\.gsd\\agent\\extensions\\gsd\\templates\\plan.md"));
|
|
82
|
+
assert.ok(!result.includes("C:\\Users\\Test\\.gsd\\agent\\extensions\\gsd\\templates\\task-plan.md"));
|
|
81
83
|
assert.ok(!result.includes("{{templatesDir}}/plan.md"));
|
|
82
84
|
assert.ok(!result.includes("{{templatesDir}}/task-plan.md"));
|
|
83
85
|
assert.ok(!result.includes("{{"));
|
|
@@ -564,6 +564,33 @@ test("validate-milestone prompt dispatches parallel reviewers", () => {
|
|
|
564
564
|
assert.match(prompt, /assessment evidence/i);
|
|
565
565
|
});
|
|
566
566
|
|
|
567
|
+
// ─── ADR-029: forward preloaded evidence to validate reviewers ────────
|
|
568
|
+
test("validate-milestone forwards preloaded evidence to reviewers and keeps full reads on-demand", () => {
|
|
569
|
+
const prompt = readPrompt("validate-milestone");
|
|
570
|
+
// Orchestrator is told to embed the preloaded evidence into reviewer tasks.
|
|
571
|
+
assert.match(prompt, /[Ee]mbed the relevant preloaded evidence/);
|
|
572
|
+
// Each reviewer is told to use the preloaded evidence, not re-read from disk.
|
|
573
|
+
const useCount = (prompt.match(/do not re-read them from disk/g) ?? []).length;
|
|
574
|
+
assert.ok(useCount >= 3, `expected all three reviewers to use preloaded evidence, found ${useCount}`);
|
|
575
|
+
// Full reads are explicitly the on-demand exception, not the routine path.
|
|
576
|
+
assert.match(prompt, /only if (its|the) preloaded (excerpt|evidence) is missing, truncated, or inconsistent/i);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// ─── ADR-029: research-milestone grounds instead of surveying ─────────
|
|
580
|
+
test("research-milestone prompt grounds in preloaded context instead of open-ended survey", () => {
|
|
581
|
+
const prompt = readPrompt("research-milestone");
|
|
582
|
+
// Grounded-research invariant present.
|
|
583
|
+
assert.match(prompt, /do not re-survey/i);
|
|
584
|
+
assert.match(prompt, /Codebase Snapshot and Project Classification/);
|
|
585
|
+
// The old open-ended survey license is gone (no "use `scout` to build a broad map").
|
|
586
|
+
assert.doesNotMatch(prompt, /use `scout` to build a broad map/);
|
|
587
|
+
// resolve_library/docs lookups remain permitted (bounded, external).
|
|
588
|
+
assert.match(prompt, /resolve_library/);
|
|
589
|
+
// Incremental save enables resume after interruption.
|
|
590
|
+
assert.match(prompt, /Save \*\*incrementally\*\*/);
|
|
591
|
+
assert.match(prompt, /Resume — Prior Partial Research/);
|
|
592
|
+
});
|
|
593
|
+
|
|
567
594
|
// ─── Prompt migration: replan-slice → gsd_replan_slice ────────────────
|
|
568
595
|
|
|
569
596
|
test("replan-slice prompt names gsd_replan_slice as the tool to use", () => {
|
|
@@ -700,8 +727,8 @@ test("guided-discuss prompts require 3-or-4 options plus Other-let-me-discuss in
|
|
|
700
727
|
);
|
|
701
728
|
assert.match(
|
|
702
729
|
prompt,
|
|
703
|
-
/grounded in (the |your |)investigation/i,
|
|
704
|
-
`${name} must require options grounded in prior investigation`,
|
|
730
|
+
/grounded in (the |your |)(investigation|preloaded context)/i,
|
|
731
|
+
`${name} must require options grounded in the preloaded context / prior investigation`,
|
|
705
732
|
);
|
|
706
733
|
}
|
|
707
734
|
});
|
|
@@ -130,3 +130,68 @@ test("buildResearchMilestonePrompt keeps broad project docs on-demand", async (t
|
|
|
130
130
|
assert.match(prompt, /`\.gsd\/REQUIREMENTS\.md`/);
|
|
131
131
|
assert.match(prompt, /`\.gsd\/DECISIONS\.md`/);
|
|
132
132
|
});
|
|
133
|
+
|
|
134
|
+
// ─── ADR-029: preload-authoritative research grounding ────────────────
|
|
135
|
+
|
|
136
|
+
test("ADR-029: research-milestone inlines project classification + codebase snapshot", async (t) => {
|
|
137
|
+
const base = makeBase();
|
|
138
|
+
t.after(() => cleanup(base));
|
|
139
|
+
invalidateAllCaches();
|
|
140
|
+
|
|
141
|
+
seed(base, "M001");
|
|
142
|
+
writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md"), "# M001 Context\n");
|
|
143
|
+
// Give the codebase scan something real to sample.
|
|
144
|
+
writeFileSync(join(base, "index.html"), "<!doctype html><html><body><h1>hi</h1></body></html>\n");
|
|
145
|
+
writeFileSync(join(base, "script.js"), "const x = 1;\nasync function go() { await x; }\n");
|
|
146
|
+
|
|
147
|
+
const prompt = await buildResearchMilestonePrompt("M001", "Research Test", base);
|
|
148
|
+
|
|
149
|
+
// Project-size signal (same block plan-milestone gets).
|
|
150
|
+
assert.match(prompt, /### Project Classification/);
|
|
151
|
+
assert.match(prompt, /Workflow sizing:/);
|
|
152
|
+
// Bounded codebase snapshot grounds research in current code reality.
|
|
153
|
+
assert.match(prompt, /### Codebase Snapshot \(current code reality\)/);
|
|
154
|
+
assert.match(prompt, /do NOT re-survey the tree/);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("ADR-029: codebase snapshot is suppressed when discuss_preparation is false", async (t) => {
|
|
158
|
+
const base = makeBase();
|
|
159
|
+
t.after(() => cleanup(base));
|
|
160
|
+
invalidateAllCaches();
|
|
161
|
+
|
|
162
|
+
seed(base, "M001");
|
|
163
|
+
writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md"), "# M001 Context\n");
|
|
164
|
+
writeFileSync(join(base, ".gsd", "PREFERENCES.md"), "---\ndiscuss_preparation: false\n---\n");
|
|
165
|
+
invalidateAllCaches();
|
|
166
|
+
|
|
167
|
+
const prompt = await buildResearchMilestonePrompt("M001", "Research Test", base);
|
|
168
|
+
|
|
169
|
+
// Snapshot opt-out honored; classification (unconditional) still present.
|
|
170
|
+
assert.doesNotMatch(prompt, /### Codebase Snapshot/);
|
|
171
|
+
assert.match(prompt, /### Project Classification/);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("ADR-029: prior partial RESEARCH is inlined as a resume block; absent otherwise", async (t) => {
|
|
175
|
+
const base = makeBase();
|
|
176
|
+
t.after(() => cleanup(base));
|
|
177
|
+
invalidateAllCaches();
|
|
178
|
+
|
|
179
|
+
seed(base, "M001");
|
|
180
|
+
writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md"), "# M001 Context\n");
|
|
181
|
+
|
|
182
|
+
// No prior research yet → no resume block. (Match the rendered block heading,
|
|
183
|
+
// not the bare phrase — step 8 of the template references the phrase by name.)
|
|
184
|
+
const before = await buildResearchMilestonePrompt("M001", "Research Test", base);
|
|
185
|
+
assert.doesNotMatch(before, /### Resume — Prior Partial Research \(continue, do not redo\)/);
|
|
186
|
+
|
|
187
|
+
// A partial RESEARCH draft from a prior (interrupted) attempt.
|
|
188
|
+
writeFileSync(
|
|
189
|
+
join(base, ".gsd", "milestones", "M001", "M001-RESEARCH.md"),
|
|
190
|
+
"# Research\n\n## Findings\n\nPartial finding: the app is a static site.\n",
|
|
191
|
+
);
|
|
192
|
+
invalidateAllCaches();
|
|
193
|
+
|
|
194
|
+
const after = await buildResearchMilestonePrompt("M001", "Research Test", base);
|
|
195
|
+
assert.match(after, /Resume — Prior Partial Research \(continue, do not redo\)/);
|
|
196
|
+
assert.match(after, /Partial finding: the app is a static site/);
|
|
197
|
+
});
|
|
@@ -3,7 +3,10 @@ import assert from "node:assert/strict";
|
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
_resolveEffectiveUnitIsolationModeForTest,
|
|
8
|
+
_withDetachedAutoKeepaliveForTest,
|
|
9
|
+
} from "../auto.ts";
|
|
7
10
|
import {
|
|
8
11
|
_scheduleAutoStartAfterIdleForTest,
|
|
9
12
|
resolveGuidedExecuteLaunchMode,
|
|
@@ -337,17 +340,28 @@ test("resume path only hard-exits on blocked stop, not blocked pause (#6154)", (
|
|
|
337
340
|
});
|
|
338
341
|
|
|
339
342
|
test("prepareForUnit skips worktree safety when isolation is not worktree (#6154)", () => {
|
|
340
|
-
const
|
|
341
|
-
const prepareForUnitIdx =
|
|
342
|
-
const prepareForUnitBody =
|
|
343
|
+
const orchSrc = readGsdFile("auto/orchestrator.ts");
|
|
344
|
+
const prepareForUnitIdx = orchSrc.indexOf("private async prepareWorktreeForUnit(");
|
|
345
|
+
const prepareForUnitBody = orchSrc.slice(prepareForUnitIdx, orchSrc.indexOf("private classifyAndRecover(", prepareForUnitIdx));
|
|
343
346
|
|
|
344
347
|
assert.ok(prepareForUnitIdx > -1, "prepareForUnit should exist");
|
|
345
348
|
assert.ok(
|
|
346
|
-
prepareForUnitBody.includes(
|
|
349
|
+
prepareForUnitBody.includes("const isolationMode = this.getEffectiveUnitIsolationMode(this.runtimeBasePath);"),
|
|
350
|
+
"prepareForUnit should resolve the effective isolation mode once",
|
|
351
|
+
);
|
|
352
|
+
assert.ok(
|
|
353
|
+
prepareForUnitBody.includes('if (isolationMode !== "worktree")'),
|
|
347
354
|
"prepareForUnit should bypass worktree safety validation outside worktree isolation mode",
|
|
348
355
|
);
|
|
349
356
|
});
|
|
350
357
|
|
|
358
|
+
test("effective unit isolation follows degraded branch fallback", () => {
|
|
359
|
+
assert.equal(_resolveEffectiveUnitIsolationModeForTest("worktree", true), "branch");
|
|
360
|
+
assert.equal(_resolveEffectiveUnitIsolationModeForTest("worktree", false), "worktree");
|
|
361
|
+
assert.equal(_resolveEffectiveUnitIsolationModeForTest("branch", true), "branch");
|
|
362
|
+
assert.equal(_resolveEffectiveUnitIsolationModeForTest("none", true), "none");
|
|
363
|
+
});
|
|
364
|
+
|
|
351
365
|
test("discuss-to-auto handoff defaults to step mode unless explicitly disabled", () => {
|
|
352
366
|
const guidedFlowSrc = readGsdFile("guided-flow.ts");
|
|
353
367
|
const workflowSrc = readGsdFile("commands/handlers/workflow.ts");
|