gsd-pi 2.44.0-dev.62b5d6c → 2.44.0-dev.a5271fc
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 +30 -12
- package/dist/resources/extensions/gsd/auto-start.js +10 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -12
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +5 -0
- package/dist/resources/extensions/gsd/preferences.js +9 -1
- package/dist/resources/extensions/gsd/state.js +19 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- 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/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 +19 -19
- 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/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +6 -8
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +24 -26
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js +29 -48
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +34 -44
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +30 -34
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +10 -12
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +43 -47
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
- package/packages/pi-coding-agent/src/core/fs-utils.test.ts +31 -43
- package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +40 -45
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
- package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
- package/src/resources/extensions/gsd/auto-start.ts +14 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +48 -11
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +8 -0
- package/src/resources/extensions/gsd/preferences.ts +11 -1
- package/src/resources/extensions/gsd/state.ts +19 -1
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +14 -16
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +43 -57
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +11 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +465 -523
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +73 -75
- package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +34 -56
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +533 -656
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +165 -143
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +29 -52
- package/src/resources/extensions/gsd/tests/captures.test.ts +148 -176
- package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +32 -33
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +141 -143
- package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
- package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +38 -59
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +228 -263
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +250 -302
- package/src/resources/extensions/gsd/tests/context-store.test.ts +354 -367
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +68 -72
- package/src/resources/extensions/gsd/tests/cost-projection.test.ts +92 -106
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +27 -35
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +220 -237
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +390 -420
- package/src/resources/extensions/gsd/tests/definition-loader.test.ts +76 -92
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +68 -83
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +183 -181
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +78 -101
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +192 -227
- package/src/resources/extensions/gsd/tests/detection.test.ts +232 -278
- package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +30 -34
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +164 -180
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +43 -49
- package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +28 -32
- package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +27 -29
- package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +34 -38
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +54 -75
- package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +21 -32
- package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +72 -97
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +38 -44
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +104 -145
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +84 -106
- package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +54 -60
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +72 -93
- package/src/resources/extensions/gsd/tests/doctor.test.ts +104 -134
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +123 -131
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +20 -24
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +48 -57
- package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +5 -7
- package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +30 -42
- package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +198 -206
- package/src/resources/extensions/gsd/tests/git-locale.test.ts +13 -27
- package/src/resources/extensions/gsd/tests/git-service.test.ts +285 -388
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +31 -39
- package/src/resources/extensions/gsd/tests/graph-operations.test.ts +63 -69
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +255 -264
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +108 -119
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +81 -103
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +229 -262
- package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +29 -37
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +81 -102
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +16 -18
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +41 -46
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +42 -53
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +75 -91
- package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +150 -194
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +101 -125
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +45 -54
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +80 -93
- package/src/resources/extensions/gsd/tests/migrate-command.test.ts +57 -66
- package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +83 -93
- package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +161 -170
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +125 -141
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +107 -131
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +87 -96
- package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +125 -164
- package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +81 -94
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +35 -36
- package/src/resources/extensions/gsd/tests/overrides.test.ts +99 -106
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +40 -47
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +25 -28
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +66 -83
- package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +54 -77
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +68 -115
- package/src/resources/extensions/gsd/tests/parsers.test.ts +546 -611
- package/src/resources/extensions/gsd/tests/paths.test.ts +72 -87
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +77 -117
- package/src/resources/extensions/gsd/tests/preferences.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
- package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +93 -119
- package/src/resources/extensions/gsd/tests/queue-order.test.ts +70 -82
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +42 -55
- package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +45 -73
- package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +28 -38
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +73 -80
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +71 -74
- package/src/resources/extensions/gsd/tests/requirements.test.ts +70 -75
- package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +44 -66
- package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +114 -181
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +63 -65
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +66 -128
- package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +18 -25
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +37 -44
- package/src/resources/extensions/gsd/tests/shared-wal.test.ts +19 -26
- package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +6 -8
- package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +22 -28
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +54 -56
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +23 -25
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +9 -11
- package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +66 -82
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +46 -47
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -22
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -86
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +41 -43
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +94 -96
- package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +11 -13
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +27 -29
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +50 -52
- package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +10 -13
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +14 -18
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +38 -39
- package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +17 -21
- package/src/resources/extensions/gsd/tests/worktree-health.test.ts +25 -30
- package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +30 -37
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +15 -22
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +59 -66
- package/src/resources/extensions/gsd/tests/worktree.test.ts +44 -50
- /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → JyimLR2pZuvKEzv26gI3w}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → JyimLR2pZuvKEzv26gI3w}/_ssgManifest.js +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { describe, test, afterEach } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
2
3
|
import {
|
|
3
4
|
openDatabase,
|
|
4
5
|
closeDatabase,
|
|
@@ -16,452 +17,438 @@ import {
|
|
|
16
17
|
queryProject,
|
|
17
18
|
} from '../context-store.ts';
|
|
18
19
|
|
|
19
|
-
const { assertEq, assertTrue, assertMatch, report } = createTestContext();
|
|
20
|
-
|
|
21
20
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
22
21
|
// context-store: fallback when DB not open
|
|
23
22
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
describe("context-store: fallback when DB not open", () => {
|
|
25
|
+
test("returns empty when DB not open", () => {
|
|
26
|
+
closeDatabase();
|
|
27
|
+
assert.ok(!isDbAvailable(), 'DB should not be available');
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
const d = queryDecisions();
|
|
30
|
+
assert.deepStrictEqual(d, [], 'queryDecisions returns [] when DB closed');
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
const r = queryRequirements();
|
|
33
|
+
assert.deepStrictEqual(r, [], 'queryRequirements returns [] when DB closed');
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const df = queryDecisions({ milestoneId: 'M001' });
|
|
36
|
+
assert.deepStrictEqual(df, [], 'queryDecisions with opts returns [] when DB closed');
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
38
|
+
const rf = queryRequirements({ sliceId: 'S01' });
|
|
39
|
+
assert.deepStrictEqual(rf, [], 'queryRequirements with opts returns [] when DB closed');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
42
|
|
|
43
43
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
44
44
|
// context-store: query decisions
|
|
45
45
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
openDatabase(':memory:');
|
|
47
|
+
describe("context-store: query decisions", () => {
|
|
48
|
+
afterEach(() => closeDatabase());
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
decision: 'use SQLite', choice: 'node:sqlite', rationale: 'built-in',
|
|
54
|
-
revisable: 'yes', made_by: 'agent', superseded_by: 'D003', // superseded!
|
|
55
|
-
});
|
|
56
|
-
insertDecision({
|
|
57
|
-
id: 'D002', when_context: 'M001/S01', scope: 'architecture',
|
|
58
|
-
decision: 'use WAL mode', choice: 'WAL', rationale: 'concurrent reads',
|
|
59
|
-
revisable: 'no', made_by: 'agent', superseded_by: null,
|
|
60
|
-
});
|
|
61
|
-
insertDecision({
|
|
62
|
-
id: 'D003', when_context: 'M002/S01', scope: 'performance',
|
|
63
|
-
decision: 'use better-sqlite3', choice: 'better-sqlite3', rationale: 'faster',
|
|
64
|
-
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
65
|
-
});
|
|
50
|
+
test("query all active decisions", () => {
|
|
51
|
+
openDatabase(':memory:');
|
|
66
52
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
id: 'D001', when_context: 'M001/S01', scope: 'architecture',
|
|
83
|
-
decision: 'decision A', choice: 'A', rationale: 'r', revisable: 'yes',
|
|
84
|
-
made_by: 'agent',
|
|
85
|
-
superseded_by: null,
|
|
86
|
-
});
|
|
87
|
-
insertDecision({
|
|
88
|
-
id: 'D002', when_context: 'M002/S02', scope: 'architecture',
|
|
89
|
-
decision: 'decision B', choice: 'B', rationale: 'r', revisable: 'yes',
|
|
90
|
-
made_by: 'agent',
|
|
91
|
-
superseded_by: null,
|
|
92
|
-
});
|
|
53
|
+
insertDecision({
|
|
54
|
+
id: 'D001', when_context: 'M001/S01', scope: 'architecture',
|
|
55
|
+
decision: 'use SQLite', choice: 'node:sqlite', rationale: 'built-in',
|
|
56
|
+
revisable: 'yes', made_by: 'agent', superseded_by: 'D003', // superseded!
|
|
57
|
+
});
|
|
58
|
+
insertDecision({
|
|
59
|
+
id: 'D002', when_context: 'M001/S01', scope: 'architecture',
|
|
60
|
+
decision: 'use WAL mode', choice: 'WAL', rationale: 'concurrent reads',
|
|
61
|
+
revisable: 'no', made_by: 'agent', superseded_by: null,
|
|
62
|
+
});
|
|
63
|
+
insertDecision({
|
|
64
|
+
id: 'D003', when_context: 'M002/S01', scope: 'performance',
|
|
65
|
+
decision: 'use better-sqlite3', choice: 'better-sqlite3', rationale: 'faster',
|
|
66
|
+
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
67
|
+
});
|
|
93
68
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
69
|
+
const all = queryDecisions();
|
|
70
|
+
assert.strictEqual(all.length, 2, 'query all active decisions returns 2 (superseded excluded)');
|
|
71
|
+
const ids = all.map(d => d.id);
|
|
72
|
+
assert.ok(ids.includes('D002'), 'D002 should be in active results');
|
|
73
|
+
assert.ok(ids.includes('D003'), 'D003 should be in active results');
|
|
74
|
+
assert.ok(!ids.includes('D001'), 'D001 (superseded) should NOT be in active results');
|
|
75
|
+
});
|
|
97
76
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
assertEq(m2[0]?.id, 'D002', 'milestone filter returns D002');
|
|
77
|
+
test("query decisions by milestone", () => {
|
|
78
|
+
openDatabase(':memory:');
|
|
101
79
|
|
|
102
|
-
|
|
103
|
-
|
|
80
|
+
insertDecision({
|
|
81
|
+
id: 'D001', when_context: 'M001/S01', scope: 'architecture',
|
|
82
|
+
decision: 'decision A', choice: 'A', rationale: 'r', revisable: 'yes',
|
|
83
|
+
made_by: 'agent',
|
|
84
|
+
superseded_by: null,
|
|
85
|
+
});
|
|
86
|
+
insertDecision({
|
|
87
|
+
id: 'D002', when_context: 'M002/S02', scope: 'architecture',
|
|
88
|
+
decision: 'decision B', choice: 'B', rationale: 'r', revisable: 'yes',
|
|
89
|
+
made_by: 'agent',
|
|
90
|
+
superseded_by: null,
|
|
91
|
+
});
|
|
104
92
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
93
|
+
const m1 = queryDecisions({ milestoneId: 'M001' });
|
|
94
|
+
assert.strictEqual(m1.length, 1, 'milestone filter M001 returns 1');
|
|
95
|
+
assert.strictEqual(m1[0]?.id, 'D001', 'milestone filter returns D001');
|
|
108
96
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
made_by: 'agent',
|
|
113
|
-
superseded_by: null,
|
|
114
|
-
});
|
|
115
|
-
insertDecision({
|
|
116
|
-
id: 'D002', when_context: 'M001/S01', scope: 'performance',
|
|
117
|
-
decision: 'decision B', choice: 'B', rationale: 'r', revisable: 'yes',
|
|
118
|
-
made_by: 'agent',
|
|
119
|
-
superseded_by: null,
|
|
97
|
+
const m2 = queryDecisions({ milestoneId: 'M002' });
|
|
98
|
+
assert.strictEqual(m2.length, 1, 'milestone filter M002 returns 1');
|
|
99
|
+
assert.strictEqual(m2[0]?.id, 'D002', 'milestone filter returns D002');
|
|
120
100
|
});
|
|
121
101
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
102
|
+
test("query decisions by scope", () => {
|
|
103
|
+
openDatabase(':memory:');
|
|
104
|
+
|
|
105
|
+
insertDecision({
|
|
106
|
+
id: 'D001', when_context: 'M001/S01', scope: 'architecture',
|
|
107
|
+
decision: 'decision A', choice: 'A', rationale: 'r', revisable: 'yes',
|
|
108
|
+
made_by: 'agent',
|
|
109
|
+
superseded_by: null,
|
|
110
|
+
});
|
|
111
|
+
insertDecision({
|
|
112
|
+
id: 'D002', when_context: 'M001/S01', scope: 'performance',
|
|
113
|
+
decision: 'decision B', choice: 'B', rationale: 'r', revisable: 'yes',
|
|
114
|
+
made_by: 'agent',
|
|
115
|
+
superseded_by: null,
|
|
116
|
+
});
|
|
125
117
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
118
|
+
const arch = queryDecisions({ scope: 'architecture' });
|
|
119
|
+
assert.strictEqual(arch.length, 1, 'scope filter architecture returns 1');
|
|
120
|
+
assert.strictEqual(arch[0]?.id, 'D001', 'scope filter returns D001');
|
|
129
121
|
|
|
130
|
-
|
|
131
|
-
|
|
122
|
+
const perf = queryDecisions({ scope: 'performance' });
|
|
123
|
+
assert.strictEqual(perf.length, 1, 'scope filter performance returns 1');
|
|
124
|
+
assert.strictEqual(perf[0]?.id, 'D002', 'scope filter returns D002');
|
|
132
125
|
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
const none = queryDecisions({ scope: 'nonexistent' });
|
|
127
|
+
assert.strictEqual(none.length, 0, 'scope filter nonexistent returns 0');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
135
130
|
|
|
136
131
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
137
132
|
// context-store: query requirements
|
|
138
133
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
139
134
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
openDatabase(':memory:');
|
|
135
|
+
describe("context-store: query requirements", () => {
|
|
136
|
+
afterEach(() => closeDatabase());
|
|
143
137
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
description: 'req A', why: 'w', source: 'M001', primary_owner: 'S01',
|
|
147
|
-
supporting_slices: 'S02', validation: 'v', notes: '', full_content: '',
|
|
148
|
-
superseded_by: 'R003', // superseded!
|
|
149
|
-
});
|
|
150
|
-
insertRequirement({
|
|
151
|
-
id: 'R002', class: 'non-functional', status: 'active',
|
|
152
|
-
description: 'req B', why: 'w', source: 'M001', primary_owner: 'S01',
|
|
153
|
-
supporting_slices: '', validation: 'v', notes: '', full_content: '',
|
|
154
|
-
superseded_by: null,
|
|
155
|
-
});
|
|
156
|
-
insertRequirement({
|
|
157
|
-
id: 'R003', class: 'functional', status: 'validated',
|
|
158
|
-
description: 'req C', why: 'w', source: 'M001', primary_owner: 'S02',
|
|
159
|
-
supporting_slices: 'S01', validation: 'v', notes: '', full_content: '',
|
|
160
|
-
superseded_by: null,
|
|
161
|
-
});
|
|
138
|
+
test("query all active requirements", () => {
|
|
139
|
+
openDatabase(':memory:');
|
|
162
140
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
superseded_by: null,
|
|
182
|
-
});
|
|
183
|
-
insertRequirement({
|
|
184
|
-
id: 'R002', class: 'functional', status: 'active',
|
|
185
|
-
description: 'req B', why: 'w', source: 'M001', primary_owner: 'S02',
|
|
186
|
-
supporting_slices: 'S01', validation: 'v', notes: '', full_content: '',
|
|
187
|
-
superseded_by: null,
|
|
188
|
-
});
|
|
189
|
-
insertRequirement({
|
|
190
|
-
id: 'R003', class: 'functional', status: 'active',
|
|
191
|
-
description: 'req C', why: 'w', source: 'M001', primary_owner: 'S03',
|
|
192
|
-
supporting_slices: '', validation: 'v', notes: '', full_content: '',
|
|
193
|
-
superseded_by: null,
|
|
194
|
-
});
|
|
141
|
+
insertRequirement({
|
|
142
|
+
id: 'R001', class: 'functional', status: 'active',
|
|
143
|
+
description: 'req A', why: 'w', source: 'M001', primary_owner: 'S01',
|
|
144
|
+
supporting_slices: 'S02', validation: 'v', notes: '', full_content: '',
|
|
145
|
+
superseded_by: 'R003', // superseded!
|
|
146
|
+
});
|
|
147
|
+
insertRequirement({
|
|
148
|
+
id: 'R002', class: 'non-functional', status: 'active',
|
|
149
|
+
description: 'req B', why: 'w', source: 'M001', primary_owner: 'S01',
|
|
150
|
+
supporting_slices: '', validation: 'v', notes: '', full_content: '',
|
|
151
|
+
superseded_by: null,
|
|
152
|
+
});
|
|
153
|
+
insertRequirement({
|
|
154
|
+
id: 'R003', class: 'functional', status: 'validated',
|
|
155
|
+
description: 'req C', why: 'w', source: 'M001', primary_owner: 'S02',
|
|
156
|
+
supporting_slices: 'S01', validation: 'v', notes: '', full_content: '',
|
|
157
|
+
superseded_by: null,
|
|
158
|
+
});
|
|
195
159
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
160
|
+
const all = queryRequirements();
|
|
161
|
+
assert.strictEqual(all.length, 2, 'query all active requirements returns 2 (superseded excluded)');
|
|
162
|
+
const ids = all.map(r => r.id);
|
|
163
|
+
assert.ok(ids.includes('R002'), 'R002 should be active');
|
|
164
|
+
assert.ok(ids.includes('R003'), 'R003 should be active');
|
|
165
|
+
assert.ok(!ids.includes('R001'), 'R001 (superseded) should NOT be active');
|
|
166
|
+
});
|
|
200
167
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
assertEq(s03[0]?.id, 'R003', 'S03 owns R003');
|
|
168
|
+
test("query requirements by slice", () => {
|
|
169
|
+
openDatabase(':memory:');
|
|
204
170
|
|
|
205
|
-
|
|
206
|
-
|
|
171
|
+
insertRequirement({
|
|
172
|
+
id: 'R001', class: 'functional', status: 'active',
|
|
173
|
+
description: 'req A', why: 'w', source: 'M001', primary_owner: 'S01',
|
|
174
|
+
supporting_slices: '', validation: 'v', notes: '', full_content: '',
|
|
175
|
+
superseded_by: null,
|
|
176
|
+
});
|
|
177
|
+
insertRequirement({
|
|
178
|
+
id: 'R002', class: 'functional', status: 'active',
|
|
179
|
+
description: 'req B', why: 'w', source: 'M001', primary_owner: 'S02',
|
|
180
|
+
supporting_slices: 'S01', validation: 'v', notes: '', full_content: '',
|
|
181
|
+
superseded_by: null,
|
|
182
|
+
});
|
|
183
|
+
insertRequirement({
|
|
184
|
+
id: 'R003', class: 'functional', status: 'active',
|
|
185
|
+
description: 'req C', why: 'w', source: 'M001', primary_owner: 'S03',
|
|
186
|
+
supporting_slices: '', validation: 'v', notes: '', full_content: '',
|
|
187
|
+
superseded_by: null,
|
|
188
|
+
});
|
|
207
189
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
190
|
+
const s01 = queryRequirements({ sliceId: 'S01' });
|
|
191
|
+
assert.strictEqual(s01.length, 2, 'slice filter S01 returns 2 (primary + supporting)');
|
|
192
|
+
const s01ids = s01.map(r => r.id).sort();
|
|
193
|
+
assert.deepStrictEqual(s01ids, ['R001', 'R002'], 'S01 owns R001 and supports R002');
|
|
211
194
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
supporting_slices: '', validation: 'v', notes: '', full_content: '',
|
|
216
|
-
superseded_by: null,
|
|
217
|
-
});
|
|
218
|
-
insertRequirement({
|
|
219
|
-
id: 'R002', class: 'functional', status: 'validated',
|
|
220
|
-
description: 'req B', why: 'w', source: 'M001', primary_owner: 'S01',
|
|
221
|
-
supporting_slices: '', validation: 'v', notes: '', full_content: '',
|
|
222
|
-
superseded_by: null,
|
|
223
|
-
});
|
|
224
|
-
insertRequirement({
|
|
225
|
-
id: 'R003', class: 'functional', status: 'deferred',
|
|
226
|
-
description: 'req C', why: 'w', source: 'M001', primary_owner: 'S01',
|
|
227
|
-
supporting_slices: '', validation: 'v', notes: '', full_content: '',
|
|
228
|
-
superseded_by: null,
|
|
195
|
+
const s03 = queryRequirements({ sliceId: 'S03' });
|
|
196
|
+
assert.strictEqual(s03.length, 1, 'slice filter S03 returns 1');
|
|
197
|
+
assert.strictEqual(s03[0]?.id, 'R003', 'S03 owns R003');
|
|
229
198
|
});
|
|
230
199
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
200
|
+
test("query requirements by status", () => {
|
|
201
|
+
openDatabase(':memory:');
|
|
202
|
+
|
|
203
|
+
insertRequirement({
|
|
204
|
+
id: 'R001', class: 'functional', status: 'active',
|
|
205
|
+
description: 'req A', why: 'w', source: 'M001', primary_owner: 'S01',
|
|
206
|
+
supporting_slices: '', validation: 'v', notes: '', full_content: '',
|
|
207
|
+
superseded_by: null,
|
|
208
|
+
});
|
|
209
|
+
insertRequirement({
|
|
210
|
+
id: 'R002', class: 'functional', status: 'validated',
|
|
211
|
+
description: 'req B', why: 'w', source: 'M001', primary_owner: 'S01',
|
|
212
|
+
supporting_slices: '', validation: 'v', notes: '', full_content: '',
|
|
213
|
+
superseded_by: null,
|
|
214
|
+
});
|
|
215
|
+
insertRequirement({
|
|
216
|
+
id: 'R003', class: 'functional', status: 'deferred',
|
|
217
|
+
description: 'req C', why: 'w', source: 'M001', primary_owner: 'S01',
|
|
218
|
+
supporting_slices: '', validation: 'v', notes: '', full_content: '',
|
|
219
|
+
superseded_by: null,
|
|
220
|
+
});
|
|
234
221
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
222
|
+
const active = queryRequirements({ status: 'active' });
|
|
223
|
+
assert.strictEqual(active.length, 1, 'status filter active returns 1');
|
|
224
|
+
assert.strictEqual(active[0]?.id, 'R001', 'active returns R001');
|
|
238
225
|
|
|
239
|
-
|
|
240
|
-
|
|
226
|
+
const validated = queryRequirements({ status: 'validated' });
|
|
227
|
+
assert.strictEqual(validated.length, 1, 'status filter validated returns 1');
|
|
228
|
+
assert.strictEqual(validated[0]?.id, 'R002', 'validated returns R002');
|
|
229
|
+
});
|
|
230
|
+
});
|
|
241
231
|
|
|
242
232
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
243
233
|
// context-store: format decisions
|
|
244
234
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
245
235
|
|
|
246
|
-
|
|
247
|
-
{
|
|
248
|
-
|
|
249
|
-
|
|
236
|
+
describe("context-store: formatDecisionsForPrompt", () => {
|
|
237
|
+
test("empty input returns empty string", () => {
|
|
238
|
+
const empty = formatDecisionsForPrompt([]);
|
|
239
|
+
assert.strictEqual(empty, '', 'empty input returns empty string');
|
|
240
|
+
});
|
|
250
241
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
242
|
+
test("formats decisions as markdown table", () => {
|
|
243
|
+
const result = formatDecisionsForPrompt([
|
|
244
|
+
{
|
|
245
|
+
seq: 1, id: 'D001', when_context: 'M001/S01', scope: 'architecture',
|
|
246
|
+
decision: 'use SQLite', choice: 'node:sqlite', rationale: 'built-in',
|
|
247
|
+
revisable: 'yes', made_by: 'agent', superseded_by: null,
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
seq: 2, id: 'D002', when_context: 'M001/S02', scope: 'performance',
|
|
251
|
+
decision: 'use WAL', choice: 'WAL', rationale: 'concurrent',
|
|
252
|
+
revisable: 'no', made_by: 'human', superseded_by: null,
|
|
253
|
+
},
|
|
254
|
+
]);
|
|
255
|
+
|
|
256
|
+
// Should be a markdown table
|
|
257
|
+
assert.match(result, /^\| # \| When \| Scope/, 'has table header');
|
|
258
|
+
assert.match(result, /\|---\|/, 'has separator row');
|
|
259
|
+
assert.match(result, /\| D001 \|/, 'has D001 row');
|
|
260
|
+
assert.match(result, /\| D002 \|/, 'has D002 row');
|
|
261
|
+
const lines = result.split('\n');
|
|
262
|
+
assert.strictEqual(lines.length, 4, 'table has 4 lines (header + separator + 2 rows)');
|
|
263
|
+
});
|
|
264
|
+
});
|
|
272
265
|
|
|
273
266
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
274
267
|
// context-store: format requirements
|
|
275
268
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
276
269
|
|
|
277
|
-
|
|
278
|
-
{
|
|
279
|
-
|
|
280
|
-
|
|
270
|
+
describe("context-store: formatRequirementsForPrompt", () => {
|
|
271
|
+
test("empty input returns empty string", () => {
|
|
272
|
+
const empty = formatRequirementsForPrompt([]);
|
|
273
|
+
assert.strictEqual(empty, '', 'empty input returns empty string');
|
|
274
|
+
});
|
|
281
275
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
276
|
+
test("formats requirements as markdown sections", () => {
|
|
277
|
+
const result = formatRequirementsForPrompt([
|
|
278
|
+
{
|
|
279
|
+
id: 'R001', class: 'functional', status: 'active',
|
|
280
|
+
description: 'System must persist decisions', why: 'agent memory',
|
|
281
|
+
source: 'M001', primary_owner: 'S01', supporting_slices: 'S02',
|
|
282
|
+
validation: 'roundtrip test', notes: 'high priority',
|
|
283
|
+
full_content: '', superseded_by: null,
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: 'R002', class: 'non-functional', status: 'active',
|
|
287
|
+
description: 'Sub-5ms query latency', why: 'prompt injection speed',
|
|
288
|
+
source: 'M001', primary_owner: 'S01', supporting_slices: '',
|
|
289
|
+
validation: 'timing test', notes: '',
|
|
290
|
+
full_content: '', superseded_by: null,
|
|
291
|
+
},
|
|
292
|
+
]);
|
|
293
|
+
|
|
294
|
+
assert.match(result, /### R001: System must persist decisions/, 'has R001 section header');
|
|
295
|
+
assert.match(result, /### R002: Sub-5ms query latency/, 'has R002 section header');
|
|
296
|
+
assert.match(result, /\*\*Class:\*\* functional/, 'has class field');
|
|
297
|
+
assert.match(result, /\*\*Status:\*\* active/, 'has status field');
|
|
298
|
+
assert.match(result, /\*\*Supporting Slices:\*\* S02/, 'has supporting slices when present');
|
|
299
|
+
// R002 has no supporting_slices — should not have that line
|
|
300
|
+
// R002 has no notes — should not have notes line
|
|
301
|
+
const r002Section = result.split('### R002')[1] || '';
|
|
302
|
+
assert.ok(!r002Section.includes('**Supporting Slices:**'), 'no supporting slices line when empty');
|
|
303
|
+
assert.ok(!r002Section.includes('**Notes:**'), 'no notes line when empty');
|
|
304
|
+
});
|
|
305
|
+
});
|
|
310
306
|
|
|
311
307
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
312
308
|
// context-store: sub-5ms timing assertion
|
|
313
309
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
314
310
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
id,
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
id,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
311
|
+
describe("context-store: sub-5ms query timing", () => {
|
|
312
|
+
afterEach(() => closeDatabase());
|
|
313
|
+
|
|
314
|
+
test("queries complete under 5ms for 50+50 rows", () => {
|
|
315
|
+
openDatabase(':memory:');
|
|
316
|
+
|
|
317
|
+
// Insert 50 decisions
|
|
318
|
+
for (let i = 1; i <= 50; i++) {
|
|
319
|
+
const id = `D${String(i).padStart(3, '0')}`;
|
|
320
|
+
insertDecision({
|
|
321
|
+
id,
|
|
322
|
+
when_context: `M00${(i % 3) + 1}/S0${(i % 5) + 1}`,
|
|
323
|
+
scope: i % 2 === 0 ? 'architecture' : 'performance',
|
|
324
|
+
decision: `decision ${i}`,
|
|
325
|
+
choice: `choice ${i}`,
|
|
326
|
+
rationale: `rationale ${i}`,
|
|
327
|
+
revisable: i % 3 === 0 ? 'no' : 'yes',
|
|
328
|
+
made_by: 'agent',
|
|
329
|
+
superseded_by: null,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Insert 50 requirements
|
|
334
|
+
for (let i = 1; i <= 50; i++) {
|
|
335
|
+
const id = `R${String(i).padStart(3, '0')}`;
|
|
336
|
+
insertRequirement({
|
|
337
|
+
id,
|
|
338
|
+
class: i % 2 === 0 ? 'functional' : 'non-functional',
|
|
339
|
+
status: i % 4 === 0 ? 'validated' : 'active',
|
|
340
|
+
description: `requirement ${i}`,
|
|
341
|
+
why: `why ${i}`,
|
|
342
|
+
source: 'M001',
|
|
343
|
+
primary_owner: `S0${(i % 5) + 1}`,
|
|
344
|
+
supporting_slices: i % 3 === 0 ? 'S01, S02' : '',
|
|
345
|
+
validation: `validation ${i}`,
|
|
346
|
+
notes: '',
|
|
347
|
+
full_content: '',
|
|
348
|
+
superseded_by: null,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Time the queries — warm up first
|
|
353
|
+
queryDecisions();
|
|
354
|
+
queryRequirements();
|
|
355
|
+
|
|
356
|
+
const start = performance.now();
|
|
357
|
+
const decisions = queryDecisions();
|
|
358
|
+
const requirements = queryRequirements();
|
|
359
|
+
const elapsed = performance.now() - start;
|
|
360
|
+
|
|
361
|
+
assert.strictEqual(decisions.length, 50, `got ${decisions.length} decisions (expected 50)`);
|
|
362
|
+
assert.strictEqual(requirements.length, 50, `got ${requirements.length} requirements (expected 50)`);
|
|
363
|
+
assert.ok(elapsed < 5, `query latency ${elapsed.toFixed(2)}ms should be < 5ms`);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
370
366
|
|
|
371
367
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
372
368
|
// context-store: queryArtifact
|
|
373
369
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
374
370
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
openDatabase(':memory:');
|
|
378
|
-
|
|
379
|
-
insertArtifact({
|
|
380
|
-
path: 'PROJECT.md',
|
|
381
|
-
artifact_type: 'project',
|
|
382
|
-
milestone_id: null,
|
|
383
|
-
slice_id: null,
|
|
384
|
-
task_id: null,
|
|
385
|
-
full_content: '# My Project\n\nProject description here.',
|
|
386
|
-
});
|
|
387
|
-
insertArtifact({
|
|
388
|
-
path: '.gsd/milestones/M001/M001-PLAN.md',
|
|
389
|
-
artifact_type: 'milestone_plan',
|
|
390
|
-
milestone_id: 'M001',
|
|
391
|
-
slice_id: null,
|
|
392
|
-
task_id: null,
|
|
393
|
-
full_content: '# M001 Plan\n\nMilestone content.',
|
|
394
|
-
});
|
|
371
|
+
describe("context-store: queryArtifact", () => {
|
|
372
|
+
afterEach(() => closeDatabase());
|
|
395
373
|
|
|
396
|
-
|
|
397
|
-
|
|
374
|
+
test("returns content for existing path", () => {
|
|
375
|
+
openDatabase(':memory:');
|
|
398
376
|
|
|
399
|
-
|
|
400
|
-
|
|
377
|
+
insertArtifact({
|
|
378
|
+
path: 'PROJECT.md',
|
|
379
|
+
artifact_type: 'project',
|
|
380
|
+
milestone_id: null,
|
|
381
|
+
slice_id: null,
|
|
382
|
+
task_id: null,
|
|
383
|
+
full_content: '# My Project\n\nProject description here.',
|
|
384
|
+
});
|
|
385
|
+
insertArtifact({
|
|
386
|
+
path: '.gsd/milestones/M001/M001-PLAN.md',
|
|
387
|
+
artifact_type: 'milestone_plan',
|
|
388
|
+
milestone_id: 'M001',
|
|
389
|
+
slice_id: null,
|
|
390
|
+
task_id: null,
|
|
391
|
+
full_content: '# M001 Plan\n\nMilestone content.',
|
|
392
|
+
});
|
|
401
393
|
|
|
402
|
-
|
|
403
|
-
|
|
394
|
+
const project = queryArtifact('PROJECT.md');
|
|
395
|
+
assert.strictEqual(project, '# My Project\n\nProject description here.', 'queryArtifact returns full_content for PROJECT.md');
|
|
404
396
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
397
|
+
const plan = queryArtifact('.gsd/milestones/M001/M001-PLAN.md');
|
|
398
|
+
assert.strictEqual(plan, '# M001 Plan\n\nMilestone content.', 'queryArtifact returns full_content for milestone plan');
|
|
399
|
+
});
|
|
408
400
|
|
|
409
|
-
|
|
410
|
-
|
|
401
|
+
test("returns null for missing path", () => {
|
|
402
|
+
openDatabase(':memory:');
|
|
411
403
|
|
|
412
|
-
|
|
413
|
-
|
|
404
|
+
const missing = queryArtifact('nonexistent.md');
|
|
405
|
+
assert.strictEqual(missing, null, 'queryArtifact returns null for path not in DB');
|
|
406
|
+
});
|
|
414
407
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
assertTrue(!isDbAvailable(), 'DB should not be available');
|
|
408
|
+
test("returns null when DB unavailable", () => {
|
|
409
|
+
closeDatabase();
|
|
410
|
+
assert.ok(!isDbAvailable(), 'DB should not be available');
|
|
419
411
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
412
|
+
const result = queryArtifact('PROJECT.md');
|
|
413
|
+
assert.strictEqual(result, null, 'queryArtifact returns null when DB closed');
|
|
414
|
+
});
|
|
415
|
+
});
|
|
423
416
|
|
|
424
417
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
425
418
|
// context-store: queryProject
|
|
426
419
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
427
420
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
openDatabase(':memory:');
|
|
431
|
-
|
|
432
|
-
insertArtifact({
|
|
433
|
-
path: 'PROJECT.md',
|
|
434
|
-
artifact_type: 'project',
|
|
435
|
-
milestone_id: null,
|
|
436
|
-
slice_id: null,
|
|
437
|
-
task_id: null,
|
|
438
|
-
full_content: '# Test Project\n\nThis is the project description.',
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
const content = queryProject();
|
|
442
|
-
assertEq(content, '# Test Project\n\nThis is the project description.', 'queryProject returns PROJECT.md content');
|
|
421
|
+
describe("context-store: queryProject", () => {
|
|
422
|
+
afterEach(() => closeDatabase());
|
|
443
423
|
|
|
444
|
-
|
|
445
|
-
|
|
424
|
+
test("returns PROJECT.md content", () => {
|
|
425
|
+
openDatabase(':memory:');
|
|
446
426
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
427
|
+
insertArtifact({
|
|
428
|
+
path: 'PROJECT.md',
|
|
429
|
+
artifact_type: 'project',
|
|
430
|
+
milestone_id: null,
|
|
431
|
+
slice_id: null,
|
|
432
|
+
task_id: null,
|
|
433
|
+
full_content: '# Test Project\n\nThis is the project description.',
|
|
434
|
+
});
|
|
450
435
|
|
|
451
|
-
|
|
452
|
-
|
|
436
|
+
const content = queryProject();
|
|
437
|
+
assert.strictEqual(content, '# Test Project\n\nThis is the project description.', 'queryProject returns PROJECT.md content');
|
|
438
|
+
});
|
|
453
439
|
|
|
454
|
-
|
|
455
|
-
|
|
440
|
+
test("returns null when no PROJECT.md", () => {
|
|
441
|
+
openDatabase(':memory:');
|
|
456
442
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
assertTrue(!isDbAvailable(), 'DB should not be available');
|
|
443
|
+
const content = queryProject();
|
|
444
|
+
assert.strictEqual(content, null, 'queryProject returns null when PROJECT.md not imported');
|
|
445
|
+
});
|
|
461
446
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
447
|
+
test("returns null when DB unavailable", () => {
|
|
448
|
+
closeDatabase();
|
|
449
|
+
assert.ok(!isDbAvailable(), 'DB should not be available');
|
|
465
450
|
|
|
466
|
-
|
|
467
|
-
|
|
451
|
+
const content = queryProject();
|
|
452
|
+
assert.strictEqual(content, null, 'queryProject returns null when DB closed');
|
|
453
|
+
});
|
|
454
|
+
});
|