gsd-pi 2.18.0 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/cli.js +3 -3
- package/dist/onboarding.d.ts +3 -1
- package/dist/onboarding.js +77 -3
- package/dist/remote-questions-config.d.ts +1 -1
- package/dist/resources/extensions/google-search/index.ts +164 -47
- package/dist/resources/extensions/gsd/auto-dashboard.ts +14 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +148 -39
- package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/dist/resources/extensions/gsd/auto.ts +690 -39
- package/dist/resources/extensions/gsd/captures.ts +384 -0
- package/dist/resources/extensions/gsd/commands.ts +654 -36
- package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/dist/resources/extensions/gsd/context-budget.ts +243 -0
- package/dist/resources/extensions/gsd/context-store.ts +195 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +51 -3
- package/dist/resources/extensions/gsd/db-writer.ts +341 -0
- package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/dist/resources/extensions/gsd/doctor.ts +283 -2
- package/dist/resources/extensions/gsd/export.ts +81 -2
- package/dist/resources/extensions/gsd/files.ts +39 -9
- package/dist/resources/extensions/gsd/git-service.ts +6 -0
- package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
- package/dist/resources/extensions/gsd/history.ts +0 -1
- package/dist/resources/extensions/gsd/index.ts +277 -1
- package/dist/resources/extensions/gsd/md-importer.ts +526 -0
- package/dist/resources/extensions/gsd/metrics.ts +84 -0
- package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/dist/resources/extensions/gsd/model-router.ts +256 -0
- package/dist/resources/extensions/gsd/notifications.ts +0 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +72 -2
- package/dist/resources/extensions/gsd/preferences.ts +198 -150
- package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -1
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -0
- package/dist/resources/extensions/gsd/quick.ts +156 -0
- package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/dist/resources/extensions/gsd/skill-health.ts +417 -0
- package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/dist/resources/extensions/gsd/state.ts +30 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
- package/dist/resources/extensions/gsd/tests/captures.test.ts +438 -0
- package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
- package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -0
- package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
- package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
- package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
- package/dist/resources/extensions/gsd/types.ts +29 -0
- package/dist/resources/extensions/gsd/undo.ts +0 -1
- package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/dist/resources/extensions/gsd/visualizer-data.ts +505 -0
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +337 -0
- package/dist/resources/extensions/gsd/visualizer-views.ts +755 -0
- package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/dist/resources/extensions/remote-questions/config.ts +4 -2
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +35 -4
- package/dist/resources/extensions/remote-questions/format.ts +166 -14
- package/dist/resources/extensions/remote-questions/manager.ts +14 -4
- package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/dist/resources/extensions/remote-questions/types.ts +2 -1
- package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/dist/resources/extensions/voice/index.ts +4 -3
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
- package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
- package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
- package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
- package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
- package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
- package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
- package/src/resources/extensions/google-search/index.ts +164 -47
- package/src/resources/extensions/gsd/auto-dashboard.ts +14 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +148 -39
- package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/src/resources/extensions/gsd/auto.ts +690 -39
- package/src/resources/extensions/gsd/captures.ts +384 -0
- package/src/resources/extensions/gsd/commands.ts +654 -36
- package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/src/resources/extensions/gsd/context-budget.ts +243 -0
- package/src/resources/extensions/gsd/context-store.ts +195 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +51 -3
- package/src/resources/extensions/gsd/db-writer.ts +341 -0
- package/src/resources/extensions/gsd/debug-logger.ts +178 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/src/resources/extensions/gsd/doctor.ts +283 -2
- package/src/resources/extensions/gsd/export.ts +81 -2
- package/src/resources/extensions/gsd/files.ts +39 -9
- package/src/resources/extensions/gsd/git-service.ts +6 -0
- package/src/resources/extensions/gsd/gsd-db.ts +752 -0
- package/src/resources/extensions/gsd/guided-flow.ts +26 -1
- package/src/resources/extensions/gsd/history.ts +0 -1
- package/src/resources/extensions/gsd/index.ts +277 -1
- package/src/resources/extensions/gsd/md-importer.ts +526 -0
- package/src/resources/extensions/gsd/metrics.ts +84 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/src/resources/extensions/gsd/model-router.ts +256 -0
- package/src/resources/extensions/gsd/notifications.ts +0 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +72 -2
- package/src/resources/extensions/gsd/preferences.ts +198 -150
- package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -1
- package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -0
- package/src/resources/extensions/gsd/quick.ts +156 -0
- package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/src/resources/extensions/gsd/skill-health.ts +417 -0
- package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/src/resources/extensions/gsd/state.ts +30 -0
- package/src/resources/extensions/gsd/templates/preferences.md +1 -0
- package/src/resources/extensions/gsd/tests/captures.test.ts +438 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
- package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/src/resources/extensions/gsd/triage-ui.ts +175 -0
- package/src/resources/extensions/gsd/types.ts +29 -0
- package/src/resources/extensions/gsd/undo.ts +0 -1
- package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +505 -0
- package/src/resources/extensions/gsd/visualizer-overlay.ts +337 -0
- package/src/resources/extensions/gsd/visualizer-views.ts +755 -0
- package/src/resources/extensions/gsd/worktree-command.ts +18 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/src/resources/extensions/remote-questions/config.ts +4 -2
- package/src/resources/extensions/remote-questions/discord-adapter.ts +35 -4
- package/src/resources/extensions/remote-questions/format.ts +166 -14
- package/src/resources/extensions/remote-questions/manager.ts +14 -4
- package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/src/resources/extensions/remote-questions/types.ts +2 -1
- package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/src/resources/extensions/voice/index.ts +4 -3
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
// gsd-tools — Structured LLM tool tests
|
|
2
|
+
//
|
|
3
|
+
// Tests the three registered tools: gsd_save_decision, gsd_update_requirement, gsd_save_summary.
|
|
4
|
+
// Each tool is tested via direct function invocation against an in-memory DB.
|
|
5
|
+
|
|
6
|
+
import { createTestContext } from './test-helpers.ts';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import * as os from 'node:os';
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import {
|
|
11
|
+
openDatabase,
|
|
12
|
+
closeDatabase,
|
|
13
|
+
isDbAvailable,
|
|
14
|
+
upsertRequirement,
|
|
15
|
+
getRequirementById,
|
|
16
|
+
getDecisionById,
|
|
17
|
+
_getAdapter,
|
|
18
|
+
insertArtifact,
|
|
19
|
+
} from '../gsd-db.ts';
|
|
20
|
+
import {
|
|
21
|
+
saveDecisionToDb,
|
|
22
|
+
updateRequirementInDb,
|
|
23
|
+
saveArtifactToDb,
|
|
24
|
+
nextDecisionId,
|
|
25
|
+
} from '../db-writer.ts';
|
|
26
|
+
import type { Requirement } from '../types.ts';
|
|
27
|
+
|
|
28
|
+
const { assertEq, assertTrue, assertMatch, report } = createTestContext();
|
|
29
|
+
|
|
30
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
31
|
+
// Helpers
|
|
32
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
33
|
+
|
|
34
|
+
function makeTmpDir(): string {
|
|
35
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-tools-'));
|
|
36
|
+
fs.mkdirSync(path.join(dir, '.gsd'), { recursive: true });
|
|
37
|
+
return dir;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function cleanupDir(dir: string): void {
|
|
41
|
+
try {
|
|
42
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
43
|
+
} catch { /* swallow */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Simulate tool execute by calling the underlying DB functions directly.
|
|
48
|
+
* The actual tool registration happens in index.ts; here we test the
|
|
49
|
+
* execute logic pattern: check DB → call writer → return result.
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
53
|
+
// gsd_save_decision tool tests
|
|
54
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
55
|
+
|
|
56
|
+
console.log('\n── gsd_save_decision ──');
|
|
57
|
+
|
|
58
|
+
{
|
|
59
|
+
const tmpDir = makeTmpDir();
|
|
60
|
+
try {
|
|
61
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
62
|
+
openDatabase(dbPath);
|
|
63
|
+
assertTrue(isDbAvailable(), 'DB should be available after open');
|
|
64
|
+
|
|
65
|
+
// (a) Decision tool creates DB row + returns new ID
|
|
66
|
+
const result = await saveDecisionToDb(
|
|
67
|
+
{
|
|
68
|
+
scope: 'architecture',
|
|
69
|
+
decision: 'Use SQLite for metadata',
|
|
70
|
+
choice: 'SQLite',
|
|
71
|
+
rationale: 'Sync API fits the CLI model',
|
|
72
|
+
revisable: 'Yes',
|
|
73
|
+
when_context: 'M001',
|
|
74
|
+
},
|
|
75
|
+
tmpDir,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
assertEq(result.id, 'D001', 'First decision should be D001');
|
|
79
|
+
|
|
80
|
+
// Verify DB row exists
|
|
81
|
+
const row = getDecisionById('D001');
|
|
82
|
+
assertTrue(row !== null, 'Decision D001 should exist in DB');
|
|
83
|
+
assertEq(row!.scope, 'architecture', 'Decision scope should match');
|
|
84
|
+
assertEq(row!.decision, 'Use SQLite for metadata', 'Decision text should match');
|
|
85
|
+
assertEq(row!.choice, 'SQLite', 'Decision choice should match');
|
|
86
|
+
|
|
87
|
+
// Verify DECISIONS.md was generated
|
|
88
|
+
const mdPath = path.join(tmpDir, '.gsd', 'DECISIONS.md');
|
|
89
|
+
assertTrue(fs.existsSync(mdPath), 'DECISIONS.md should be created');
|
|
90
|
+
const mdContent = fs.readFileSync(mdPath, 'utf-8');
|
|
91
|
+
assertTrue(mdContent.includes('D001'), 'DECISIONS.md should contain D001');
|
|
92
|
+
assertTrue(mdContent.includes('SQLite'), 'DECISIONS.md should contain choice');
|
|
93
|
+
|
|
94
|
+
// (e) Decision tool auto-assigns correct next ID
|
|
95
|
+
const result2 = await saveDecisionToDb(
|
|
96
|
+
{
|
|
97
|
+
scope: 'testing',
|
|
98
|
+
decision: 'Test runner',
|
|
99
|
+
choice: 'vitest',
|
|
100
|
+
rationale: 'Fast and ESM-native',
|
|
101
|
+
},
|
|
102
|
+
tmpDir,
|
|
103
|
+
);
|
|
104
|
+
assertEq(result2.id, 'D002', 'Second decision should be D002');
|
|
105
|
+
|
|
106
|
+
const result3 = await saveDecisionToDb(
|
|
107
|
+
{
|
|
108
|
+
scope: 'CI',
|
|
109
|
+
decision: 'CI platform',
|
|
110
|
+
choice: 'GitHub Actions',
|
|
111
|
+
rationale: 'Integrated with repo',
|
|
112
|
+
},
|
|
113
|
+
tmpDir,
|
|
114
|
+
);
|
|
115
|
+
assertEq(result3.id, 'D003', 'Third decision should be D003');
|
|
116
|
+
|
|
117
|
+
closeDatabase();
|
|
118
|
+
} finally {
|
|
119
|
+
cleanupDir(tmpDir);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
124
|
+
// gsd_update_requirement tool tests
|
|
125
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
126
|
+
|
|
127
|
+
console.log('\n── gsd_update_requirement ──');
|
|
128
|
+
|
|
129
|
+
{
|
|
130
|
+
const tmpDir = makeTmpDir();
|
|
131
|
+
try {
|
|
132
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
133
|
+
openDatabase(dbPath);
|
|
134
|
+
|
|
135
|
+
// Seed a requirement
|
|
136
|
+
const seedReq: Requirement = {
|
|
137
|
+
id: 'R001',
|
|
138
|
+
class: 'functional',
|
|
139
|
+
status: 'active',
|
|
140
|
+
description: 'Must support SQLite storage',
|
|
141
|
+
why: 'Structured data needs',
|
|
142
|
+
source: 'design',
|
|
143
|
+
primary_owner: 'S03',
|
|
144
|
+
supporting_slices: '',
|
|
145
|
+
validation: '',
|
|
146
|
+
notes: '',
|
|
147
|
+
full_content: '',
|
|
148
|
+
superseded_by: null,
|
|
149
|
+
};
|
|
150
|
+
upsertRequirement(seedReq);
|
|
151
|
+
|
|
152
|
+
// (b) Requirement update tool modifies existing requirement
|
|
153
|
+
await updateRequirementInDb(
|
|
154
|
+
'R001',
|
|
155
|
+
{ status: 'validated', validation: 'Unit tests pass', notes: 'Verified in S06' },
|
|
156
|
+
tmpDir,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const updated = getRequirementById('R001');
|
|
160
|
+
assertTrue(updated !== null, 'R001 should still exist');
|
|
161
|
+
assertEq(updated!.status, 'validated', 'Status should be updated');
|
|
162
|
+
assertEq(updated!.validation, 'Unit tests pass', 'Validation should be updated');
|
|
163
|
+
assertEq(updated!.notes, 'Verified in S06', 'Notes should be updated');
|
|
164
|
+
// Original fields preserved
|
|
165
|
+
assertEq(updated!.description, 'Must support SQLite storage', 'Description should be preserved');
|
|
166
|
+
assertEq(updated!.primary_owner, 'S03', 'Primary owner should be preserved');
|
|
167
|
+
|
|
168
|
+
// Verify REQUIREMENTS.md was generated
|
|
169
|
+
const mdPath = path.join(tmpDir, '.gsd', 'REQUIREMENTS.md');
|
|
170
|
+
assertTrue(fs.existsSync(mdPath), 'REQUIREMENTS.md should be created');
|
|
171
|
+
const mdContent = fs.readFileSync(mdPath, 'utf-8');
|
|
172
|
+
assertTrue(mdContent.includes('R001'), 'REQUIREMENTS.md should contain R001');
|
|
173
|
+
assertTrue(mdContent.includes('validated'), 'REQUIREMENTS.md should reflect updated status');
|
|
174
|
+
|
|
175
|
+
// Updating non-existent requirement throws
|
|
176
|
+
let threwForMissing = false;
|
|
177
|
+
try {
|
|
178
|
+
await updateRequirementInDb('R999', { status: 'deferred' }, tmpDir);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
threwForMissing = true;
|
|
181
|
+
assertTrue(
|
|
182
|
+
(err as Error).message.includes('R999'),
|
|
183
|
+
'Error should mention the missing requirement ID',
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
assertTrue(threwForMissing, 'Should throw for non-existent requirement');
|
|
187
|
+
|
|
188
|
+
closeDatabase();
|
|
189
|
+
} finally {
|
|
190
|
+
cleanupDir(tmpDir);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
195
|
+
// gsd_save_summary tool tests
|
|
196
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
197
|
+
|
|
198
|
+
console.log('\n── gsd_save_summary ──');
|
|
199
|
+
|
|
200
|
+
{
|
|
201
|
+
const tmpDir = makeTmpDir();
|
|
202
|
+
try {
|
|
203
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
204
|
+
openDatabase(dbPath);
|
|
205
|
+
|
|
206
|
+
// (c) Summary tool creates artifact row
|
|
207
|
+
await saveArtifactToDb(
|
|
208
|
+
{
|
|
209
|
+
path: 'milestones/M001/slices/S01/S01-SUMMARY.md',
|
|
210
|
+
artifact_type: 'SUMMARY',
|
|
211
|
+
content: '# S01 Summary\n\nThis is a test summary.',
|
|
212
|
+
milestone_id: 'M001',
|
|
213
|
+
slice_id: 'S01',
|
|
214
|
+
},
|
|
215
|
+
tmpDir,
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// Verify artifact in DB
|
|
219
|
+
const adapter = _getAdapter();
|
|
220
|
+
assertTrue(adapter !== null, 'Adapter should be available');
|
|
221
|
+
const rows = adapter!.prepare(
|
|
222
|
+
"SELECT * FROM artifacts WHERE path = 'milestones/M001/slices/S01/S01-SUMMARY.md'",
|
|
223
|
+
).all();
|
|
224
|
+
assertEq(rows.length, 1, 'Should have 1 artifact row');
|
|
225
|
+
assertEq(rows[0]['artifact_type'] as string, 'SUMMARY', 'Artifact type should be SUMMARY');
|
|
226
|
+
assertEq(rows[0]['milestone_id'] as string, 'M001', 'Milestone ID should match');
|
|
227
|
+
assertEq(rows[0]['slice_id'] as string, 'S01', 'Slice ID should match');
|
|
228
|
+
|
|
229
|
+
// Verify file was written to disk
|
|
230
|
+
const filePath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-SUMMARY.md');
|
|
231
|
+
assertTrue(fs.existsSync(filePath), 'Summary file should be written to disk');
|
|
232
|
+
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
233
|
+
assertTrue(fileContent.includes('S01 Summary'), 'File should contain summary content');
|
|
234
|
+
|
|
235
|
+
// Test milestone-level artifact (no slice_id)
|
|
236
|
+
await saveArtifactToDb(
|
|
237
|
+
{
|
|
238
|
+
path: 'milestones/M001/M001-CONTEXT.md',
|
|
239
|
+
artifact_type: 'CONTEXT',
|
|
240
|
+
content: '# M001 Context\n\nContext notes.',
|
|
241
|
+
milestone_id: 'M001',
|
|
242
|
+
},
|
|
243
|
+
tmpDir,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const mFilePath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'M001-CONTEXT.md');
|
|
247
|
+
assertTrue(fs.existsSync(mFilePath), 'Milestone-level artifact file should be created');
|
|
248
|
+
|
|
249
|
+
// Test task-level artifact
|
|
250
|
+
await saveArtifactToDb(
|
|
251
|
+
{
|
|
252
|
+
path: 'milestones/M001/slices/S01/tasks/T01-SUMMARY.md',
|
|
253
|
+
artifact_type: 'SUMMARY',
|
|
254
|
+
content: '# T01 Summary\n\nTask summary.',
|
|
255
|
+
milestone_id: 'M001',
|
|
256
|
+
slice_id: 'S01',
|
|
257
|
+
task_id: 'T01',
|
|
258
|
+
},
|
|
259
|
+
tmpDir,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const tFilePath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'tasks', 'T01-SUMMARY.md');
|
|
263
|
+
assertTrue(fs.existsSync(tFilePath), 'Task-level artifact file should be created');
|
|
264
|
+
|
|
265
|
+
closeDatabase();
|
|
266
|
+
} finally {
|
|
267
|
+
cleanupDir(tmpDir);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
272
|
+
// DB unavailable error paths
|
|
273
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
274
|
+
|
|
275
|
+
console.log('\n── DB unavailable error paths ──');
|
|
276
|
+
|
|
277
|
+
{
|
|
278
|
+
// (d) All tools return isError when DB unavailable
|
|
279
|
+
// Close any open DB and don't open a new one
|
|
280
|
+
try { closeDatabase(); } catch { /* already closed */ }
|
|
281
|
+
|
|
282
|
+
// isDbAvailable() should return false
|
|
283
|
+
assertTrue(!isDbAvailable(), 'DB should be unavailable after close');
|
|
284
|
+
|
|
285
|
+
// nextDecisionId degrades gracefully
|
|
286
|
+
const fallbackId = await nextDecisionId();
|
|
287
|
+
assertEq(fallbackId, 'D001', 'nextDecisionId should return D001 when DB unavailable');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
291
|
+
// Tool result format verification
|
|
292
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
293
|
+
|
|
294
|
+
console.log('\n── Tool result format ──');
|
|
295
|
+
|
|
296
|
+
{
|
|
297
|
+
const tmpDir = makeTmpDir();
|
|
298
|
+
try {
|
|
299
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
300
|
+
openDatabase(dbPath);
|
|
301
|
+
|
|
302
|
+
// Verify result follows AgentToolResult interface: {content: [{type: "text", text}], details}
|
|
303
|
+
const result = await saveDecisionToDb(
|
|
304
|
+
{
|
|
305
|
+
scope: 'format-test',
|
|
306
|
+
decision: 'Test format',
|
|
307
|
+
choice: 'TypeBox',
|
|
308
|
+
rationale: 'Schema validation',
|
|
309
|
+
},
|
|
310
|
+
tmpDir,
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
// The saveDecisionToDb returns {id} — the tool wrapping adds the AgentToolResult shape.
|
|
314
|
+
// Verify the raw function returns the expected shape.
|
|
315
|
+
assertTrue(typeof result.id === 'string', 'saveDecisionToDb should return {id: string}');
|
|
316
|
+
assertMatch(result.id, /^D\d{3}$/, 'ID should match DXXX pattern');
|
|
317
|
+
|
|
318
|
+
closeDatabase();
|
|
319
|
+
} finally {
|
|
320
|
+
cleanupDir(tmpDir);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
325
|
+
|
|
326
|
+
report();
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// Integration Edge Case Tests
|
|
2
|
+
//
|
|
3
|
+
// Three scenarios that only had per-module coverage before:
|
|
4
|
+
// 1. Empty project — no markdown files → migration finds nothing → queries return empty
|
|
5
|
+
// 2. Partial migration — DECISIONS.md exists but no REQUIREMENTS.md → no crash
|
|
6
|
+
// 3. Fallback mode — _resetProvider → queries degrade → re-open restores
|
|
7
|
+
//
|
|
8
|
+
// Uses real module imports (no mocks), file-backed DBs, temp directories.
|
|
9
|
+
|
|
10
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { tmpdir } from 'node:os';
|
|
13
|
+
|
|
14
|
+
import { openDatabase, closeDatabase, isDbAvailable, _resetProvider } from '../gsd-db.ts';
|
|
15
|
+
import { migrateFromMarkdown } from '../md-importer.ts';
|
|
16
|
+
import {
|
|
17
|
+
queryDecisions,
|
|
18
|
+
queryRequirements,
|
|
19
|
+
formatDecisionsForPrompt,
|
|
20
|
+
formatRequirementsForPrompt,
|
|
21
|
+
} from '../context-store.ts';
|
|
22
|
+
import { createTestContext } from './test-helpers.ts';
|
|
23
|
+
|
|
24
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
25
|
+
|
|
26
|
+
// ─── Fixture Helper ────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
function generateDecisionsMarkdown(count: number): string {
|
|
29
|
+
const lines: string[] = [
|
|
30
|
+
'# Decisions Register',
|
|
31
|
+
'',
|
|
32
|
+
'<!-- Append-only. Never edit or remove existing rows. -->',
|
|
33
|
+
'',
|
|
34
|
+
'| # | When | Scope | Decision | Choice | Rationale | Revisable? |',
|
|
35
|
+
'|---|------|-------|----------|--------|-----------|------------|',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
for (let i = 1; i <= count; i++) {
|
|
39
|
+
const id = `D${String(i).padStart(3, '0')}`;
|
|
40
|
+
const milestone = i <= 3 ? 'M001' : 'M002';
|
|
41
|
+
lines.push(`| ${id} | ${milestone}/S01 | testing | decision ${i} text | choice ${i} | rationale ${i} | yes |`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return lines.join('\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
48
|
+
// Edge Case 1: Empty Project
|
|
49
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
50
|
+
|
|
51
|
+
console.log('\n=== integration-edge: empty project ===');
|
|
52
|
+
{
|
|
53
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-int-edge-empty-'));
|
|
54
|
+
const gsdDir = join(base, '.gsd');
|
|
55
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
56
|
+
|
|
57
|
+
const dbPath = join(gsdDir, 'test-edge-empty.db');
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Open DB first so migrateFromMarkdown doesn't auto-create at default path
|
|
61
|
+
openDatabase(dbPath);
|
|
62
|
+
assertTrue(isDbAvailable(), 'empty: DB available after open');
|
|
63
|
+
|
|
64
|
+
// Migrate with no markdown files on disk
|
|
65
|
+
const result = migrateFromMarkdown(base);
|
|
66
|
+
|
|
67
|
+
assertEq(result.decisions, 0, 'empty: 0 decisions imported');
|
|
68
|
+
assertEq(result.requirements, 0, 'empty: 0 requirements imported');
|
|
69
|
+
assertEq(result.artifacts, 0, 'empty: 0 artifacts imported');
|
|
70
|
+
|
|
71
|
+
// Query decisions → empty array
|
|
72
|
+
const decisions = queryDecisions();
|
|
73
|
+
assertEq(decisions.length, 0, 'empty: queryDecisions returns empty array');
|
|
74
|
+
|
|
75
|
+
// Query requirements → empty array
|
|
76
|
+
const requirements = queryRequirements();
|
|
77
|
+
assertEq(requirements.length, 0, 'empty: queryRequirements returns empty array');
|
|
78
|
+
|
|
79
|
+
// Query with scope filters → still empty, no crash
|
|
80
|
+
const scopedDecisions = queryDecisions({ milestoneId: 'M001' });
|
|
81
|
+
assertEq(scopedDecisions.length, 0, 'empty: scoped queryDecisions returns empty');
|
|
82
|
+
|
|
83
|
+
const scopedRequirements = queryRequirements({ sliceId: 'S01' });
|
|
84
|
+
assertEq(scopedRequirements.length, 0, 'empty: scoped queryRequirements returns empty');
|
|
85
|
+
|
|
86
|
+
// Format empty results → empty strings
|
|
87
|
+
const formattedD = formatDecisionsForPrompt([]);
|
|
88
|
+
const formattedR = formatRequirementsForPrompt([]);
|
|
89
|
+
assertEq(formattedD, '', 'empty: formatDecisionsForPrompt returns empty string');
|
|
90
|
+
assertEq(formattedR, '', 'empty: formatRequirementsForPrompt returns empty string');
|
|
91
|
+
|
|
92
|
+
// Format with actual empty query results
|
|
93
|
+
const formattedD2 = formatDecisionsForPrompt(decisions);
|
|
94
|
+
const formattedR2 = formatRequirementsForPrompt(requirements);
|
|
95
|
+
assertEq(formattedD2, '', 'empty: format of empty query decisions is empty string');
|
|
96
|
+
assertEq(formattedR2, '', 'empty: format of empty query requirements is empty string');
|
|
97
|
+
|
|
98
|
+
closeDatabase();
|
|
99
|
+
} finally {
|
|
100
|
+
closeDatabase();
|
|
101
|
+
rmSync(base, { recursive: true, force: true });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
106
|
+
// Edge Case 2: Partial Migration (decisions only, no requirements)
|
|
107
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
108
|
+
|
|
109
|
+
console.log('\n=== integration-edge: partial migration ===');
|
|
110
|
+
{
|
|
111
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-int-edge-partial-'));
|
|
112
|
+
const gsdDir = join(base, '.gsd');
|
|
113
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
114
|
+
|
|
115
|
+
// Write DECISIONS.md but NOT REQUIREMENTS.md
|
|
116
|
+
const decisionsMarkdown = generateDecisionsMarkdown(6);
|
|
117
|
+
writeFileSync(join(gsdDir, 'DECISIONS.md'), decisionsMarkdown);
|
|
118
|
+
|
|
119
|
+
const dbPath = join(gsdDir, 'test-edge-partial.db');
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
openDatabase(dbPath);
|
|
123
|
+
assertTrue(isDbAvailable(), 'partial: DB available after open');
|
|
124
|
+
|
|
125
|
+
const result = migrateFromMarkdown(base);
|
|
126
|
+
|
|
127
|
+
// Decisions imported, requirements skipped gracefully
|
|
128
|
+
assertTrue(result.decisions === 6, `partial: imported ${result.decisions} decisions, expected 6`);
|
|
129
|
+
assertEq(result.requirements, 0, 'partial: 0 requirements imported (no file)');
|
|
130
|
+
|
|
131
|
+
// Decisions queryable
|
|
132
|
+
const decisions = queryDecisions();
|
|
133
|
+
assertTrue(decisions.length === 6, `partial: queryDecisions returns 6 (got ${decisions.length})`);
|
|
134
|
+
|
|
135
|
+
const m001Decisions = queryDecisions({ milestoneId: 'M001' });
|
|
136
|
+
assertTrue(m001Decisions.length > 0, 'partial: M001 decisions non-empty');
|
|
137
|
+
assertTrue(m001Decisions.length < decisions.length, 'partial: M001 scope filters correctly');
|
|
138
|
+
|
|
139
|
+
// Requirements return empty — no crash
|
|
140
|
+
const requirements = queryRequirements();
|
|
141
|
+
assertEq(requirements.length, 0, 'partial: queryRequirements returns empty');
|
|
142
|
+
|
|
143
|
+
const scopedReqs = queryRequirements({ sliceId: 'S01' });
|
|
144
|
+
assertEq(scopedReqs.length, 0, 'partial: scoped queryRequirements returns empty');
|
|
145
|
+
|
|
146
|
+
// Format works on partial data
|
|
147
|
+
const formattedD = formatDecisionsForPrompt(m001Decisions);
|
|
148
|
+
assertTrue(formattedD.length > 0, 'partial: formatted decisions non-empty');
|
|
149
|
+
|
|
150
|
+
const formattedR = formatRequirementsForPrompt(requirements);
|
|
151
|
+
assertEq(formattedR, '', 'partial: formatted empty requirements is empty string');
|
|
152
|
+
|
|
153
|
+
closeDatabase();
|
|
154
|
+
} finally {
|
|
155
|
+
closeDatabase();
|
|
156
|
+
rmSync(base, { recursive: true, force: true });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
161
|
+
// Edge Case 3: Fallback Mode (_resetProvider)
|
|
162
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
163
|
+
|
|
164
|
+
console.log('\n=== integration-edge: fallback mode ===');
|
|
165
|
+
{
|
|
166
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-int-edge-fallback-'));
|
|
167
|
+
const gsdDir = join(base, '.gsd');
|
|
168
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
169
|
+
|
|
170
|
+
const decisionsMarkdown = generateDecisionsMarkdown(4);
|
|
171
|
+
writeFileSync(join(gsdDir, 'DECISIONS.md'), decisionsMarkdown);
|
|
172
|
+
|
|
173
|
+
const dbPath = join(gsdDir, 'test-edge-fallback.db');
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
// Step 1: Open DB normally and verify it works
|
|
177
|
+
openDatabase(dbPath);
|
|
178
|
+
assertTrue(isDbAvailable(), 'fallback: DB available after open');
|
|
179
|
+
|
|
180
|
+
migrateFromMarkdown(base);
|
|
181
|
+
const before = queryDecisions();
|
|
182
|
+
assertTrue(before.length === 4, `fallback: 4 decisions before reset (got ${before.length})`);
|
|
183
|
+
|
|
184
|
+
// Step 2: Close and reset provider → DB unavailable
|
|
185
|
+
closeDatabase();
|
|
186
|
+
_resetProvider();
|
|
187
|
+
assertTrue(!isDbAvailable(), 'fallback: DB unavailable after _resetProvider');
|
|
188
|
+
|
|
189
|
+
// Step 3: Queries degrade gracefully (return empty, don't throw)
|
|
190
|
+
const degradedDecisions = queryDecisions();
|
|
191
|
+
assertEq(degradedDecisions.length, 0, 'fallback: queryDecisions returns empty when unavailable');
|
|
192
|
+
|
|
193
|
+
const degradedRequirements = queryRequirements();
|
|
194
|
+
assertEq(degradedRequirements.length, 0, 'fallback: queryRequirements returns empty when unavailable');
|
|
195
|
+
|
|
196
|
+
const degradedScopedD = queryDecisions({ milestoneId: 'M001' });
|
|
197
|
+
assertEq(degradedScopedD.length, 0, 'fallback: scoped queryDecisions returns empty when unavailable');
|
|
198
|
+
|
|
199
|
+
const degradedScopedR = queryRequirements({ sliceId: 'S01' });
|
|
200
|
+
assertEq(degradedScopedR.length, 0, 'fallback: scoped queryRequirements returns empty when unavailable');
|
|
201
|
+
|
|
202
|
+
// Format functions work on empty arrays (no crash)
|
|
203
|
+
const formattedD = formatDecisionsForPrompt(degradedDecisions);
|
|
204
|
+
assertEq(formattedD, '', 'fallback: format degraded decisions is empty');
|
|
205
|
+
|
|
206
|
+
const formattedR = formatRequirementsForPrompt(degradedRequirements);
|
|
207
|
+
assertEq(formattedR, '', 'fallback: format degraded requirements is empty');
|
|
208
|
+
|
|
209
|
+
// Step 4: Re-open DB → restores availability
|
|
210
|
+
openDatabase(dbPath);
|
|
211
|
+
assertTrue(isDbAvailable(), 'fallback: DB available after re-open');
|
|
212
|
+
|
|
213
|
+
// Data should be there from the file-backed DB (persisted by first open)
|
|
214
|
+
// But rows may need re-import since the DB was freshly opened from the file
|
|
215
|
+
migrateFromMarkdown(base);
|
|
216
|
+
const restored = queryDecisions();
|
|
217
|
+
assertTrue(restored.length === 4, `fallback: 4 decisions after re-open (got ${restored.length})`);
|
|
218
|
+
|
|
219
|
+
closeDatabase();
|
|
220
|
+
} finally {
|
|
221
|
+
closeDatabase();
|
|
222
|
+
rmSync(base, { recursive: true, force: true });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ─── Report ────────────────────────────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
report();
|