gsd-pi 2.66.1-dev.ed243f2 → 2.67.0-dev.43b0159
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/claude-cli-check.d.ts +8 -0
- package/dist/claude-cli-check.js +36 -0
- package/dist/cli.js +40 -0
- package/dist/onboarding.js +19 -2
- package/dist/resources/extensions/ask-user-questions.js +79 -11
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +4 -3
- package/dist/resources/extensions/claude-code-cli/readiness.js +63 -12
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -3
- package/dist/resources/extensions/gsd/auto/loop.js +13 -1
- package/dist/resources/extensions/gsd/auto/phases.js +22 -3
- package/dist/resources/extensions/gsd/auto/run-unit.js +10 -2
- package/dist/resources/extensions/gsd/auto/session.js +1 -1
- package/dist/resources/extensions/gsd/auto-dashboard.js +65 -15
- package/dist/resources/extensions/gsd/auto-dispatch.js +30 -28
- package/dist/resources/extensions/gsd/auto-model-selection.js +12 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +173 -25
- package/dist/resources/extensions/gsd/auto-recovery.js +11 -12
- package/dist/resources/extensions/gsd/auto.js +13 -1
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +32 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +18 -6
- package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +5 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +59 -5
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +8 -5
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +186 -14
- package/dist/resources/extensions/gsd/codebase-generator.js +4 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
- package/dist/resources/extensions/gsd/commands/dispatcher.js +1 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +94 -4
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +49 -9
- package/dist/resources/extensions/gsd/context-store.js +134 -2
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -1
- package/dist/resources/extensions/gsd/detection.js +6 -0
- package/dist/resources/extensions/gsd/files.js +19 -2
- package/dist/resources/extensions/gsd/guided-flow.js +12 -8
- package/dist/resources/extensions/gsd/index.js +1 -1
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +2 -0
- package/dist/resources/extensions/gsd/parsers-legacy.js +3 -1
- package/dist/resources/extensions/gsd/preferences.js +6 -1
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -3
- package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
- package/dist/resources/extensions/gsd/prompts/discuss.md +3 -3
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
- package/dist/resources/extensions/gsd/prompts/rethink.md +6 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +11 -9
- package/dist/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +2 -1
- package/dist/resources/extensions/gsd/state.js +2 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.js +27 -26
- package/dist/resources/extensions/gsd/workflow-reconcile.js +46 -7
- package/dist/resources/extensions/remote-questions/manager.js +8 -0
- package/dist/resources/extensions/shared/interview-ui.js +10 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
- 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/react-loadable-manifest.json +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.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
- 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/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.8874bcae249c02e1.js → 6502.b804e48b7919f55e.js} +3 -3
- package/dist/web/standalone/.next/static/chunks/{webpack-9fed74684e1c5bb1.js → webpack-65f0501b197d1c49.js} +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +4 -3
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/utils/json-parse.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/json-parse.js +11 -1
- package/packages/pi-ai/dist/utils/json-parse.js.map +1 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.js +60 -1
- package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
- package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts +2 -0
- package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/tests/json-parse.test.js +14 -0
- package/packages/pi-ai/dist/utils/tests/json-parse.test.js.map +1 -0
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +10 -0
- package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic-shared.ts +4 -3
- package/packages/pi-ai/src/utils/json-parse.ts +11 -1
- package/packages/pi-ai/src/utils/repair-tool-json.ts +69 -1
- package/packages/pi-ai/src/utils/tests/json-parse.test.ts +17 -0
- package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +13 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +3 -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 +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +16 -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 +58 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js +58 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -0
- 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 +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- 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 +2 -1
- 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 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +4 -0
- package/packages/pi-coding-agent/src/core/retry-handler.test.ts +69 -0
- package/packages/pi-coding-agent/src/core/retry-handler.ts +66 -1
- package/packages/pi-coding-agent/src/core/sdk.ts +5 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +18 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +11 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +2 -2
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js +13 -0
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +35 -0
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -0
- package/packages/pi-tui/dist/__tests__/tui.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/tui.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/tui.test.js +43 -0
- package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -0
- package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/packages/pi-tui/dist/autocomplete.js +9 -7
- package/packages/pi-tui/dist/autocomplete.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts +2 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.js +54 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -0
- package/packages/pi-tui/dist/components/editor.d.ts +3 -1
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +14 -3
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.js +6 -0
- package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +8 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/__tests__/autocomplete.test.ts +15 -0
- package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +43 -0
- package/packages/pi-tui/src/__tests__/tui.test.ts +50 -0
- package/packages/pi-tui/src/autocomplete.ts +9 -7
- package/packages/pi-tui/src/components/__tests__/editor.test.ts +64 -0
- package/packages/pi-tui/src/components/editor.ts +14 -3
- package/packages/pi-tui/src/stdin-buffer.ts +7 -0
- package/packages/pi-tui/src/tui.ts +9 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/ask-user-questions.ts +103 -11
- package/src/resources/extensions/claude-code-cli/partial-builder.ts +4 -3
- package/src/resources/extensions/claude-code-cli/readiness.ts +67 -12
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +12 -3
- package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +17 -0
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +18 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -1
- package/src/resources/extensions/gsd/auto/loop.ts +14 -1
- package/src/resources/extensions/gsd/auto/phases.ts +27 -4
- package/src/resources/extensions/gsd/auto/run-unit.ts +14 -2
- package/src/resources/extensions/gsd/auto/session.ts +1 -1
- package/src/resources/extensions/gsd/auto-dashboard.ts +76 -16
- package/src/resources/extensions/gsd/auto-dispatch.ts +36 -35
- package/src/resources/extensions/gsd/auto-model-selection.ts +12 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +195 -25
- package/src/resources/extensions/gsd/auto-recovery.ts +15 -15
- package/src/resources/extensions/gsd/auto.ts +12 -1
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +27 -6
- package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +6 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +67 -6
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +11 -8
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +209 -16
- package/src/resources/extensions/gsd/codebase-generator.ts +4 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
- package/src/resources/extensions/gsd/commands/dispatcher.ts +1 -2
- package/src/resources/extensions/gsd/commands/handlers/core.ts +113 -8
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +49 -11
- package/src/resources/extensions/gsd/context-store.ts +167 -2
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +3 -1
- package/src/resources/extensions/gsd/detection.ts +6 -0
- package/src/resources/extensions/gsd/files.ts +21 -2
- package/src/resources/extensions/gsd/guided-flow.ts +15 -8
- package/src/resources/extensions/gsd/index.ts +6 -0
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +2 -0
- package/src/resources/extensions/gsd/parsers-legacy.ts +3 -1
- package/src/resources/extensions/gsd/preferences.ts +6 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
- package/src/resources/extensions/gsd/prompts/discuss.md +3 -3
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/rethink.md +6 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +11 -9
- package/src/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +4 -1
- package/src/resources/extensions/gsd/state.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +52 -1
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +50 -2
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +21 -7
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/context-store.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/decision-scope-cascade.test.ts +370 -0
- package/src/resources/extensions/gsd/tests/detection.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +53 -13
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/measurement.test.ts +531 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +3 -4
- package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +71 -2
- package/src/resources/extensions/gsd/tests/parsers.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +26 -4
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/queue-execution-guard.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +210 -35
- package/src/resources/extensions/gsd/visualizer-overlay.ts +31 -27
- package/src/resources/extensions/gsd/workflow-reconcile.ts +59 -8
- package/src/resources/extensions/remote-questions/manager.ts +9 -0
- package/src/resources/extensions/shared/interview-ui.ts +13 -0
- /package/dist/web/standalone/.next/static/{HAq0VE4k68rhRvJbQL1VW → CrKrzIIxk55witDF1eS0L}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{HAq0VE4k68rhRvJbQL1VW → CrKrzIIxk55witDF1eS0L}/_ssgManifest.js +0 -0
|
@@ -703,6 +703,31 @@ Widget description.
|
|
|
703
703
|
assert.deepStrictEqual(p.tasks[0].title, 'Build the widget', 'em-dash heading T01 title');
|
|
704
704
|
});
|
|
705
705
|
|
|
706
|
+
test('parsePlan: filename subheadings do not become task ids', () => {
|
|
707
|
+
const content = `# S15: Filename Headings
|
|
708
|
+
|
|
709
|
+
**Goal:** Ignore file-reference subheadings inside task descriptions.
|
|
710
|
+
**Demo:** Only real task ids are parsed.
|
|
711
|
+
|
|
712
|
+
## Tasks
|
|
713
|
+
|
|
714
|
+
- [ ] **T01: First task** \`est:10m\`
|
|
715
|
+
Implement the feature.
|
|
716
|
+
|
|
717
|
+
### constraints.py — \`add_off_request_tiered()\`
|
|
718
|
+
- preserve behavior
|
|
719
|
+
|
|
720
|
+
### annotations.py — \`annotate()\`
|
|
721
|
+
- keep metadata
|
|
722
|
+
`;
|
|
723
|
+
|
|
724
|
+
const p = parsePlan(content);
|
|
725
|
+
assert.deepStrictEqual(p.tasks.map((task) => task.id), ['T01'], 'filename subheadings should not create extra tasks');
|
|
726
|
+
assert.deepStrictEqual(p.tasks[0].title, 'First task', 'real task should still parse normally');
|
|
727
|
+
assert.ok(p.tasks[0].description.includes('preserve behavior'), 'detail lines under filename subheadings should remain attached to the task');
|
|
728
|
+
assert.ok(p.tasks[0].description.includes('keep metadata'), 'later detail lines should also remain attached to the task');
|
|
729
|
+
});
|
|
730
|
+
|
|
706
731
|
test('parsePlan: mixed checkbox and heading-style tasks', () => {
|
|
707
732
|
const content = `# S14: Mixed Format
|
|
708
733
|
|
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
parsePreferencesMarkdown,
|
|
18
18
|
_resetParseWarningFlag,
|
|
19
19
|
} from "../preferences.ts";
|
|
20
|
+
import { formatConfiguredModel, toPersistedModelId } from "../commands-prefs-wizard.ts";
|
|
21
|
+
import { _resetLogs, peekLogs } from "../workflow-logger.ts";
|
|
20
22
|
import type { GSDPreferences, GSDModelConfigV2, GSDPhaseModelConfig } from "../preferences.ts";
|
|
21
23
|
|
|
22
24
|
// ── Git preferences ──────────────────────────────────────────────────────────
|
|
@@ -346,6 +348,22 @@ test("handles model config with explicit provider field", () => {
|
|
|
346
348
|
assert.equal(execution.provider, "bedrock");
|
|
347
349
|
});
|
|
348
350
|
|
|
351
|
+
test("formatConfiguredModel renders provider-qualified object config", () => {
|
|
352
|
+
assert.equal(
|
|
353
|
+
formatConfiguredModel({ model: "claude-opus-4-6", provider: "bedrock" }),
|
|
354
|
+
"bedrock/claude-opus-4-6",
|
|
355
|
+
);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("toPersistedModelId prefixes provider chosen in prefs wizard", () => {
|
|
359
|
+
assert.equal(toPersistedModelId("openai", "gpt-5.4"), "openai/gpt-5.4");
|
|
360
|
+
assert.equal(
|
|
361
|
+
toPersistedModelId("openai", "openai/gpt-5.4"),
|
|
362
|
+
"openai/gpt-5.4",
|
|
363
|
+
"already-qualified IDs should be preserved",
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
|
|
349
367
|
test("handles empty models config", () => {
|
|
350
368
|
const prefs = parsePreferencesMarkdown("---\nversion: 1\n---\n");
|
|
351
369
|
assert.notEqual(prefs, null);
|
|
@@ -422,6 +440,25 @@ test("parsePreferencesMarkdown parses heading+list format without frontmatter (#
|
|
|
422
440
|
assert.deepStrictEqual(result!.git, { isolation: "none" });
|
|
423
441
|
});
|
|
424
442
|
|
|
443
|
+
test("section parse warning is emitted at most once for heading+list YAML failures (#3759)", () => {
|
|
444
|
+
_resetParseWarningFlag();
|
|
445
|
+
_resetLogs();
|
|
446
|
+
|
|
447
|
+
const content = `## Git
|
|
448
|
+
bad: [
|
|
449
|
+
`;
|
|
450
|
+
|
|
451
|
+
parsePreferencesMarkdown(content);
|
|
452
|
+
parsePreferencesMarkdown(content);
|
|
453
|
+
parsePreferencesMarkdown(content);
|
|
454
|
+
|
|
455
|
+
const warnings = peekLogs().filter((entry) => entry.component === "guided" && entry.message.includes("preferences section parse failed"));
|
|
456
|
+
assert.equal(warnings.length, 1, `expected exactly 1 guided warning, got ${warnings.length}`);
|
|
457
|
+
|
|
458
|
+
_resetParseWarningFlag();
|
|
459
|
+
_resetLogs();
|
|
460
|
+
});
|
|
461
|
+
|
|
425
462
|
// ── Experimental preferences ─────────────────────────────────────────────────
|
|
426
463
|
|
|
427
464
|
test("experimental.rtk: true is accepted and stored", () => {
|
|
@@ -51,6 +51,12 @@ test("guided discussion prompts avoid wrap-up prompts after every round", () =>
|
|
|
51
51
|
assert.doesNotMatch(slicePrompt, /I think I have a solid picture of this slice\. Ready to wrap up/i);
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
test("guided milestone discussion scopes depth verification to the milestone id", () => {
|
|
55
|
+
const prompt = readPrompt("guided-discuss-milestone");
|
|
56
|
+
assert.match(prompt, /depth_verification_\{\{milestoneId\}\}/, "depth verification id should include the milestone id");
|
|
57
|
+
assert.doesNotMatch(prompt, /depth_verification_confirm" — this enables the write-gate downstream/i, "legacy global depth gate wording should be gone");
|
|
58
|
+
});
|
|
59
|
+
|
|
54
60
|
test("guided-resume-task prompt preserves recovery state until work is superseded", () => {
|
|
55
61
|
const prompt = readPrompt("guided-resume-task");
|
|
56
62
|
assert.match(prompt, /Do \*\*not\*\* delete the continue file immediately/i);
|
|
@@ -65,11 +71,13 @@ test("execute-task prompt references gsd_complete_task tool", () => {
|
|
|
65
71
|
assert.match(prompt, /gsd_complete_task/);
|
|
66
72
|
});
|
|
67
73
|
|
|
68
|
-
test("execute-task prompt
|
|
74
|
+
test("execute-task prompt uses gsd_complete_task as canonical summary write path", () => {
|
|
69
75
|
const prompt = readPrompt("execute-task");
|
|
70
|
-
// The prompt instructs writing the summary file AND calling the tool
|
|
71
76
|
assert.match(prompt, /\{\{taskSummaryPath\}\}/);
|
|
72
77
|
assert.match(prompt, /gsd_complete_task/);
|
|
78
|
+
assert.match(prompt, /DB-backed tool is the canonical write path/i);
|
|
79
|
+
assert.match(prompt, /Do \*\*not\*\* manually write `?\{\{taskSummaryPath\}\}`?/i);
|
|
80
|
+
assert.doesNotMatch(prompt, /^\d+\.\s+Write `?\{\{taskSummaryPath\}\}`?\s*$/m);
|
|
73
81
|
});
|
|
74
82
|
|
|
75
83
|
test("execute-task prompt does not instruct LLM to toggle checkboxes manually", () => {
|
|
@@ -113,10 +121,14 @@ test("guided-complete-slice prompt references gsd_slice_complete tool", () => {
|
|
|
113
121
|
|
|
114
122
|
test("complete-slice prompt instructs writing summary and UAT files before tool call", () => {
|
|
115
123
|
const prompt = readPrompt("complete-slice");
|
|
116
|
-
// The prompt instructs writing the summary AND UAT files, then calling the tool
|
|
117
124
|
assert.match(prompt, /\{\{sliceSummaryPath\}\}/);
|
|
118
125
|
assert.match(prompt, /\{\{sliceUatPath\}\}/);
|
|
119
126
|
assert.match(prompt, /gsd_complete_slice/);
|
|
127
|
+
assert.match(prompt, /DB-backed tool is the canonical write path/i);
|
|
128
|
+
assert.match(prompt, /Do \*\*not\*\* manually write `?\{\{sliceSummaryPath\}\}`?/i);
|
|
129
|
+
assert.match(prompt, /Do \*\*not\*\* manually write `?\{\{sliceUatPath\}\}`?/i);
|
|
130
|
+
assert.doesNotMatch(prompt, /^\d+\.\s+Write `?\{\{sliceSummaryPath\}\}`?.*$/m);
|
|
131
|
+
assert.doesNotMatch(prompt, /^\d+\.\s+Write `?\{\{sliceUatPath\}\}`?.*$/m);
|
|
120
132
|
});
|
|
121
133
|
|
|
122
134
|
test("complete-slice prompt preserves decisions and knowledge review steps", () => {
|
|
@@ -125,6 +137,15 @@ test("complete-slice prompt preserves decisions and knowledge review steps", ()
|
|
|
125
137
|
assert.match(prompt, /KNOWLEDGE\.md/);
|
|
126
138
|
});
|
|
127
139
|
|
|
140
|
+
test("validate-milestone prompt uses gsd_validate_milestone as canonical validation write path", () => {
|
|
141
|
+
const prompt = readPrompt("validate-milestone");
|
|
142
|
+
assert.match(prompt, /gsd_validate_milestone/);
|
|
143
|
+
assert.match(prompt, /\{\{validationPath\}\}/);
|
|
144
|
+
assert.match(prompt, /DB-backed tool is the canonical write path/i);
|
|
145
|
+
assert.match(prompt, /Do \*\*not\*\* manually write `?\{\{validationPath\}\}`?/i);
|
|
146
|
+
assert.doesNotMatch(prompt, /Write to `?\{\{validationPath\}\}`?:/i);
|
|
147
|
+
});
|
|
148
|
+
|
|
128
149
|
test("complete-slice prompt still contains template variables for context", () => {
|
|
129
150
|
const prompt = readPrompt("complete-slice");
|
|
130
151
|
assert.match(prompt, /\{\{sliceSummaryPath\}\}/);
|
|
@@ -188,7 +209,8 @@ test("validate-milestone prompt dispatches parallel reviewers", () => {
|
|
|
188
209
|
assert.match(prompt, /Reviewer C/);
|
|
189
210
|
assert.match(prompt, /Requirements Coverage/);
|
|
190
211
|
assert.match(prompt, /Cross-Slice Integration/);
|
|
191
|
-
assert.match(prompt, /
|
|
212
|
+
assert.match(prompt, /Assessment & Acceptance Criteria/);
|
|
213
|
+
assert.match(prompt, /assessment evidence/i);
|
|
192
214
|
});
|
|
193
215
|
|
|
194
216
|
// ─── Prompt migration: replan-slice → gsd_replan_slice ────────────────
|
|
@@ -458,6 +458,66 @@ test("openai-codex-responses.ts extracts nested error fields", () => {
|
|
|
458
458
|
);
|
|
459
459
|
});
|
|
460
460
|
|
|
461
|
+
// ── Fix 1: resetTransientRetryState resets module-level singleton ────────────
|
|
462
|
+
|
|
463
|
+
test("resetTransientRetryState is exported from agent-end-recovery.ts", () => {
|
|
464
|
+
const src = readFileSync(join(__dirname, "..", "bootstrap", "agent-end-recovery.ts"), "utf-8");
|
|
465
|
+
assert.ok(
|
|
466
|
+
src.includes("export function resetTransientRetryState"),
|
|
467
|
+
"agent-end-recovery.ts must export resetTransientRetryState for provider-error-resume.ts",
|
|
468
|
+
);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test("provider-error-resume.ts calls resetTransientRetryState before startAuto", () => {
|
|
472
|
+
const src = readFileSync(join(__dirname, "..", "bootstrap", "provider-error-resume.ts"), "utf-8");
|
|
473
|
+
assert.ok(
|
|
474
|
+
src.includes("resetTransientRetryState"),
|
|
475
|
+
"provider-error-resume.ts must import and call resetTransientRetryState",
|
|
476
|
+
);
|
|
477
|
+
// Ensure reset is called BEFORE startAuto — order matters
|
|
478
|
+
const resetIdx = src.indexOf("resetTransientRetryState()");
|
|
479
|
+
const startIdx = src.indexOf("await deps.startAuto(");
|
|
480
|
+
assert.ok(
|
|
481
|
+
resetIdx !== -1 && startIdx !== -1 && resetIdx < startIdx,
|
|
482
|
+
"resetTransientRetryState() must be called before deps.startAuto()",
|
|
483
|
+
);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// ── Fix 2: Session creation timeout treated as transient in phases.ts ───────
|
|
487
|
+
|
|
488
|
+
test("phases.ts handles timeout session-creation failures with pause instead of stopAuto", () => {
|
|
489
|
+
const src = readFileSync(join(__dirname, "..", "auto", "phases.ts"), "utf-8");
|
|
490
|
+
|
|
491
|
+
// The cancelled + isTransient + category=timeout path must pause, not hard-stop
|
|
492
|
+
assert.ok(
|
|
493
|
+
src.includes('category === "timeout"'),
|
|
494
|
+
"phases.ts must check category === 'timeout' on transient cancelled unitResults",
|
|
495
|
+
);
|
|
496
|
+
// Must call pauseAuto (not stopAuto) for timeout cancellations
|
|
497
|
+
assert.ok(
|
|
498
|
+
/category === "timeout"[\s\S]{0,300}pauseAuto/.test(src),
|
|
499
|
+
"phases.ts must call pauseAuto for session-timeout failures (not stopAuto or continue)",
|
|
500
|
+
);
|
|
501
|
+
// Must NOT use action: "continue" for transient cancellations (causes infinite loops)
|
|
502
|
+
assert.ok(
|
|
503
|
+
!/isTransient[\s\S]{0,500}action:\s*"continue"/.test(src),
|
|
504
|
+
"phases.ts must NOT return action:continue for cancelled units — use break+pause instead",
|
|
505
|
+
);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
// ── Fix 3: MAX_TRANSIENT_AUTO_RESUMES raised to 8 ───────────────────────────
|
|
509
|
+
|
|
510
|
+
test("MAX_TRANSIENT_AUTO_RESUMES is at least 8 for sustained overload resilience", () => {
|
|
511
|
+
const src = readFileSync(join(__dirname, "..", "bootstrap", "agent-end-recovery.ts"), "utf-8");
|
|
512
|
+
const match = src.match(/MAX_TRANSIENT_AUTO_RESUMES\s*=\s*(\d+)/);
|
|
513
|
+
assert.ok(match, "MAX_TRANSIENT_AUTO_RESUMES must be defined");
|
|
514
|
+
const value = Number(match![1]);
|
|
515
|
+
assert.ok(
|
|
516
|
+
value >= 8,
|
|
517
|
+
`MAX_TRANSIENT_AUTO_RESUMES must be >= 8 for sustained overload resilience, got ${value}`,
|
|
518
|
+
);
|
|
519
|
+
});
|
|
520
|
+
|
|
461
521
|
// ── agent-session retryable regex handles server_error (#1166) ──────────────
|
|
462
522
|
|
|
463
523
|
test("agent-session retryable error regex matches server_error (underscore)", () => {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* (e) write/edit to source path → block
|
|
14
14
|
* (f) bash command → block (could execute work)
|
|
15
15
|
* (g) registered GSD tools (gsd_milestone_generate_id, gsd_summary_save) → pass
|
|
16
|
+
* (h) unknown custom tools → block
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
import test from 'node:test';
|
|
@@ -155,3 +156,11 @@ test('queue-guard: allows web search and library tools during queue mode', () =>
|
|
|
155
156
|
const r4 = shouldBlockQueueExecution('fetch_page', '', true);
|
|
156
157
|
assert.strictEqual(r4.block, false, 'fetch_page should pass');
|
|
157
158
|
});
|
|
159
|
+
|
|
160
|
+
// ─── Scenario 10: Unknown custom tools are blocked during queue mode ──
|
|
161
|
+
|
|
162
|
+
test('queue-guard: blocks unknown custom tools during queue mode', () => {
|
|
163
|
+
const result = shouldBlockQueueExecution('custom_codegen_tool', '', true);
|
|
164
|
+
assert.strictEqual(result.block, true, 'unknown custom tools should be blocked');
|
|
165
|
+
assert.ok(result.reason, 'should explain the queue restriction');
|
|
166
|
+
});
|
|
@@ -101,6 +101,25 @@ test("parseTaskPlanIO handles multiple backtick tokens on one line", () => {
|
|
|
101
101
|
assert.deepEqual(io.outputFiles, ["src/c.ts"]);
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
test("parseTaskPlanIO strips inline descriptions from backtick-wrapped file references", () => {
|
|
105
|
+
const content = `# T01: Described Paths
|
|
106
|
+
|
|
107
|
+
## Inputs
|
|
108
|
+
|
|
109
|
+
- \`src/config.ts — existing configuration\`
|
|
110
|
+
- \`src/flags.ts - feature flags\`
|
|
111
|
+
|
|
112
|
+
## Expected Output
|
|
113
|
+
|
|
114
|
+
- \`definitions/ac-audit.md — current state of AC CRM\`
|
|
115
|
+
- \`docs/runbook.md - update deployment notes\`
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
const io = parseTaskPlanIO(content);
|
|
119
|
+
assert.deepEqual(io.inputFiles, ["src/config.ts", "src/flags.ts"]);
|
|
120
|
+
assert.deepEqual(io.outputFiles, ["definitions/ac-audit.md", "docs/runbook.md"]);
|
|
121
|
+
});
|
|
122
|
+
|
|
104
123
|
// ─── deriveTaskGraph ──────────────────────────────────────────────────────
|
|
105
124
|
|
|
106
125
|
test("deriveTaskGraph: linear chain T01→T02→T03", () => {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdirSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
|
|
7
|
+
import { registerShortcuts } from "../bootstrap/register-shortcuts.ts";
|
|
8
|
+
|
|
9
|
+
function makeTempDir(prefix: string): string {
|
|
10
|
+
const dir = join(
|
|
11
|
+
tmpdir(),
|
|
12
|
+
`gsd-register-shortcuts-test-${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
13
|
+
);
|
|
14
|
+
mkdirSync(dir, { recursive: true });
|
|
15
|
+
return dir;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function cleanup(dir: string): void {
|
|
19
|
+
try {
|
|
20
|
+
rmSync(dir, { recursive: true, force: true });
|
|
21
|
+
} catch {
|
|
22
|
+
// best-effort
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test("dashboard shortcut resolves the project root instead of the current worktree path", async (t) => {
|
|
27
|
+
const projectRoot = makeTempDir("project");
|
|
28
|
+
const worktreeRoot = join(projectRoot, ".gsd", "worktrees", "M001");
|
|
29
|
+
mkdirSync(join(projectRoot, ".gsd"), { recursive: true });
|
|
30
|
+
mkdirSync(worktreeRoot, { recursive: true });
|
|
31
|
+
|
|
32
|
+
const originalCwd = process.cwd();
|
|
33
|
+
process.chdir(worktreeRoot);
|
|
34
|
+
t.after(() => {
|
|
35
|
+
process.chdir(originalCwd);
|
|
36
|
+
cleanup(projectRoot);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
let capturedHandler: ((ctx: any) => Promise<void>) | null = null;
|
|
40
|
+
const shortcuts: Array<{ description: string; handler: (ctx: any) => Promise<void> }> = [];
|
|
41
|
+
const pi = {
|
|
42
|
+
registerShortcut: (_key: unknown, shortcut: { description: string; handler: (ctx: any) => Promise<void> }) => {
|
|
43
|
+
shortcuts.push(shortcut);
|
|
44
|
+
if (!capturedHandler) {
|
|
45
|
+
capturedHandler = shortcut.handler;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
} as any;
|
|
49
|
+
|
|
50
|
+
registerShortcuts(pi);
|
|
51
|
+
assert.ok(capturedHandler, "dashboard shortcut is registered");
|
|
52
|
+
const dashboardShortcut = shortcuts[0];
|
|
53
|
+
assert.ok(dashboardShortcut, "dashboard shortcut is captured");
|
|
54
|
+
|
|
55
|
+
let customCalls = 0;
|
|
56
|
+
const notices: Array<{ message: string; type?: string }> = [];
|
|
57
|
+
await dashboardShortcut.handler({
|
|
58
|
+
hasUI: true,
|
|
59
|
+
ui: {
|
|
60
|
+
custom: async () => {
|
|
61
|
+
customCalls++;
|
|
62
|
+
return true;
|
|
63
|
+
},
|
|
64
|
+
notify: (message: string, type?: string) => {
|
|
65
|
+
notices.push({ message, type });
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
assert.ok(customCalls > 0, "shortcut opens the dashboard overlay when project root is resolved");
|
|
71
|
+
assert.equal(notices.length, 0, "shortcut does not fall back to the missing-.gsd warning");
|
|
72
|
+
assert.equal(shortcuts.length, 3, "all GSD shortcuts are still registered");
|
|
73
|
+
});
|
|
@@ -760,6 +760,104 @@ test("ask-user-questions source-level: tryRemoteQuestions is called before the h
|
|
|
760
760
|
);
|
|
761
761
|
});
|
|
762
762
|
|
|
763
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
764
|
+
// Race model tests (#3810) — local TUI races against remote channel
|
|
765
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
766
|
+
|
|
767
|
+
test("ask-user-questions source-level: raceRemoteAndLocal function exists", () => {
|
|
768
|
+
const src = readFileSync(
|
|
769
|
+
join(__dirname, "..", "..", "ask-user-questions.ts"),
|
|
770
|
+
"utf-8",
|
|
771
|
+
);
|
|
772
|
+
assert.ok(
|
|
773
|
+
src.includes("async function raceRemoteAndLocal("),
|
|
774
|
+
"raceRemoteAndLocal helper should exist for racing local TUI against remote channel",
|
|
775
|
+
);
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
test("ask-user-questions source-level: race path uses isRemoteConfigured for routing", () => {
|
|
779
|
+
const src = readFileSync(
|
|
780
|
+
join(__dirname, "..", "..", "ask-user-questions.ts"),
|
|
781
|
+
"utf-8",
|
|
782
|
+
);
|
|
783
|
+
assert.ok(
|
|
784
|
+
src.includes("isRemoteConfigured()"),
|
|
785
|
+
"execute() should call isRemoteConfigured() for lightweight routing decision",
|
|
786
|
+
);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
test("ask-user-questions source-level: race path checks both hasRemote and ctx.hasUI", () => {
|
|
790
|
+
// Regression: #3810 — the race should only activate when BOTH remote and local UI
|
|
791
|
+
// are available. Headless mode should still use remote-only, and no-remote should
|
|
792
|
+
// use local-only.
|
|
793
|
+
const src = readFileSync(
|
|
794
|
+
join(__dirname, "..", "..", "ask-user-questions.ts"),
|
|
795
|
+
"utf-8",
|
|
796
|
+
);
|
|
797
|
+
assert.ok(
|
|
798
|
+
src.includes("hasRemote && ctx.hasUI"),
|
|
799
|
+
"Race path should require both remote configured and local UI available",
|
|
800
|
+
);
|
|
801
|
+
assert.ok(
|
|
802
|
+
src.includes("hasRemote && !ctx.hasUI"),
|
|
803
|
+
"Headless path should handle remote-only when no local UI",
|
|
804
|
+
);
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
test("ask-user-questions source-level: race treats remote timeout as non-win", () => {
|
|
808
|
+
// Regression: the whole point of the race is that a remote timeout should NOT
|
|
809
|
+
// block the local TUI. The race helper must filter out timed_out results.
|
|
810
|
+
const src = readFileSync(
|
|
811
|
+
join(__dirname, "..", "..", "ask-user-questions.ts"),
|
|
812
|
+
"utf-8",
|
|
813
|
+
);
|
|
814
|
+
const raceFnStart = src.indexOf("async function raceRemoteAndLocal(");
|
|
815
|
+
const raceFnEnd = src.indexOf("\n}", raceFnStart);
|
|
816
|
+
const raceFnBody = src.slice(raceFnStart, raceFnEnd);
|
|
817
|
+
assert.ok(
|
|
818
|
+
raceFnBody.includes("timed_out"),
|
|
819
|
+
"raceRemoteAndLocal should check for timed_out in remote results",
|
|
820
|
+
);
|
|
821
|
+
assert.ok(
|
|
822
|
+
raceFnBody.includes("details?.error"),
|
|
823
|
+
"raceRemoteAndLocal should check for error in remote results",
|
|
824
|
+
);
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
test("ask-user-questions source-level: race uses AbortController to cancel loser", () => {
|
|
828
|
+
const src = readFileSync(
|
|
829
|
+
join(__dirname, "..", "..", "ask-user-questions.ts"),
|
|
830
|
+
"utf-8",
|
|
831
|
+
);
|
|
832
|
+
assert.ok(
|
|
833
|
+
src.includes("new AbortController()"),
|
|
834
|
+
"Race path should create an AbortController for cancellation",
|
|
835
|
+
);
|
|
836
|
+
assert.ok(
|
|
837
|
+
src.includes("controller.abort()"),
|
|
838
|
+
"raceRemoteAndLocal should abort the controller to cancel the losing side",
|
|
839
|
+
);
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
test("manager source-level: isRemoteConfigured export exists", () => {
|
|
843
|
+
const src = readFileSync(
|
|
844
|
+
join(__dirname, "..", "..", "remote-questions", "manager.ts"),
|
|
845
|
+
"utf-8",
|
|
846
|
+
);
|
|
847
|
+
assert.ok(
|
|
848
|
+
src.includes("export function isRemoteConfigured()"),
|
|
849
|
+
"manager.ts should export isRemoteConfigured for lightweight config checking",
|
|
850
|
+
);
|
|
851
|
+
// Must delegate to resolveRemoteConfig — no separate config parsing
|
|
852
|
+
const fnStart = src.indexOf("export function isRemoteConfigured()");
|
|
853
|
+
const fnEnd = src.indexOf("\n}", fnStart);
|
|
854
|
+
const fnBody = src.slice(fnStart, fnEnd);
|
|
855
|
+
assert.ok(
|
|
856
|
+
fnBody.includes("resolveRemoteConfig()"),
|
|
857
|
+
"isRemoteConfigured should delegate to resolveRemoteConfig",
|
|
858
|
+
);
|
|
859
|
+
});
|
|
860
|
+
|
|
763
861
|
test("config source-level: removeProviderToken uses auth.remove not auth.set with empty key", () => {
|
|
764
862
|
const commandSrc = readFileSync(
|
|
765
863
|
join(__dirname, "..", "..", "remote-questions", "remote-command.ts"),
|
|
@@ -6,7 +6,7 @@ import { tmpdir } from "node:os";
|
|
|
6
6
|
|
|
7
7
|
const { deriveState } = await import("../state.js");
|
|
8
8
|
|
|
9
|
-
test("deriveState reports
|
|
9
|
+
test("deriveState reports the last completed milestone when all milestone slices are done", async () => {
|
|
10
10
|
const base = mkdtempSync(join(tmpdir(), "gsd-smart-entry-complete-"));
|
|
11
11
|
|
|
12
12
|
try {
|
|
@@ -31,7 +31,7 @@ test("deriveState reports complete when all milestone slices are done", async ()
|
|
|
31
31
|
|
|
32
32
|
const state = await deriveState(base);
|
|
33
33
|
assert.equal(state.phase, "complete");
|
|
34
|
-
assert.equal(state.
|
|
34
|
+
assert.equal(state.lastCompletedMilestone?.id, "M001");
|
|
35
35
|
} finally {
|
|
36
36
|
rmSync(base, { recursive: true, force: true });
|
|
37
37
|
}
|
|
@@ -9,6 +9,7 @@ import { deriveState, isValidationTerminal } from "../state.ts";
|
|
|
9
9
|
import { resolveExpectedArtifactPath, diagnoseExpectedArtifact } from "../auto-artifact-paths.ts";
|
|
10
10
|
import { verifyExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.ts";
|
|
11
11
|
import { resolveDispatch, type DispatchContext } from "../auto-dispatch.ts";
|
|
12
|
+
import { buildValidateMilestonePrompt } from "../auto-prompts.ts";
|
|
12
13
|
import type { GSDState } from "../types.ts";
|
|
13
14
|
import { clearPathCache } from "../paths.ts";
|
|
14
15
|
import { clearParseCache } from "../files.ts";
|
|
@@ -57,6 +58,12 @@ function writeSliceSummary(base: string, mid: string, sid: string, content: stri
|
|
|
57
58
|
writeFileSync(join(dir, `${sid}-SUMMARY.md`), content);
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
function writeSliceAssessment(base: string, mid: string, sid: string, content: string): void {
|
|
62
|
+
const dir = join(base, ".gsd", "milestones", mid, "slices", sid);
|
|
63
|
+
mkdirSync(dir, { recursive: true });
|
|
64
|
+
writeFileSync(join(dir, `${sid}-ASSESSMENT.md`), content);
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
const ALL_DONE_ROADMAP = `# M001: Test Milestone
|
|
61
68
|
|
|
62
69
|
## Vision
|
|
@@ -192,6 +199,25 @@ test("deriveState returns complete when both VALIDATION and SUMMARY exist", asyn
|
|
|
192
199
|
}
|
|
193
200
|
});
|
|
194
201
|
|
|
202
|
+
test("buildValidateMilestonePrompt inlines ASSESSMENT evidence instead of UAT spec", async () => {
|
|
203
|
+
const base = makeTmpBase();
|
|
204
|
+
try {
|
|
205
|
+
writeRoadmap(base, "M001", ALL_DONE_ROADMAP);
|
|
206
|
+
const dir = join(base, ".gsd", "milestones", "M001");
|
|
207
|
+
writeFileSync(join(dir, "M001-CONTEXT.md"), CONTEXT_FILE);
|
|
208
|
+
writeSliceSummary(base, "M001", "S01", "# S01 Summary\nDelivered.");
|
|
209
|
+
writeFileSync(join(dir, "slices", "S01", "S01-UAT.md"), "# UAT Spec\nDo the thing.\n");
|
|
210
|
+
writeSliceAssessment(base, "M001", "S01", "---\nverdict: PASS\n---\n# Assessment\nEvidence captured.");
|
|
211
|
+
|
|
212
|
+
const prompt = await buildValidateMilestonePrompt("M001", "Test Milestone", base);
|
|
213
|
+
assert.match(prompt, /S01 Assessment/i, "prompt should inline assessment evidence");
|
|
214
|
+
assert.match(prompt, /verdict: PASS/i, "prompt should include the assessment verdict");
|
|
215
|
+
assert.doesNotMatch(prompt, /UAT Spec/i, "prompt should not inline the raw UAT spec as evidence");
|
|
216
|
+
} finally {
|
|
217
|
+
cleanup(base);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
195
221
|
// ─── Dispatch rule ────────────────────────────────────────────────────────
|
|
196
222
|
|
|
197
223
|
test("dispatch rule matches validating-milestone phase", async () => {
|
|
@@ -233,3 +233,62 @@ assert.ok(
|
|
|
233
233
|
overlaySrc.includes('from "../shared/mod.js"'),
|
|
234
234
|
"imports from shared barrel",
|
|
235
235
|
);
|
|
236
|
+
|
|
237
|
+
test("visualizer overlay closes on escape in filter and help submodes", async () => {
|
|
238
|
+
const mod = await import("../visualizer-overlay.js");
|
|
239
|
+
|
|
240
|
+
const mockTui = { requestRender: () => {} };
|
|
241
|
+
const mockTheme = {
|
|
242
|
+
fg: (_color: string, text: string) => text,
|
|
243
|
+
bold: (text: string) => text,
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
let closedFilter = false;
|
|
247
|
+
const filterOverlay = new mod.GSDVisualizerOverlay(
|
|
248
|
+
mockTui,
|
|
249
|
+
mockTheme as any,
|
|
250
|
+
() => { closedFilter = true; },
|
|
251
|
+
);
|
|
252
|
+
filterOverlay.filterMode = true;
|
|
253
|
+
filterOverlay.handleInput("\u0003");
|
|
254
|
+
assert.equal(closedFilter, true, "Ctrl+C closes while filter mode is active");
|
|
255
|
+
filterOverlay.dispose();
|
|
256
|
+
|
|
257
|
+
let closedHelp = false;
|
|
258
|
+
const helpOverlay = new mod.GSDVisualizerOverlay(
|
|
259
|
+
mockTui,
|
|
260
|
+
mockTheme as any,
|
|
261
|
+
() => { closedHelp = true; },
|
|
262
|
+
);
|
|
263
|
+
helpOverlay.showHelp = true;
|
|
264
|
+
helpOverlay.handleInput("\u001b");
|
|
265
|
+
assert.equal(closedHelp, true, "Escape closes while help overlay is visible");
|
|
266
|
+
helpOverlay.dispose();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("visualizer overlay tab hitboxes include rendered badges", async () => {
|
|
270
|
+
const mod = await import("../visualizer-overlay.js");
|
|
271
|
+
|
|
272
|
+
const mockTui = { requestRender: () => {} };
|
|
273
|
+
const mockTheme = {
|
|
274
|
+
fg: (_color: string, text: string) => text,
|
|
275
|
+
bold: (text: string) => text,
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const overlay = new mod.GSDVisualizerOverlay(
|
|
279
|
+
mockTui,
|
|
280
|
+
mockTheme as any,
|
|
281
|
+
() => {},
|
|
282
|
+
);
|
|
283
|
+
overlay.loading = true;
|
|
284
|
+
overlay.data = { captures: { pendingCount: 3 } } as any;
|
|
285
|
+
|
|
286
|
+
const lines = overlay.render(120);
|
|
287
|
+
const tabLine = lines.find((line: string) => line.includes("Captures") && line.includes("(3)"));
|
|
288
|
+
assert.ok(tabLine, "rendered tab bar includes captures badge");
|
|
289
|
+
const plain = tabLine!.replace(/\x1b\[[0-9;]*m/g, "");
|
|
290
|
+
const badgeColumn = plain.indexOf("(3)") + 2;
|
|
291
|
+
overlay.handleInput(`\x1b[<0;${badgeColumn};2M`);
|
|
292
|
+
assert.equal(overlay.activeTab, 8, "clicking the badge area selects the captures tab");
|
|
293
|
+
overlay.dispose();
|
|
294
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import test, { afterEach } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync, existsSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
|
|
7
|
+
import { appendEvent, readEvents } from "../workflow-events.ts";
|
|
8
|
+
import { listConflicts, reconcileWorktreeLogs, resolveConflict } from "../workflow-reconcile.ts";
|
|
9
|
+
import { closeDatabase } from "../gsd-db.ts";
|
|
10
|
+
|
|
11
|
+
const tmpDirs: string[] = [];
|
|
12
|
+
|
|
13
|
+
function makeTmpRepo(): { main: string; worktree: string } {
|
|
14
|
+
const root = mkdtempSync(join(tmpdir(), "workflow-reconcile-"));
|
|
15
|
+
const main = join(root, "main");
|
|
16
|
+
const worktree = join(root, "worktree");
|
|
17
|
+
mkdirSync(main, { recursive: true });
|
|
18
|
+
mkdirSync(worktree, { recursive: true });
|
|
19
|
+
tmpDirs.push(root);
|
|
20
|
+
return { main, worktree };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
closeDatabase();
|
|
25
|
+
for (const dir of tmpDirs) {
|
|
26
|
+
try {
|
|
27
|
+
rmSync(dir, { recursive: true, force: true });
|
|
28
|
+
} catch {
|
|
29
|
+
// Best-effort cleanup on platforms that keep files open briefly.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
tmpDirs.length = 0;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("resolveConflict(pick=main) rewrites the worktree log durably", () => {
|
|
36
|
+
const { main, worktree } = makeTmpRepo();
|
|
37
|
+
|
|
38
|
+
appendEvent(main, {
|
|
39
|
+
cmd: "plan_milestone",
|
|
40
|
+
params: { milestoneId: "M001", title: "Base Milestone" },
|
|
41
|
+
ts: "2026-01-01T00:00:00.000Z",
|
|
42
|
+
actor: "agent",
|
|
43
|
+
});
|
|
44
|
+
appendEvent(worktree, {
|
|
45
|
+
cmd: "plan_milestone",
|
|
46
|
+
params: { milestoneId: "M001", title: "Base Milestone" },
|
|
47
|
+
ts: "2026-01-01T00:00:00.000Z",
|
|
48
|
+
actor: "agent",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
appendEvent(main, {
|
|
52
|
+
cmd: "plan_milestone",
|
|
53
|
+
params: { milestoneId: "M001", title: "Main Choice" },
|
|
54
|
+
ts: "2026-01-01T00:01:00.000Z",
|
|
55
|
+
actor: "agent",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
appendEvent(worktree, {
|
|
59
|
+
cmd: "plan_milestone",
|
|
60
|
+
params: { milestoneId: "M001", title: "Worktree Choice" },
|
|
61
|
+
ts: "2026-01-01T00:01:00.000Z",
|
|
62
|
+
actor: "agent",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const initial = reconcileWorktreeLogs(main, worktree);
|
|
66
|
+
assert.equal(initial.conflicts.length, 1, "expected one conflict before resolution");
|
|
67
|
+
assert.ok(listConflicts(main).length === 1, "CONFLICTS.md should exist after detection");
|
|
68
|
+
|
|
69
|
+
resolveConflict(main, worktree, "milestone:M001", "main");
|
|
70
|
+
|
|
71
|
+
assert.equal(listConflicts(main).length, 0, "conflict file should be cleared after resolving main");
|
|
72
|
+
const conflictsPath = join(main, ".gsd", "CONFLICTS.md");
|
|
73
|
+
assert.equal(
|
|
74
|
+
existsSync(conflictsPath),
|
|
75
|
+
false,
|
|
76
|
+
"CONFLICTS.md should be removed after the last conflict is resolved",
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const wtEvents = readEvents(join(worktree, ".gsd", "event-log.jsonl"));
|
|
80
|
+
assert.ok(
|
|
81
|
+
wtEvents.some((e) => e.cmd === "plan_milestone" && e.params.title === "Main Choice"),
|
|
82
|
+
"worktree log should be rewritten to the main-side resolution",
|
|
83
|
+
);
|
|
84
|
+
assert.ok(
|
|
85
|
+
!wtEvents.some((e) => e.cmd === "plan_milestone" && e.params.title === "Worktree Choice"),
|
|
86
|
+
"worktree log should no longer contain the discarded conflict event",
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const second = reconcileWorktreeLogs(main, worktree);
|
|
90
|
+
assert.equal(second.conflicts.length, 0, "reconcile should stay clean after choosing main");
|
|
91
|
+
});
|