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
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { handleCoreCommand } from "../commands/handlers/core.ts";
|
|
5
|
+
|
|
6
|
+
function makeCtx(customResult: unknown) {
|
|
7
|
+
const notices: Array<{ message: string; type?: string }> = [];
|
|
8
|
+
return {
|
|
9
|
+
hasUI: true,
|
|
10
|
+
ui: {
|
|
11
|
+
custom: async () => customResult,
|
|
12
|
+
notify: (message: string, type?: string) => {
|
|
13
|
+
notices.push({ message, type });
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
notices,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
test("visualize only falls back when ctx.ui.custom() is unavailable", async () => {
|
|
21
|
+
const successCtx = makeCtx(true);
|
|
22
|
+
const success = await handleCoreCommand("visualize", successCtx as any);
|
|
23
|
+
assert.equal(success, true);
|
|
24
|
+
assert.equal(successCtx.notices.length, 0, "successful overlay close does not trigger fallback");
|
|
25
|
+
|
|
26
|
+
const fallbackCtx = makeCtx(undefined);
|
|
27
|
+
const fallback = await handleCoreCommand("visualize", fallbackCtx as any);
|
|
28
|
+
assert.equal(fallback, true);
|
|
29
|
+
assert.equal(fallbackCtx.notices.length, 1, "unavailable overlay triggers fallback warning");
|
|
30
|
+
assert.match(fallbackCtx.notices[0]!.message, /interactive terminal/i);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("show-config only falls back when ctx.ui.custom() is unavailable", async () => {
|
|
34
|
+
const successCtx = makeCtx(true);
|
|
35
|
+
const success = await handleCoreCommand("show-config", successCtx as any);
|
|
36
|
+
assert.equal(success, true);
|
|
37
|
+
assert.equal(successCtx.notices.length, 0, "successful overlay close does not trigger fallback");
|
|
38
|
+
|
|
39
|
+
const fallbackCtx = makeCtx(undefined);
|
|
40
|
+
const fallback = await handleCoreCommand("show-config", fallbackCtx as any);
|
|
41
|
+
assert.equal(fallback, true);
|
|
42
|
+
assert.equal(fallbackCtx.notices.length, 1, "unavailable overlay triggers text fallback");
|
|
43
|
+
assert.match(fallbackCtx.notices[0]!.message, /GSD Configuration/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("model command resolves and persists exact provider-qualified selection", async () => {
|
|
47
|
+
const selectedModel = { provider: "openai", id: "gpt-5.4" };
|
|
48
|
+
let applied: typeof selectedModel | null = null;
|
|
49
|
+
const ctx = {
|
|
50
|
+
hasUI: true,
|
|
51
|
+
model: { provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
52
|
+
modelRegistry: {
|
|
53
|
+
getAvailable: () => [
|
|
54
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
55
|
+
selectedModel,
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
ui: {
|
|
59
|
+
notify: (message: string, type?: string) => {
|
|
60
|
+
notices.push({ message, type });
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
} as any;
|
|
64
|
+
const notices: Array<{ message: string; type?: string }> = [];
|
|
65
|
+
const pi = {
|
|
66
|
+
setModel: async (model: typeof selectedModel) => {
|
|
67
|
+
applied = model;
|
|
68
|
+
return true;
|
|
69
|
+
},
|
|
70
|
+
} as any;
|
|
71
|
+
|
|
72
|
+
const handled = await handleCoreCommand("model openai/gpt-5.4", ctx, pi);
|
|
73
|
+
assert.equal(handled, true);
|
|
74
|
+
assert.deepEqual(applied, selectedModel);
|
|
75
|
+
assert.match(notices[0]!.message, /openai\/gpt-5\.4/);
|
|
76
|
+
});
|
|
@@ -178,7 +178,7 @@ function makeMockDeps(overrides?: Partial<LoopDeps>): LoopDeps & { callLog: stri
|
|
|
178
178
|
getCurrentBranch: () => "main",
|
|
179
179
|
autoWorktreeBranch: () => "auto/M001",
|
|
180
180
|
resolveMilestoneFile: () => null,
|
|
181
|
-
reconcileMergeState: () =>
|
|
181
|
+
reconcileMergeState: () => "clean",
|
|
182
182
|
getLedger: () => null,
|
|
183
183
|
getProjectTotals: () => ({ cost: 0 }),
|
|
184
184
|
formatCost: (c: number) => `$${c.toFixed(2)}`,
|
|
@@ -311,6 +311,12 @@ describe("Custom engine loop integration", () => {
|
|
|
311
311
|
`stopAuto reason should include "Workflow complete", got: ${stopEntry}`,
|
|
312
312
|
);
|
|
313
313
|
|
|
314
|
+
assert.equal(
|
|
315
|
+
deps.callLog.filter((e: string) => e === "deriveState").length,
|
|
316
|
+
3,
|
|
317
|
+
"custom engine should stop immediately after a milestone-complete reconcile",
|
|
318
|
+
);
|
|
319
|
+
|
|
314
320
|
// Verify dev path was NOT used (resolveDispatch should not appear)
|
|
315
321
|
assert.ok(
|
|
316
322
|
!deps.callLog.includes("resolveDispatch"),
|
|
@@ -249,6 +249,37 @@ describe("CustomWorkflowEngine.reconcile", () => {
|
|
|
249
249
|
const graph = readGraph(runDir);
|
|
250
250
|
assert.equal(graph.steps[0].status, "complete");
|
|
251
251
|
});
|
|
252
|
+
|
|
253
|
+
it("re-reads GRAPH.yaml before reconcile so concurrent edits are preserved", async () => {
|
|
254
|
+
const { engine, runDir } = setupEngine([
|
|
255
|
+
makeStep({ id: "step-1" }),
|
|
256
|
+
makeStep({ id: "step-2", dependsOn: ["step-1"] }),
|
|
257
|
+
], "wf");
|
|
258
|
+
|
|
259
|
+
const staleState = await engine.deriveState("/unused");
|
|
260
|
+
|
|
261
|
+
// Simulate another process appending a new step after deriveState() ran.
|
|
262
|
+
writeGraph(runDir, makeGraph([
|
|
263
|
+
makeStep({ id: "step-1" }),
|
|
264
|
+
makeStep({ id: "step-2", dependsOn: ["step-1"] }),
|
|
265
|
+
makeStep({ id: "step-3", dependsOn: ["step-2"] }),
|
|
266
|
+
], "wf"));
|
|
267
|
+
|
|
268
|
+
const result = await engine.reconcile(staleState, {
|
|
269
|
+
unitType: "custom-step",
|
|
270
|
+
unitId: "wf/step-1",
|
|
271
|
+
startedAt: Date.now() - 1000,
|
|
272
|
+
finishedAt: Date.now(),
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
assert.equal(result.outcome, "continue");
|
|
276
|
+
|
|
277
|
+
const graph = readGraph(runDir);
|
|
278
|
+
assert.equal(graph.steps.length, 3, "reconcile should preserve the concurrent graph edit");
|
|
279
|
+
assert.equal(graph.steps[0].status, "complete");
|
|
280
|
+
assert.equal(graph.steps[1].status, "pending");
|
|
281
|
+
assert.equal(graph.steps[2].status, "pending");
|
|
282
|
+
});
|
|
252
283
|
});
|
|
253
284
|
|
|
254
285
|
// ─── getDisplayMetadata ──────────────────────────────────────────────────
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
// decision-scope-cascade: Tests for R005 fallback cascade and scope derivation
|
|
2
|
+
//
|
|
3
|
+
// Validates:
|
|
4
|
+
// (a) inlineDecisionsFromDb cascade: milestone + scope → milestone only → null
|
|
5
|
+
// (b) deriveSliceScope extracts meaningful scope keywords from slice titles
|
|
6
|
+
// (c) deriveSliceScope returns undefined for generic titles
|
|
7
|
+
|
|
8
|
+
import { describe, test, afterEach, beforeEach } from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import {
|
|
11
|
+
openDatabase,
|
|
12
|
+
closeDatabase,
|
|
13
|
+
isDbAvailable,
|
|
14
|
+
insertDecision,
|
|
15
|
+
} from '../gsd-db.ts';
|
|
16
|
+
import {
|
|
17
|
+
queryDecisions,
|
|
18
|
+
formatDecisionsForPrompt,
|
|
19
|
+
} from '../context-store.ts';
|
|
20
|
+
import { deriveSliceScope } from '../auto-prompts.ts';
|
|
21
|
+
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
23
|
+
// deriveSliceScope: Extract meaningful scope from slice titles
|
|
24
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
25
|
+
|
|
26
|
+
describe("deriveSliceScope: keyword extraction", () => {
|
|
27
|
+
test("extracts first meaningful noun from title", () => {
|
|
28
|
+
// "Auth Middleware & Protected Route" → "auth"
|
|
29
|
+
assert.strictEqual(
|
|
30
|
+
deriveSliceScope("Auth Middleware & Protected Route"),
|
|
31
|
+
"auth",
|
|
32
|
+
"extracts 'auth' from auth-related title",
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// "Database & User Model Setup" → "database" (not "setup" which is generic)
|
|
36
|
+
const dbScope = deriveSliceScope("Database & User Model Setup");
|
|
37
|
+
assert.ok(
|
|
38
|
+
dbScope === "database" || dbScope === "user",
|
|
39
|
+
`expected 'database' or 'user', got '${dbScope}'`,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// "API Rate Limiting" → "api"
|
|
43
|
+
assert.strictEqual(
|
|
44
|
+
deriveSliceScope("API Rate Limiting"),
|
|
45
|
+
"api",
|
|
46
|
+
"extracts 'api' from API-related title",
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// "Stripe Payment Integration" → "stripe"
|
|
50
|
+
assert.strictEqual(
|
|
51
|
+
deriveSliceScope("Stripe Payment Integration"),
|
|
52
|
+
"stripe",
|
|
53
|
+
"extracts 'stripe' from payment-related title",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("returns undefined for generic titles", () => {
|
|
58
|
+
// "Integration Testing" → undefined (both words are generic)
|
|
59
|
+
assert.strictEqual(
|
|
60
|
+
deriveSliceScope("Integration Testing"),
|
|
61
|
+
undefined,
|
|
62
|
+
"returns undefined for generic 'Integration Testing'",
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// "Setup & Configuration" → undefined (all generic)
|
|
66
|
+
assert.strictEqual(
|
|
67
|
+
deriveSliceScope("Setup & Configuration"),
|
|
68
|
+
undefined,
|
|
69
|
+
"returns undefined for generic 'Setup & Configuration'",
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// "Final Review" → undefined
|
|
73
|
+
assert.strictEqual(
|
|
74
|
+
deriveSliceScope("Final Review"),
|
|
75
|
+
undefined,
|
|
76
|
+
"returns undefined for generic 'Final Review'",
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// "Basic Implementation" → undefined
|
|
80
|
+
assert.strictEqual(
|
|
81
|
+
deriveSliceScope("Basic Implementation"),
|
|
82
|
+
undefined,
|
|
83
|
+
"returns undefined for generic 'Basic Implementation'",
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("handles description as additional context", () => {
|
|
88
|
+
// Generic title but specific description
|
|
89
|
+
const scope = deriveSliceScope(
|
|
90
|
+
"Initial Setup",
|
|
91
|
+
"Configure PostgreSQL database connection",
|
|
92
|
+
);
|
|
93
|
+
assert.ok(
|
|
94
|
+
scope === "postgresql" || scope === "database" || scope === "configure",
|
|
95
|
+
`expected meaningful scope from description, got '${scope}'`,
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("handles edge cases", () => {
|
|
100
|
+
// Empty title
|
|
101
|
+
assert.strictEqual(
|
|
102
|
+
deriveSliceScope(""),
|
|
103
|
+
undefined,
|
|
104
|
+
"returns undefined for empty title",
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Short words only
|
|
108
|
+
assert.strictEqual(
|
|
109
|
+
deriveSliceScope("A B C"),
|
|
110
|
+
undefined,
|
|
111
|
+
"returns undefined for very short words",
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Mixed case and punctuation
|
|
115
|
+
assert.strictEqual(
|
|
116
|
+
deriveSliceScope("OAuth2 + JWT Authentication"),
|
|
117
|
+
"oauth2",
|
|
118
|
+
"handles mixed case and punctuation",
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("filters unit IDs (S01, M001, T03)", () => {
|
|
123
|
+
// "S01: Infrastructure" → undefined (S01 is a unit ID, infrastructure is generic)
|
|
124
|
+
assert.strictEqual(
|
|
125
|
+
deriveSliceScope("S01: Infrastructure"),
|
|
126
|
+
undefined,
|
|
127
|
+
"skips S01 ID and returns undefined for generic 'Infrastructure'",
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// "M001 Setup" → undefined (M001 is a unit ID, setup is generic)
|
|
131
|
+
assert.strictEqual(
|
|
132
|
+
deriveSliceScope("M001 Setup"),
|
|
133
|
+
undefined,
|
|
134
|
+
"skips M001 ID and returns undefined for generic 'Setup'",
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// "T03: Database Migration" → "database" (skips T03, returns meaningful word)
|
|
138
|
+
assert.strictEqual(
|
|
139
|
+
deriveSliceScope("T03: Database Migration"),
|
|
140
|
+
"database",
|
|
141
|
+
"skips T03 ID and returns 'database'",
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// "S02 Auth Flow" → "auth" (skips S02, returns meaningful word)
|
|
145
|
+
assert.strictEqual(
|
|
146
|
+
deriveSliceScope("S02 Auth Flow"),
|
|
147
|
+
"auth",
|
|
148
|
+
"skips S02 ID and returns 'auth'",
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("filters process/activity words", () => {
|
|
153
|
+
// "Integration Testing + Hardening" → undefined (all generic/process words)
|
|
154
|
+
assert.strictEqual(
|
|
155
|
+
deriveSliceScope("Integration Testing + Hardening"),
|
|
156
|
+
undefined,
|
|
157
|
+
"returns undefined for 'Integration Testing + Hardening'",
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// "Validation & Verification" → undefined (both are process words)
|
|
161
|
+
assert.strictEqual(
|
|
162
|
+
deriveSliceScope("Validation & Verification"),
|
|
163
|
+
undefined,
|
|
164
|
+
"returns undefined for 'Validation & Verification'",
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// "Performance Optimization" → "performance" (optimization is generic, performance is domain)
|
|
168
|
+
assert.strictEqual(
|
|
169
|
+
deriveSliceScope("Performance Optimization"),
|
|
170
|
+
"performance",
|
|
171
|
+
"extracts 'performance' before generic 'optimization'",
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// "Security Enhancement" → "security" (enhancement is generic, security is domain)
|
|
175
|
+
assert.strictEqual(
|
|
176
|
+
deriveSliceScope("Security Enhancement"),
|
|
177
|
+
"security",
|
|
178
|
+
"extracts 'security' before generic 'enhancement'",
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// "WebSocket Delivery Pipeline" → "websocket"
|
|
182
|
+
assert.strictEqual(
|
|
183
|
+
deriveSliceScope("WebSocket Delivery Pipeline"),
|
|
184
|
+
"websocket",
|
|
185
|
+
"extracts 'websocket' from delivery pipeline title",
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// "Prisma Schema + Migration" → "prisma"
|
|
189
|
+
assert.strictEqual(
|
|
190
|
+
deriveSliceScope("Prisma Schema + Migration"),
|
|
191
|
+
"prisma",
|
|
192
|
+
"extracts 'prisma' from schema migration title",
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
198
|
+
// inlineDecisionsFromDb cascade: R005 implementation
|
|
199
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
200
|
+
|
|
201
|
+
describe("inlineDecisionsFromDb: cascade fallback (R005)", () => {
|
|
202
|
+
beforeEach(() => {
|
|
203
|
+
openDatabase(':memory:');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
afterEach(() => {
|
|
207
|
+
closeDatabase();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("cascade: scoped query returns scoped decisions when they exist", () => {
|
|
211
|
+
// Insert decisions with different scopes
|
|
212
|
+
insertDecision({
|
|
213
|
+
id: 'D001', when_context: 'M001/S01', scope: 'auth',
|
|
214
|
+
decision: 'use JWT', choice: 'JWT', rationale: 'standard',
|
|
215
|
+
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
216
|
+
});
|
|
217
|
+
insertDecision({
|
|
218
|
+
id: 'D002', when_context: 'M001/S02', scope: 'database',
|
|
219
|
+
decision: 'use PostgreSQL', choice: 'PostgreSQL', rationale: 'relational',
|
|
220
|
+
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
221
|
+
});
|
|
222
|
+
insertDecision({
|
|
223
|
+
id: 'D003', when_context: 'M001/S01', scope: 'architecture',
|
|
224
|
+
decision: 'use microservices', choice: 'microservices', rationale: 'scalable',
|
|
225
|
+
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Query with scope 'auth' should return D001 only
|
|
229
|
+
const authDecisions = queryDecisions({ milestoneId: 'M001', scope: 'auth' });
|
|
230
|
+
assert.strictEqual(authDecisions.length, 1, 'scoped query returns 1 decision');
|
|
231
|
+
assert.strictEqual(authDecisions[0]?.id, 'D001', 'returns D001 for auth scope');
|
|
232
|
+
|
|
233
|
+
// Query with scope 'database' should return D002 only
|
|
234
|
+
const dbDecisions = queryDecisions({ milestoneId: 'M001', scope: 'database' });
|
|
235
|
+
assert.strictEqual(dbDecisions.length, 1, 'scoped query returns 1 decision');
|
|
236
|
+
assert.strictEqual(dbDecisions[0]?.id, 'D002', 'returns D002 for database scope');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("cascade: milestone-only fallback when scoped query returns empty", () => {
|
|
240
|
+
// Insert decisions for M001 with generic scope (e.g. 'architecture')
|
|
241
|
+
insertDecision({
|
|
242
|
+
id: 'D001', when_context: 'M001/S01', scope: 'architecture',
|
|
243
|
+
decision: 'use microservices', choice: 'microservices', rationale: 'scalable',
|
|
244
|
+
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
245
|
+
});
|
|
246
|
+
insertDecision({
|
|
247
|
+
id: 'D002', when_context: 'M001/S02', scope: 'performance',
|
|
248
|
+
decision: 'use caching', choice: 'Redis', rationale: 'fast',
|
|
249
|
+
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Query with scope 'auth' (no decisions with this scope) should return empty
|
|
253
|
+
const authDecisions = queryDecisions({ milestoneId: 'M001', scope: 'auth' });
|
|
254
|
+
assert.strictEqual(authDecisions.length, 0, 'scoped query for auth returns empty');
|
|
255
|
+
|
|
256
|
+
// Simulate cascade: fallback to milestone-only query
|
|
257
|
+
const milestoneDecisions = queryDecisions({ milestoneId: 'M001' });
|
|
258
|
+
assert.strictEqual(milestoneDecisions.length, 2, 'milestone-only query returns 2 decisions');
|
|
259
|
+
const ids = milestoneDecisions.map(d => d.id).sort();
|
|
260
|
+
assert.deepStrictEqual(ids, ['D001', 'D002'], 'milestone fallback returns all M001 decisions');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("cascade: returns null when both scoped and milestone queries are empty", () => {
|
|
264
|
+
// Insert decisions only for M002
|
|
265
|
+
insertDecision({
|
|
266
|
+
id: 'D001', when_context: 'M002/S01', scope: 'auth',
|
|
267
|
+
decision: 'use OAuth', choice: 'OAuth2', rationale: 'standard',
|
|
268
|
+
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Query M001 with scope should return empty (no M001 decisions at all)
|
|
272
|
+
const scopedDecisions = queryDecisions({ milestoneId: 'M001', scope: 'auth' });
|
|
273
|
+
assert.strictEqual(scopedDecisions.length, 0, 'scoped query returns empty');
|
|
274
|
+
|
|
275
|
+
// Fallback to milestone-only should also return empty (no M001 decisions)
|
|
276
|
+
const milestoneDecisions = queryDecisions({ milestoneId: 'M001' });
|
|
277
|
+
assert.strictEqual(milestoneDecisions.length, 0, 'milestone-only query returns empty');
|
|
278
|
+
|
|
279
|
+
// This scenario would result in null from inlineDecisionsFromDb
|
|
280
|
+
// (we can't directly test inlineDecisionsFromDb here without mocking fs)
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("cascade: demonstrates the full cascade behavior", () => {
|
|
284
|
+
// This test demonstrates the cascade logic that inlineDecisionsFromDb implements:
|
|
285
|
+
// 1. First try { milestoneId: 'M001', scope: 'payment' } → empty
|
|
286
|
+
// 2. Then try { milestoneId: 'M001' } → gets D001, D002
|
|
287
|
+
// 3. Return the milestone-level decisions
|
|
288
|
+
|
|
289
|
+
// Setup: decisions exist at milestone level but not for 'payment' scope
|
|
290
|
+
insertDecision({
|
|
291
|
+
id: 'D001', when_context: 'M001/S01', scope: 'architecture',
|
|
292
|
+
decision: 'use REST', choice: 'REST API', rationale: 'standard',
|
|
293
|
+
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
294
|
+
});
|
|
295
|
+
insertDecision({
|
|
296
|
+
id: 'D002', when_context: 'M001/S02', scope: 'security',
|
|
297
|
+
decision: 'use HTTPS', choice: 'TLS 1.3', rationale: 'secure',
|
|
298
|
+
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Step 1: Query with scope 'payment' (no matches)
|
|
302
|
+
const paymentDecisions = queryDecisions({ milestoneId: 'M001', scope: 'payment' });
|
|
303
|
+
assert.strictEqual(paymentDecisions.length, 0, 'payment scope query returns empty');
|
|
304
|
+
|
|
305
|
+
// Step 2: Since scope was provided but returned empty, cascade to milestone-only
|
|
306
|
+
const milestoneDecisions = queryDecisions({ milestoneId: 'M001' });
|
|
307
|
+
assert.strictEqual(milestoneDecisions.length, 2, 'milestone fallback returns 2 decisions');
|
|
308
|
+
|
|
309
|
+
// Step 3: Format and verify content
|
|
310
|
+
const formatted = formatDecisionsForPrompt(milestoneDecisions);
|
|
311
|
+
assert.match(formatted, /D001/, 'formatted output includes D001');
|
|
312
|
+
assert.match(formatted, /D002/, 'formatted output includes D002');
|
|
313
|
+
assert.match(formatted, /architecture/, 'formatted output includes architecture scope');
|
|
314
|
+
assert.match(formatted, /security/, 'formatted output includes security scope');
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
319
|
+
// Integration: scope derivation feeds into cascade
|
|
320
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
321
|
+
|
|
322
|
+
describe("integration: scope derivation with cascade", () => {
|
|
323
|
+
beforeEach(() => {
|
|
324
|
+
openDatabase(':memory:');
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
afterEach(() => {
|
|
328
|
+
closeDatabase();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("derived scope finds matching decisions when they exist", () => {
|
|
332
|
+
// Insert decisions with 'auth' scope
|
|
333
|
+
insertDecision({
|
|
334
|
+
id: 'D001', when_context: 'M001/S01', scope: 'auth',
|
|
335
|
+
decision: 'use JWT', choice: 'JWT tokens', rationale: 'stateless',
|
|
336
|
+
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Derive scope from slice title
|
|
340
|
+
const derivedScope = deriveSliceScope("Auth Middleware & Protected Routes");
|
|
341
|
+
assert.strictEqual(derivedScope, 'auth', 'derives auth scope from title');
|
|
342
|
+
|
|
343
|
+
// Query with derived scope should find the decision
|
|
344
|
+
const decisions = queryDecisions({ milestoneId: 'M001', scope: derivedScope });
|
|
345
|
+
assert.strictEqual(decisions.length, 1, 'scoped query finds matching decision');
|
|
346
|
+
assert.strictEqual(decisions[0]?.id, 'D001', 'finds the auth decision');
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("generic title triggers milestone-level fallback", () => {
|
|
350
|
+
// Insert decisions with various scopes
|
|
351
|
+
insertDecision({
|
|
352
|
+
id: 'D001', when_context: 'M001/S01', scope: 'architecture',
|
|
353
|
+
decision: 'use monolith', choice: 'monolith', rationale: 'simple',
|
|
354
|
+
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
355
|
+
});
|
|
356
|
+
insertDecision({
|
|
357
|
+
id: 'D002', when_context: 'M001/S02', scope: 'tooling',
|
|
358
|
+
decision: 'use TypeScript', choice: 'TypeScript', rationale: 'type safety',
|
|
359
|
+
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Derive scope from generic slice title
|
|
363
|
+
const derivedScope = deriveSliceScope("Integration Testing");
|
|
364
|
+
assert.strictEqual(derivedScope, undefined, 'generic title returns undefined scope');
|
|
365
|
+
|
|
366
|
+
// Without a scope, query returns all milestone decisions
|
|
367
|
+
const decisions = queryDecisions({ milestoneId: 'M001', scope: derivedScope });
|
|
368
|
+
assert.strictEqual(decisions.length, 2, 'no scope filter returns all decisions');
|
|
369
|
+
});
|
|
370
|
+
});
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
detectProjectState,
|
|
18
18
|
detectV1Planning,
|
|
19
19
|
detectProjectSignals,
|
|
20
|
+
scanProjectFiles,
|
|
20
21
|
} from "../detection.ts";
|
|
21
22
|
|
|
22
23
|
function makeTempDir(prefix: string): string {
|
|
@@ -1188,3 +1189,39 @@ test("detectProjectSignals: Spring Boot settings-defined catalog accessor emits
|
|
|
1188
1189
|
cleanup(dir);
|
|
1189
1190
|
}
|
|
1190
1191
|
});
|
|
1192
|
+
|
|
1193
|
+
// ─── scanProjectFiles: RECURSIVE_SCAN_IGNORED_DIRS ──────────────────────
|
|
1194
|
+
|
|
1195
|
+
test("scanProjectFiles: excludes .claude, .gsd, .planning, .plans, .cursor, .vscode directories", () => {
|
|
1196
|
+
const dir = makeTempDir("scan-ignore-dotdirs");
|
|
1197
|
+
try {
|
|
1198
|
+
// Create project files that should be included
|
|
1199
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
1200
|
+
writeFileSync(join(dir, "src", "main.ts"), "// main\n", "utf-8");
|
|
1201
|
+
writeFileSync(join(dir, "README.md"), "# Project\n", "utf-8");
|
|
1202
|
+
|
|
1203
|
+
// Create tool directories that should be excluded
|
|
1204
|
+
const excludedDirs = [".claude", ".gsd", ".planning", ".plans", ".cursor", ".vscode"];
|
|
1205
|
+
for (const d of excludedDirs) {
|
|
1206
|
+
mkdirSync(join(dir, d), { recursive: true });
|
|
1207
|
+
writeFileSync(join(dir, d, "config.json"), "{}\n", "utf-8");
|
|
1208
|
+
}
|
|
1209
|
+
// Nested .claude directory
|
|
1210
|
+
mkdirSync(join(dir, ".claude", "memory"), { recursive: true });
|
|
1211
|
+
writeFileSync(join(dir, ".claude", "memory", "user.md"), "# Memory\n", "utf-8");
|
|
1212
|
+
|
|
1213
|
+
const files = scanProjectFiles(dir);
|
|
1214
|
+
|
|
1215
|
+
// Should include project files
|
|
1216
|
+
assert.ok(files.includes("src/main.ts"), "should include src/main.ts");
|
|
1217
|
+
assert.ok(files.includes("README.md"), "should include README.md");
|
|
1218
|
+
|
|
1219
|
+
// Should exclude all tool directories
|
|
1220
|
+
for (const d of excludedDirs) {
|
|
1221
|
+
const hasExcluded = files.some((f) => f.startsWith(`${d}/`));
|
|
1222
|
+
assert.ok(!hasExcluded, `should exclude ${d}/ directory but found: ${files.filter((f) => f.startsWith(`${d}/`)).join(", ")}`);
|
|
1223
|
+
}
|
|
1224
|
+
} finally {
|
|
1225
|
+
cleanup(dir);
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
|
|
8
|
+
import { validateFileChanges } from "../safety/file-change-validator.ts";
|
|
9
|
+
|
|
10
|
+
function git(cwd: string, ...args: string[]): string {
|
|
11
|
+
return execFileSync("git", args, {
|
|
12
|
+
cwd,
|
|
13
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
14
|
+
encoding: "utf-8",
|
|
15
|
+
}).trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test("validateFileChanges ignores inline descriptions in expected output paths", (t) => {
|
|
19
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-file-change-validator-"));
|
|
20
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
21
|
+
|
|
22
|
+
mkdirSync(join(base, "definitions"), { recursive: true });
|
|
23
|
+
git(base, "init");
|
|
24
|
+
git(base, "config", "user.email", "test@example.com");
|
|
25
|
+
git(base, "config", "user.name", "Test User");
|
|
26
|
+
|
|
27
|
+
const target = join(base, "definitions", "ac-audit.md");
|
|
28
|
+
writeFileSync(target, "initial\n");
|
|
29
|
+
git(base, "add", ".");
|
|
30
|
+
git(base, "commit", "-m", "initial");
|
|
31
|
+
|
|
32
|
+
writeFileSync(target, "updated\n");
|
|
33
|
+
git(base, "add", ".");
|
|
34
|
+
git(base, "commit", "-m", "update");
|
|
35
|
+
|
|
36
|
+
const audit = validateFileChanges(
|
|
37
|
+
base,
|
|
38
|
+
["definitions/ac-audit.md — current state of AC CRM, tags, pipelines, automations"],
|
|
39
|
+
[],
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
assert.ok(audit, "audit should be produced when expected output exists");
|
|
43
|
+
assert.deepEqual(audit.unexpectedFiles, []);
|
|
44
|
+
assert.deepEqual(audit.missingFiles, []);
|
|
45
|
+
assert.equal(
|
|
46
|
+
audit.violations.some((v) => v.severity === "warning"),
|
|
47
|
+
false,
|
|
48
|
+
"described expected output should not trigger unexpected-file warnings",
|
|
49
|
+
);
|
|
50
|
+
});
|
|
@@ -245,6 +245,41 @@ describe('gsd-tools', () => {
|
|
|
245
245
|
}
|
|
246
246
|
});
|
|
247
247
|
|
|
248
|
+
test('gsd_summary_save supports CONTEXT-DRAFT persistence', async () => {
|
|
249
|
+
const tmpDir = makeTmpDir();
|
|
250
|
+
try {
|
|
251
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
252
|
+
openDatabase(dbPath);
|
|
253
|
+
|
|
254
|
+
await saveArtifactToDb(
|
|
255
|
+
{
|
|
256
|
+
path: 'milestones/M001/M001-CONTEXT-DRAFT.md',
|
|
257
|
+
artifact_type: 'CONTEXT-DRAFT',
|
|
258
|
+
content: '# M001 Draft Context\n\nDraft notes.',
|
|
259
|
+
milestone_id: 'M001',
|
|
260
|
+
},
|
|
261
|
+
tmpDir,
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const draftPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'M001-CONTEXT-DRAFT.md');
|
|
265
|
+
assert.ok(fs.existsSync(draftPath), 'Draft context file should be created');
|
|
266
|
+
const draftContent = fs.readFileSync(draftPath, 'utf-8');
|
|
267
|
+
assert.ok(draftContent.includes('Draft Context'), 'Draft context file should contain draft content');
|
|
268
|
+
|
|
269
|
+
const adapter = _getAdapter();
|
|
270
|
+
assert.ok(adapter !== null, 'Adapter should be available');
|
|
271
|
+
const rows = adapter!.prepare(
|
|
272
|
+
"SELECT * FROM artifacts WHERE path = 'milestones/M001/M001-CONTEXT-DRAFT.md'",
|
|
273
|
+
).all();
|
|
274
|
+
assert.deepStrictEqual(rows.length, 1, 'Should have 1 draft artifact row');
|
|
275
|
+
assert.deepStrictEqual(rows[0]['artifact_type'] as string, 'CONTEXT-DRAFT', 'Artifact type should be CONTEXT-DRAFT');
|
|
276
|
+
|
|
277
|
+
closeDatabase();
|
|
278
|
+
} finally {
|
|
279
|
+
cleanupDir(tmpDir);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
248
283
|
test('DB unavailable error paths', async () => {
|
|
249
284
|
// (d) All tools return isError when DB unavailable
|
|
250
285
|
// Close any open DB and don't open a new one
|