gsd-pi 2.81.0-dev.72a81bdf3 → 2.82.0-dev.2841a1e44
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 +49 -30
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +3 -1
- package/dist/resources/extensions/browser-tools/tools/screenshot.js +1 -0
- package/dist/resources/extensions/browser-tools/tools/zoom.js +1 -0
- package/dist/resources/extensions/cmux/index.js +5 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +113 -6
- package/dist/resources/extensions/gsd/auto/phases.js +9 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +169 -124
- package/dist/resources/extensions/gsd/auto-prompts.js +13 -5
- package/dist/resources/extensions/gsd/auto-verification.js +28 -22
- package/dist/resources/extensions/gsd/auto.js +128 -52
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +5 -0
- package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +16 -7
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +55 -12
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +3 -1
- package/dist/resources/extensions/gsd/clean-root-preflight.js +170 -8
- package/dist/resources/extensions/gsd/commands/catalog.js +4 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +22 -1
- package/dist/resources/extensions/gsd/commands-bootstrap.js +5 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +15 -2
- package/dist/resources/extensions/gsd/context-store.js +112 -0
- package/dist/resources/extensions/gsd/db-writer.js +150 -84
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/dist/resources/extensions/gsd/doctor-git-checks.js +41 -6
- package/dist/resources/extensions/gsd/knowledge-backfill.js +144 -0
- package/dist/resources/extensions/gsd/knowledge-capture.js +136 -0
- package/dist/resources/extensions/gsd/knowledge-parser.js +154 -0
- package/dist/resources/extensions/gsd/knowledge-projection.js +210 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +6 -1
- package/dist/resources/extensions/gsd/md-importer.js +1 -1
- package/dist/resources/extensions/gsd/memory-backfill.js +73 -17
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +222 -0
- package/dist/resources/extensions/gsd/migrate/command.js +5 -0
- package/dist/resources/extensions/gsd/migrate/preview.js +9 -0
- package/dist/resources/extensions/gsd/migrate/transformer.js +51 -4
- package/dist/resources/extensions/gsd/migrate/writer.js +11 -1
- package/dist/resources/extensions/gsd/prompts/system.md +2 -2
- package/dist/resources/extensions/gsd/provider-switch-observer.js +146 -0
- package/dist/resources/extensions/gsd/templates/knowledge.md +2 -2
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +119 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +25 -2
- package/dist/resources/extensions/gsd/verification-verdict.js +26 -0
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +21 -2
- package/dist/resources/extensions/subagent/index.js +448 -78
- package/dist/resources/extensions/subagent/launch.js +77 -0
- package/dist/resources/extensions/subagent/run-store.js +148 -0
- package/dist/resources/extensions/visual-brief/artifact-policy.js +29 -0
- package/dist/resources/extensions/visual-brief/extension-manifest.json +8 -0
- package/dist/resources/extensions/visual-brief/index.js +5 -0
- package/dist/resources/extensions/visual-brief/page-contract.js +122 -0
- package/dist/resources/extensions/visual-brief/prompts.js +111 -0
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +2 -2
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- 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 +2 -2
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +2 -2
- 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 +2 -2
- 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 +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
- 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 +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/2973.33f26573894b6153.js +2 -0
- package/dist/web/standalone/.next/static/chunks/{8359.e059d86b255fce1c.js → 8359.7eb3bb8f8ecf4c01.js} +2 -2
- package/dist/web/standalone/.next/static/chunks/{webpack-de742b64187e13fe.js → webpack-6a95bc41e0f7ec89.js} +1 -1
- package/dist/web/standalone/.next/static/css/0262768ec1b89d34.css +1 -0
- package/package.json +5 -4
- package/packages/contracts/dist/rpc.test.js +7 -0
- package/packages/contracts/dist/rpc.test.js.map +1 -1
- package/packages/contracts/dist/workflow.d.ts +21 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +24 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/src/rpc.test.ts +8 -0
- package/packages/contracts/src/workflow.ts +24 -0
- package/packages/daemon/package.json +2 -2
- package/packages/mcp-server/README.md +14 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts +0 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +80 -0
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/workflow-tools-parity.test.ts +244 -0
- package/packages/mcp-server/src/workflow-tools.test.ts +22 -0
- package/packages/mcp-server/src/workflow-tools.ts +168 -0
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/index.d.ts +2 -2
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -1
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts +11 -0
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +20 -0
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-ai/src/index.ts +7 -2
- package/packages/pi-ai/src/providers/transform-messages.ts +24 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +4 -4
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js +17 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js.map +1 -0
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/system-prompt.ts +4 -4
- package/packages/pi-coding-agent/src/tests/system-prompt-file-safety.test.ts +22 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +5 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/pi-tui/src/tui.ts +6 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/packages/rpc-client/package.json +1 -1
- package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/GSD-WORKFLOW.md +3 -1
- package/src/resources/extensions/browser-tools/tools/screenshot.ts +1 -0
- package/src/resources/extensions/browser-tools/tools/zoom.ts +1 -0
- package/src/resources/extensions/cmux/index.ts +6 -0
- package/src/resources/extensions/gsd/auto/contracts.ts +46 -11
- package/src/resources/extensions/gsd/auto/orchestrator.ts +118 -6
- package/src/resources/extensions/gsd/auto/phases.ts +14 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +194 -137
- package/src/resources/extensions/gsd/auto-prompts.ts +13 -5
- package/src/resources/extensions/gsd/auto-verification.ts +36 -34
- package/src/resources/extensions/gsd/auto.ts +136 -51
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +6 -0
- package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +16 -6
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +58 -15
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +3 -2
- package/src/resources/extensions/gsd/clean-root-preflight.ts +174 -8
- package/src/resources/extensions/gsd/commands/catalog.ts +4 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +25 -1
- package/src/resources/extensions/gsd/commands-bootstrap.ts +10 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +19 -2
- package/src/resources/extensions/gsd/context-store.ts +120 -1
- package/src/resources/extensions/gsd/db-writer.ts +167 -84
- package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/src/resources/extensions/gsd/doctor-git-checks.ts +44 -6
- package/src/resources/extensions/gsd/doctor-types.ts +2 -0
- package/src/resources/extensions/gsd/knowledge-backfill.ts +164 -0
- package/src/resources/extensions/gsd/knowledge-capture.ts +160 -0
- package/src/resources/extensions/gsd/knowledge-parser.ts +174 -0
- package/src/resources/extensions/gsd/knowledge-projection.ts +241 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +6 -1
- package/src/resources/extensions/gsd/md-importer.ts +1 -1
- package/src/resources/extensions/gsd/memory-backfill.ts +89 -17
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +277 -0
- package/src/resources/extensions/gsd/migrate/command.ts +5 -0
- package/src/resources/extensions/gsd/migrate/preview.ts +10 -0
- package/src/resources/extensions/gsd/migrate/transformer.ts +58 -4
- package/src/resources/extensions/gsd/migrate/writer.ts +14 -1
- package/src/resources/extensions/gsd/prompts/system.md +2 -2
- package/src/resources/extensions/gsd/provider-switch-observer.ts +185 -0
- package/src/resources/extensions/gsd/templates/knowledge.md +2 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +408 -4
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/brief-command.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/browser-tools-compatibility-declarations.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +107 -2
- package/src/resources/extensions/gsd/tests/closeout-git-deferral.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/context-store-decisions-from-memories.test.ts +312 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +13 -8
- package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +453 -0
- package/src/resources/extensions/gsd/tests/decisions-stop-table-writes.test.ts +348 -0
- package/src/resources/extensions/gsd/tests/evidence-cross-ref.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +8 -4
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +11 -7
- package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/integration/integration-lifecycle.test.ts +13 -5
- package/src/resources/extensions/gsd/tests/integration/migrate-command.test.ts +48 -3
- package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +323 -0
- package/src/resources/extensions/gsd/tests/knowledge-capture.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -2
- package/src/resources/extensions/gsd/tests/load-knowledge-block-rules-only.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +316 -0
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/plan-milestone-sketch-render.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +79 -1
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +252 -0
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +16 -4
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +78 -0
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +16 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +135 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +35 -2
- package/src/resources/extensions/gsd/verification-verdict.ts +47 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +4 -0
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +20 -2
- package/src/resources/extensions/subagent/index.ts +567 -103
- package/src/resources/extensions/subagent/launch.ts +131 -0
- package/src/resources/extensions/subagent/run-store.ts +218 -0
- package/src/resources/extensions/subagent/tests/launch.test.ts +115 -0
- package/src/resources/extensions/subagent/tests/run-store.test.ts +111 -0
- package/src/resources/extensions/visual-brief/artifact-policy.ts +41 -0
- package/src/resources/extensions/visual-brief/extension-manifest.json +8 -0
- package/src/resources/extensions/visual-brief/index.ts +8 -0
- package/src/resources/extensions/visual-brief/page-contract.ts +134 -0
- package/src/resources/extensions/visual-brief/prompts.ts +147 -0
- package/src/resources/extensions/visual-brief/tests/visual-brief.test.ts +172 -0
- package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +0 -1
- package/dist/web/standalone/.next/static/css/54ec2745c1da488b.css +0 -1
- /package/dist/web/standalone/.next/static/{rIkMv4YSNlfSeqmGqWVns → Qgr2B_MRhPxC0z8fwv4vT}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{rIkMv4YSNlfSeqmGqWVns → Qgr2B_MRhPxC0z8fwv4vT}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
// ADR-013 Phase 6 cutover (Stage 3) — destructive lock-in.
|
|
2
|
+
//
|
|
3
|
+
// After this stage, new decisions written via gsd_save_decision land ONLY in
|
|
4
|
+
// the memories table. The decisions table receives no new rows; its existing
|
|
5
|
+
// rows remain for backwards-compat reads until #5756 drops the table.
|
|
6
|
+
//
|
|
7
|
+
// Tests cover five properties:
|
|
8
|
+
// 1. New saveDecisionToDb call does NOT insert into the decisions table
|
|
9
|
+
// 2. The new decision DOES appear as a memory row with sourceDecisionId
|
|
10
|
+
// 3. The rendered DECISIONS.md projection includes the new decision
|
|
11
|
+
// 4. Pre-existing decisions-table rows are untouched
|
|
12
|
+
// 5. Decision IDs are monotonic across both surfaces (max of either + 1)
|
|
13
|
+
|
|
14
|
+
import test from "node:test";
|
|
15
|
+
import assert from "node:assert/strict";
|
|
16
|
+
import {
|
|
17
|
+
existsSync,
|
|
18
|
+
mkdirSync,
|
|
19
|
+
mkdtempSync,
|
|
20
|
+
readFileSync,
|
|
21
|
+
rmSync,
|
|
22
|
+
} from "node:fs";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import { tmpdir } from "node:os";
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
_getAdapter,
|
|
28
|
+
closeDatabase,
|
|
29
|
+
insertDecision,
|
|
30
|
+
openDatabase,
|
|
31
|
+
} from "../gsd-db.ts";
|
|
32
|
+
import { saveDecisionToDb } from "../db-writer.ts";
|
|
33
|
+
import {
|
|
34
|
+
getAllDecisionsFromMemories,
|
|
35
|
+
queryDecisions,
|
|
36
|
+
} from "../context-store.ts";
|
|
37
|
+
|
|
38
|
+
function makeTmpBase(): string {
|
|
39
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-decisions-stage3-"));
|
|
40
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
41
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
42
|
+
return base;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function cleanup(base: string): void {
|
|
46
|
+
try {
|
|
47
|
+
closeDatabase();
|
|
48
|
+
} catch {
|
|
49
|
+
/* noop */
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
rmSync(base, { recursive: true, force: true });
|
|
53
|
+
} catch {
|
|
54
|
+
/* noop */
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function countDecisionRows(): number {
|
|
59
|
+
const adapter = _getAdapter();
|
|
60
|
+
if (!adapter) return 0;
|
|
61
|
+
const row = adapter
|
|
62
|
+
.prepare("SELECT COUNT(*) as n FROM decisions")
|
|
63
|
+
.get() as { n: number };
|
|
64
|
+
return row.n;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function seedLegacyDecision(args: {
|
|
68
|
+
id: string;
|
|
69
|
+
when_context: string;
|
|
70
|
+
scope: string;
|
|
71
|
+
decision: string;
|
|
72
|
+
choice: string;
|
|
73
|
+
rationale: string;
|
|
74
|
+
superseded_by?: string | null;
|
|
75
|
+
}): void {
|
|
76
|
+
insertDecision({
|
|
77
|
+
id: args.id,
|
|
78
|
+
when_context: args.when_context,
|
|
79
|
+
scope: args.scope,
|
|
80
|
+
decision: args.decision,
|
|
81
|
+
choice: args.choice,
|
|
82
|
+
rationale: args.rationale,
|
|
83
|
+
revisable: "Yes",
|
|
84
|
+
made_by: "agent",
|
|
85
|
+
superseded_by: args.superseded_by ?? null,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ─── Core invariants ────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
test("saveDecisionToDb no longer writes to the decisions table", async () => {
|
|
92
|
+
const base = makeTmpBase();
|
|
93
|
+
try {
|
|
94
|
+
const before = countDecisionRows();
|
|
95
|
+
assert.equal(before, 0);
|
|
96
|
+
|
|
97
|
+
await saveDecisionToDb(
|
|
98
|
+
{
|
|
99
|
+
when_context: "M001 discuss",
|
|
100
|
+
scope: "M001",
|
|
101
|
+
decision: "Stage 3 destructive: memories-only writes",
|
|
102
|
+
choice: "Drop the upsertDecision call",
|
|
103
|
+
rationale: "Cutover step required by ADR-013 Phase 6",
|
|
104
|
+
revisable: "Yes",
|
|
105
|
+
made_by: "agent",
|
|
106
|
+
},
|
|
107
|
+
base,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const after = countDecisionRows();
|
|
111
|
+
assert.equal(after, 0, "decisions table must remain empty after Stage 3 save");
|
|
112
|
+
} finally {
|
|
113
|
+
cleanup(base);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("saveDecisionToDb writes the new decision into memories with sourceDecisionId", async () => {
|
|
118
|
+
const base = makeTmpBase();
|
|
119
|
+
try {
|
|
120
|
+
const { id } = await saveDecisionToDb(
|
|
121
|
+
{
|
|
122
|
+
when_context: "M001 discuss",
|
|
123
|
+
scope: "M001",
|
|
124
|
+
decision: "Use atomic file writes",
|
|
125
|
+
choice: "atomicWriteSync",
|
|
126
|
+
rationale: "crash-safe markdown projections",
|
|
127
|
+
revisable: "Yes",
|
|
128
|
+
made_by: "agent",
|
|
129
|
+
},
|
|
130
|
+
base,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const fromMemories = getAllDecisionsFromMemories();
|
|
134
|
+
assert.equal(fromMemories.length, 1);
|
|
135
|
+
assert.equal(fromMemories[0]?.id, id);
|
|
136
|
+
assert.equal(fromMemories[0]?.decision, "Use atomic file writes");
|
|
137
|
+
} finally {
|
|
138
|
+
cleanup(base);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("DECISIONS.md projection includes the memory-only decision", async () => {
|
|
143
|
+
const base = makeTmpBase();
|
|
144
|
+
try {
|
|
145
|
+
await saveDecisionToDb(
|
|
146
|
+
{
|
|
147
|
+
when_context: "M001 discuss",
|
|
148
|
+
scope: "M001",
|
|
149
|
+
decision: "Adopt SQLite",
|
|
150
|
+
choice: "better-sqlite3",
|
|
151
|
+
rationale: "synchronous + native",
|
|
152
|
+
revisable: "Yes",
|
|
153
|
+
made_by: "agent",
|
|
154
|
+
},
|
|
155
|
+
base,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const md = readFileSync(join(base, ".gsd", "DECISIONS.md"), "utf-8");
|
|
159
|
+
assert.match(md, /\| D001 \|/);
|
|
160
|
+
assert.match(md, /Adopt SQLite/);
|
|
161
|
+
assert.match(md, /# Decisions Register/);
|
|
162
|
+
} finally {
|
|
163
|
+
cleanup(base);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// ─── Existing decisions table is preserved ──────────────────────────────────
|
|
168
|
+
|
|
169
|
+
test("pre-existing decisions-table rows are untouched by Stage 3 saves", async () => {
|
|
170
|
+
const base = makeTmpBase();
|
|
171
|
+
try {
|
|
172
|
+
// Seed two legacy decisions directly into the table — simulates a
|
|
173
|
+
// pre-cutover project.
|
|
174
|
+
seedLegacyDecision({
|
|
175
|
+
id: "D001",
|
|
176
|
+
when_context: "M001 historical",
|
|
177
|
+
scope: "M001",
|
|
178
|
+
decision: "Legacy decision one",
|
|
179
|
+
choice: "A",
|
|
180
|
+
rationale: "r1",
|
|
181
|
+
});
|
|
182
|
+
seedLegacyDecision({
|
|
183
|
+
id: "D002",
|
|
184
|
+
when_context: "M001 historical",
|
|
185
|
+
scope: "M001",
|
|
186
|
+
decision: "Legacy decision two",
|
|
187
|
+
choice: "B",
|
|
188
|
+
rationale: "r2",
|
|
189
|
+
});
|
|
190
|
+
assert.equal(countDecisionRows(), 2);
|
|
191
|
+
|
|
192
|
+
// New save via the cutover path.
|
|
193
|
+
await saveDecisionToDb(
|
|
194
|
+
{
|
|
195
|
+
when_context: "M001 today",
|
|
196
|
+
scope: "M001",
|
|
197
|
+
decision: "Post-cutover decision",
|
|
198
|
+
choice: "C",
|
|
199
|
+
rationale: "r3",
|
|
200
|
+
revisable: "Yes",
|
|
201
|
+
made_by: "agent",
|
|
202
|
+
},
|
|
203
|
+
base,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Decisions table unchanged.
|
|
207
|
+
assert.equal(countDecisionRows(), 2);
|
|
208
|
+
|
|
209
|
+
// Legacy reads still see the pre-existing rows.
|
|
210
|
+
const fromLegacy = queryDecisions();
|
|
211
|
+
assert.equal(fromLegacy.length, 2);
|
|
212
|
+
assert.deepEqual(
|
|
213
|
+
fromLegacy.map((d) => d.id).sort(),
|
|
214
|
+
["D001", "D002"],
|
|
215
|
+
);
|
|
216
|
+
} finally {
|
|
217
|
+
cleanup(base);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// ─── Cross-surface ID monotonicity ─────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
test("decision IDs are monotonic across legacy table + memory surfaces", async () => {
|
|
224
|
+
const base = makeTmpBase();
|
|
225
|
+
try {
|
|
226
|
+
// Pre-cutover project with D001..D005 in the legacy table (no memory
|
|
227
|
+
// backfill yet — simulates an upgrade that hasn't run backfill).
|
|
228
|
+
for (let i = 1; i <= 5; i++) {
|
|
229
|
+
seedLegacyDecision({
|
|
230
|
+
id: `D${String(i).padStart(3, "0")}`,
|
|
231
|
+
when_context: "historical",
|
|
232
|
+
scope: "M001",
|
|
233
|
+
decision: `Decision ${i}`,
|
|
234
|
+
choice: "x",
|
|
235
|
+
rationale: "y",
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
assert.equal(countDecisionRows(), 5);
|
|
239
|
+
|
|
240
|
+
// First Stage 3 save: must pick the next-after-table ID.
|
|
241
|
+
const a = await saveDecisionToDb(
|
|
242
|
+
{
|
|
243
|
+
when_context: "M001 today",
|
|
244
|
+
scope: "M001",
|
|
245
|
+
decision: "First post-cutover",
|
|
246
|
+
choice: "A",
|
|
247
|
+
rationale: "r",
|
|
248
|
+
revisable: "Yes",
|
|
249
|
+
made_by: "agent",
|
|
250
|
+
},
|
|
251
|
+
base,
|
|
252
|
+
);
|
|
253
|
+
assert.equal(a.id, "D006", "first cutover save must follow the legacy table max (D005 -> D006)");
|
|
254
|
+
|
|
255
|
+
// Second save: must pick the next-after-memory ID.
|
|
256
|
+
const b = await saveDecisionToDb(
|
|
257
|
+
{
|
|
258
|
+
when_context: "M001 today",
|
|
259
|
+
scope: "M001",
|
|
260
|
+
decision: "Second post-cutover",
|
|
261
|
+
choice: "B",
|
|
262
|
+
rationale: "r",
|
|
263
|
+
revisable: "Yes",
|
|
264
|
+
made_by: "agent",
|
|
265
|
+
},
|
|
266
|
+
base,
|
|
267
|
+
);
|
|
268
|
+
assert.equal(b.id, "D007", "second cutover save must follow memory max (D006 -> D007)");
|
|
269
|
+
|
|
270
|
+
// Decisions table still has only the original five.
|
|
271
|
+
assert.equal(countDecisionRows(), 5);
|
|
272
|
+
} finally {
|
|
273
|
+
cleanup(base);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("next-ID falls back to D001 when both surfaces are empty", async () => {
|
|
278
|
+
const base = makeTmpBase();
|
|
279
|
+
try {
|
|
280
|
+
const { id } = await saveDecisionToDb(
|
|
281
|
+
{
|
|
282
|
+
when_context: "M001",
|
|
283
|
+
scope: "M001",
|
|
284
|
+
decision: "First ever",
|
|
285
|
+
choice: "A",
|
|
286
|
+
rationale: "r",
|
|
287
|
+
revisable: "Yes",
|
|
288
|
+
made_by: "agent",
|
|
289
|
+
},
|
|
290
|
+
base,
|
|
291
|
+
);
|
|
292
|
+
assert.equal(id, "D001");
|
|
293
|
+
} finally {
|
|
294
|
+
cleanup(base);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("next-ID skips beyond memory-only entries when the legacy table is empty", async () => {
|
|
299
|
+
const base = makeTmpBase();
|
|
300
|
+
try {
|
|
301
|
+
// Three memory-only saves on a fresh project.
|
|
302
|
+
const a = await saveDecisionToDb(
|
|
303
|
+
{ when_context: "M001", scope: "M001", decision: "A", choice: "1", rationale: "r", revisable: "Yes", made_by: "agent" },
|
|
304
|
+
base,
|
|
305
|
+
);
|
|
306
|
+
const b = await saveDecisionToDb(
|
|
307
|
+
{ when_context: "M001", scope: "M001", decision: "B", choice: "2", rationale: "r", revisable: "Yes", made_by: "agent" },
|
|
308
|
+
base,
|
|
309
|
+
);
|
|
310
|
+
const c = await saveDecisionToDb(
|
|
311
|
+
{ when_context: "M001", scope: "M001", decision: "C", choice: "3", rationale: "r", revisable: "Yes", made_by: "agent" },
|
|
312
|
+
base,
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
assert.deepEqual([a.id, b.id, c.id], ["D001", "D002", "D003"]);
|
|
316
|
+
assert.equal(countDecisionRows(), 0, "no decisions-table writes despite three saves");
|
|
317
|
+
} finally {
|
|
318
|
+
cleanup(base);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// ─── Defensive: malformed structured_fields don't break next-ID ────────────
|
|
323
|
+
|
|
324
|
+
test("malformed structuredFields in memories are silently skipped during next-ID computation", async () => {
|
|
325
|
+
const base = makeTmpBase();
|
|
326
|
+
try {
|
|
327
|
+
// Insert a memory with broken JSON in structured_fields. The next-ID
|
|
328
|
+
// logic must not throw.
|
|
329
|
+
const adapter = _getAdapter();
|
|
330
|
+
assert.ok(adapter);
|
|
331
|
+
adapter
|
|
332
|
+
.prepare(
|
|
333
|
+
"INSERT INTO memories (id, category, content, confidence, created_at, updated_at, structured_fields) VALUES ('mem-broken', 'architecture', 'oops', 0.5, datetime('now'), datetime('now'), '{\"sourceDecisionId\":\"D999\"')",
|
|
334
|
+
)
|
|
335
|
+
.run();
|
|
336
|
+
|
|
337
|
+
const { id } = await saveDecisionToDb(
|
|
338
|
+
{ when_context: "M001", scope: "M001", decision: "Survivor", choice: "x", rationale: "y", revisable: "Yes", made_by: "agent" },
|
|
339
|
+
base,
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
// Broken JSON for D999 doesn't parse, so the surfaced max stays at 0.
|
|
343
|
+
// The new save must still produce a valid sequential ID.
|
|
344
|
+
assert.equal(id, "D001");
|
|
345
|
+
} finally {
|
|
346
|
+
cleanup(base);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Tests for verification evidence cross-reference mismatch policy.
|
|
3
|
+
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
|
|
7
|
+
import { crossReferenceEvidence } from "../safety/evidence-cross-ref.ts";
|
|
8
|
+
import type { EvidenceEntry } from "../safety/evidence-collector.ts";
|
|
9
|
+
|
|
10
|
+
test("claims of passing verification become errors when recorded bash evidence failed", () => {
|
|
11
|
+
const mismatches = crossReferenceEvidence(
|
|
12
|
+
[{ command: "npm test", exitCode: 0, verdict: "passed" }],
|
|
13
|
+
[
|
|
14
|
+
{
|
|
15
|
+
kind: "bash",
|
|
16
|
+
toolCallId: "call-1",
|
|
17
|
+
command: "npm test",
|
|
18
|
+
exitCode: 1,
|
|
19
|
+
outputSnippet: "failed",
|
|
20
|
+
timestamp: Date.now(),
|
|
21
|
+
},
|
|
22
|
+
] as EvidenceEntry[],
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
assert.equal(mismatches.length, 1);
|
|
26
|
+
assert.equal(mismatches[0].severity, "error");
|
|
27
|
+
assert.match(mismatches[0].reason, /Claimed exitCode=0/);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("missing recorded bash evidence remains a warning", () => {
|
|
31
|
+
const mismatches = crossReferenceEvidence(
|
|
32
|
+
[{ command: "npm test", exitCode: 0, verdict: "passed" }],
|
|
33
|
+
[],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
assert.equal(mismatches.length, 1);
|
|
37
|
+
assert.equal(mismatches[0].severity, "warning");
|
|
38
|
+
});
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
saveDecisionToDb,
|
|
16
16
|
} from '../db-writer.ts';
|
|
17
|
+
import { getAllDecisionsFromMemories } from '../context-store.ts';
|
|
17
18
|
|
|
18
19
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
19
20
|
// Helpers
|
|
@@ -31,11 +32,14 @@ function cleanupDir(dir: string): void {
|
|
|
31
32
|
} catch { /* swallow */ }
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Query all decisions. ADR-013 Stage 3 (PR #5755): decisions are written
|
|
37
|
+
* exclusively to memories; this helper returns the same Decision shape the
|
|
38
|
+
* previous SELECT * FROM decisions produced so the assertions below stay
|
|
39
|
+
* one-line lookups.
|
|
40
|
+
*/
|
|
35
41
|
function queryAllDecisions(): Array<Record<string, unknown>> {
|
|
36
|
-
|
|
37
|
-
if (!adapter) return [];
|
|
38
|
-
return adapter.prepare('SELECT * FROM decisions ORDER BY seq').all();
|
|
42
|
+
return getAllDecisionsFromMemories().map((d) => ({ ...d }));
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
isDbAvailable,
|
|
15
15
|
upsertRequirement,
|
|
16
16
|
getRequirementById,
|
|
17
|
-
getDecisionById,
|
|
18
17
|
_getAdapter,
|
|
19
18
|
insertArtifact,
|
|
20
19
|
} from '../gsd-db.ts';
|
|
@@ -26,6 +25,7 @@ import {
|
|
|
26
25
|
nextDecisionId,
|
|
27
26
|
nextRequirementId,
|
|
28
27
|
} from '../db-writer.ts';
|
|
28
|
+
import { getAllDecisionsFromMemories } from '../context-store.ts';
|
|
29
29
|
import type { Requirement } from '../types.ts';
|
|
30
30
|
|
|
31
31
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -73,12 +73,16 @@ describe('gsd-tools', () => {
|
|
|
73
73
|
|
|
74
74
|
assert.deepStrictEqual(result.id, 'D001', 'First decision should be D001');
|
|
75
75
|
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
assert.
|
|
80
|
-
|
|
81
|
-
assert.
|
|
76
|
+
// ADR-013 Stage 3: decisions land in memories, not the legacy decisions
|
|
77
|
+
// table. Verify the memory row carries the same content.
|
|
78
|
+
const memoryDecisions = getAllDecisionsFromMemories();
|
|
79
|
+
assert.equal(memoryDecisions.length, 1, 'one memory row exists after save');
|
|
80
|
+
const memDecision = memoryDecisions[0];
|
|
81
|
+
assert.ok(memDecision, 'memory decision exists after save');
|
|
82
|
+
assert.equal(memDecision.id, 'D001');
|
|
83
|
+
assert.deepStrictEqual(memDecision.scope, 'architecture', 'memory decision scope should match');
|
|
84
|
+
assert.deepStrictEqual(memDecision.decision, 'Use SQLite for metadata', 'memory decision text should match');
|
|
85
|
+
assert.deepStrictEqual(memDecision.choice, 'SQLite', 'memory decision choice should match');
|
|
82
86
|
|
|
83
87
|
// Verify DECISIONS.md was generated
|
|
84
88
|
const mdPath = path.join(tmpDir, '.gsd', 'DECISIONS.md');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// GSD-2 doctor git integration tests
|
|
1
2
|
import { describe, test } from 'node:test';
|
|
2
3
|
import assert from 'node:assert/strict';
|
|
3
4
|
/**
|
|
@@ -761,6 +762,49 @@ describe('doctor-git', async () => {
|
|
|
761
762
|
assert.ok(log.includes("gsd snapshot"), "commit is tagged with gsd snapshot");
|
|
762
763
|
});
|
|
763
764
|
|
|
765
|
+
test('stale_uncommitted_changes (skips snapshot when tracked changes contain conflict markers)', async () => {
|
|
766
|
+
const dir = createRepoWithActiveMilestone();
|
|
767
|
+
cleanups.push(dir);
|
|
768
|
+
|
|
769
|
+
const pastDate = new Date(Date.now() - 45 * 60 * 1000).toISOString();
|
|
770
|
+
run(`git commit --amend --no-edit --date="${pastDate}"`, dir);
|
|
771
|
+
execSync(`git commit --amend --no-edit`, {
|
|
772
|
+
cwd: dir,
|
|
773
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
774
|
+
encoding: "utf-8",
|
|
775
|
+
env: { ...process.env, GIT_COMMITTER_DATE: pastDate },
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
writeFileSync(join(dir, "README.md"), [
|
|
779
|
+
"# test",
|
|
780
|
+
"<<<<<<< Updated upstream",
|
|
781
|
+
"modified content",
|
|
782
|
+
"=======",
|
|
783
|
+
"stashed content",
|
|
784
|
+
">>>>>>> Stashed changes",
|
|
785
|
+
"",
|
|
786
|
+
].join("\n"));
|
|
787
|
+
|
|
788
|
+
const commitsBefore = run("git rev-list --count HEAD", dir);
|
|
789
|
+
const fixed = await runGSDDoctor(dir, { fix: true });
|
|
790
|
+
const conflictIssues = fixed.issues.filter(
|
|
791
|
+
i => i.code === ("conflict_markers_in_tracked_files" as typeof i.code),
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
assert.equal(conflictIssues.length, 1, "detects conflict markers before snapshotting");
|
|
795
|
+
assert.equal(conflictIssues[0]?.severity, "error", "conflict marker issue blocks automation");
|
|
796
|
+
assert.equal(conflictIssues[0]?.fixable, false, "conflict marker issue requires manual resolution");
|
|
797
|
+
assert.ok(
|
|
798
|
+
fixed.fixesApplied.some(f => f.includes("gsd snapshot skipped")),
|
|
799
|
+
"fix reports skipped snapshot",
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
const commitsAfter = run("git rev-list --count HEAD", dir);
|
|
803
|
+
assert.equal(commitsAfter, commitsBefore, "no snapshot commit is created");
|
|
804
|
+
assert.equal(run("git diff --cached --name-only", dir), "", "no files are staged");
|
|
805
|
+
assert.match(run("git status --short", dir), /M README\.md/m, "tracked file remains modified");
|
|
806
|
+
});
|
|
807
|
+
|
|
764
808
|
// ─── Test: stale_uncommitted_changes NOT flagged when recent commit ──
|
|
765
809
|
test('stale_uncommitted_changes (no false positive on recent commit)', async () => {
|
|
766
810
|
const dir = createRepoWithActiveMilestone();
|
|
@@ -17,10 +17,12 @@ import { migrateFromMarkdown, parseDecisionsTable } from '../../md-importer.ts';
|
|
|
17
17
|
import {
|
|
18
18
|
queryDecisions,
|
|
19
19
|
queryRequirements,
|
|
20
|
+
getAllDecisionsFromMemories,
|
|
20
21
|
formatDecisionsForPrompt,
|
|
21
22
|
formatRequirementsForPrompt,
|
|
22
23
|
} from '../../context-store.ts';
|
|
23
24
|
import { saveDecisionToDb, generateDecisionsMd } from '../../db-writer.ts';
|
|
25
|
+
import { backfillDecisionsToMemories } from '../../memory-backfill.ts';
|
|
24
26
|
import { describe, test, beforeEach, afterEach } from 'node:test';
|
|
25
27
|
import assert from 'node:assert/strict';
|
|
26
28
|
|
|
@@ -233,8 +235,8 @@ test('integration-lifecycle: full pipeline', async () => {
|
|
|
233
235
|
assert.ok(typeof saved.id === 'string', 'lifecycle: saveDecisionToDb returned an id');
|
|
234
236
|
assert.match(saved.id, /^D\d+$/, 'lifecycle: saved ID matches D### pattern');
|
|
235
237
|
|
|
236
|
-
// Query back from DB
|
|
237
|
-
const allAfterSave =
|
|
238
|
+
// Query back from DB (memories — Stage 3 of ADR-013 stopped legacy decisions-table writes)
|
|
239
|
+
const allAfterSave = getAllDecisionsFromMemories();
|
|
238
240
|
const savedDecision = allAfterSave.find(d => d.id === saved.id);
|
|
239
241
|
assert.ok(savedDecision !== null && savedDecision !== undefined, `lifecycle: saved decision ${saved.id} found in DB`);
|
|
240
242
|
assert.deepStrictEqual(savedDecision?.decision, 'integration test write-back decision', 'lifecycle: saved decision text matches');
|
|
@@ -253,9 +255,15 @@ test('integration-lifecycle: full pipeline', async () => {
|
|
|
253
255
|
assert.deepStrictEqual(reparsedSaved?.rationale, 'proves round-trip fidelity', 'lifecycle: round-trip rationale preserved');
|
|
254
256
|
|
|
255
257
|
// ── Step 8: DB consistency — total count sanity ─────────────────────
|
|
256
|
-
|
|
257
|
-
//
|
|
258
|
-
|
|
258
|
+
// ADR-013 Stage 3 split the write paths: migrateFromMarkdown still
|
|
259
|
+
// populates the legacy decisions table, but saveDecisionToDb now writes
|
|
260
|
+
// only to memories. The unified projection (and the post-#5756 cutover
|
|
261
|
+
// end-state) lives in memories, so simulate what bootstrap does and
|
|
262
|
+
// backfill the table rows into memories before counting.
|
|
263
|
+
backfillDecisionsToMemories();
|
|
264
|
+
const finalCount = getAllDecisionsFromMemories().length;
|
|
265
|
+
// Original 14 + 1 re-import (decisions table → memories via backfill) + 1 saveDecisionToDb = 16
|
|
266
|
+
assert.ok(finalCount === DECISIONS_COUNT + 2, `lifecycle: final memory-store count = ${DECISIONS_COUNT + 2} (got ${finalCount})`);
|
|
259
267
|
|
|
260
268
|
closeDatabase();
|
|
261
269
|
} finally {
|
|
@@ -14,7 +14,9 @@ import {
|
|
|
14
14
|
generatePreview,
|
|
15
15
|
writeGSDDirectory,
|
|
16
16
|
} from '../../migrate/index.ts';
|
|
17
|
+
import { importWrittenMigrationToDb } from '../../migrate/command.ts';
|
|
17
18
|
import { deriveState } from '../../state.ts';
|
|
19
|
+
import { closeDatabase, getDecisionById, getRequirementCounts } from '../../gsd-db.ts';
|
|
18
20
|
import { describe, test, beforeEach, afterEach } from 'node:test';
|
|
19
21
|
import assert from 'node:assert/strict';
|
|
20
22
|
|
|
@@ -52,6 +54,16 @@ const SAMPLE_REQUIREMENTS = `# Requirements
|
|
|
52
54
|
- Description: Output matches GSD format.
|
|
53
55
|
`;
|
|
54
56
|
|
|
57
|
+
const SAMPLE_REQUIREMENTS_LEGACY_IDS = `# Requirements
|
|
58
|
+
|
|
59
|
+
## Active
|
|
60
|
+
|
|
61
|
+
- [ ] **CORE-PIPELINE**: Pipeline must work end-to-end.
|
|
62
|
+
- [ ] **OUTPUT-FORMAT**: Output matches GSD format.
|
|
63
|
+
- [ ] **IMPORT-DB**: Migration imports requirements into the DB.
|
|
64
|
+
- [ ] **STATUS-WIDGET**: Status can query migrated requirements.
|
|
65
|
+
`;
|
|
66
|
+
|
|
55
67
|
const SAMPLE_STATE = `# State
|
|
56
68
|
|
|
57
69
|
**Current Phase:** 20-features
|
|
@@ -166,14 +178,14 @@ Depends on foundation work.
|
|
|
166
178
|
</context>
|
|
167
179
|
`;
|
|
168
180
|
|
|
169
|
-
function createCompleteFixture(): string {
|
|
181
|
+
function createCompleteFixture(requirementsContent: string = SAMPLE_REQUIREMENTS): string {
|
|
170
182
|
const base = mkdtempSync(join(tmpdir(), 'gsd-cmd-test-'));
|
|
171
183
|
const planning = join(base, '.planning');
|
|
172
184
|
mkdirSync(planning, { recursive: true });
|
|
173
185
|
|
|
174
186
|
writeFileSync(join(planning, 'PROJECT.md'), SAMPLE_PROJECT);
|
|
175
187
|
writeFileSync(join(planning, 'ROADMAP.md'), SAMPLE_ROADMAP);
|
|
176
|
-
writeFileSync(join(planning, 'REQUIREMENTS.md'),
|
|
188
|
+
writeFileSync(join(planning, 'REQUIREMENTS.md'), requirementsContent);
|
|
177
189
|
writeFileSync(join(planning, 'STATE.md'), SAMPLE_STATE);
|
|
178
190
|
writeFileSync(join(planning, 'config.json'), SAMPLE_CONFIG);
|
|
179
191
|
|
|
@@ -303,6 +315,7 @@ test('Full pipeline: parse → transform → preview → write → deriveState',
|
|
|
303
315
|
const expectedTaskPct = totalTasks > 0 ? Math.round((doneTasks / totalTasks) * 100) : 0;
|
|
304
316
|
assert.deepStrictEqual(preview.sliceCompletionPct, expectedSlicePct, 'pipeline: preview sliceCompletionPct');
|
|
305
317
|
assert.deepStrictEqual(preview.taskCompletionPct, expectedTaskPct, 'pipeline: preview taskCompletionPct');
|
|
318
|
+
assert.deepStrictEqual(preview.decisions.total, 1, 'pipeline: preview decisions total');
|
|
306
319
|
|
|
307
320
|
// Requirements in preview
|
|
308
321
|
assert.deepStrictEqual(preview.requirements.active, 1, 'pipeline: preview requirements active');
|
|
@@ -342,6 +355,39 @@ test('Full pipeline: parse → transform → preview → write → deriveState',
|
|
|
342
355
|
}
|
|
343
356
|
});
|
|
344
357
|
|
|
358
|
+
test('Full pipeline: legacy requirement IDs import into DB with canonical IDs', async () => {
|
|
359
|
+
const base = createCompleteFixture(SAMPLE_REQUIREMENTS_LEGACY_IDS);
|
|
360
|
+
const writeTarget = mkdtempSync(join(tmpdir(), 'gsd-cmd-legacy-reqs-'));
|
|
361
|
+
try {
|
|
362
|
+
const parsed = await parsePlanningDirectory(join(base, '.planning'));
|
|
363
|
+
const project = transformToGSD(parsed);
|
|
364
|
+
const preview = generatePreview(project);
|
|
365
|
+
|
|
366
|
+
assert.deepStrictEqual(
|
|
367
|
+
project.requirements.map((req) => req.id),
|
|
368
|
+
['R001', 'R002', 'R003', 'R004'],
|
|
369
|
+
'legacy-reqs: transform assigns canonical R IDs',
|
|
370
|
+
);
|
|
371
|
+
assert.ok(
|
|
372
|
+
project.requirements[0]?.description.includes('Legacy ID: CORE-PIPELINE'),
|
|
373
|
+
'legacy-reqs: original ID survives in migrated requirement content',
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
await writeGSDDirectory(project, writeTarget);
|
|
377
|
+
const imported = await importWrittenMigrationToDb(writeTarget, preview);
|
|
378
|
+
const counts = getRequirementCounts();
|
|
379
|
+
|
|
380
|
+
assert.deepStrictEqual(imported.decisions, 1, 'legacy-reqs: DB import includes migrated decisions');
|
|
381
|
+
assert.deepStrictEqual(imported.requirements, 4, 'legacy-reqs: DB import count matches preview');
|
|
382
|
+
assert.ok(getDecisionById('D001') !== null, 'legacy-reqs: migrated decision is queryable');
|
|
383
|
+
assert.deepStrictEqual(counts.total, 4, 'legacy-reqs: DB stores all migrated requirements');
|
|
384
|
+
} finally {
|
|
385
|
+
closeDatabase();
|
|
386
|
+
rmSync(base, { recursive: true, force: true });
|
|
387
|
+
rmSync(writeTarget, { recursive: true, force: true });
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
|
|
345
391
|
// ─── Test 6: .gsd/ exists detection ────────────────────────────────────
|
|
346
392
|
|
|
347
393
|
test('.gsd/ exists detection', () => {
|
|
@@ -357,4 +403,3 @@ test('.gsd/ exists detection', () => {
|
|
|
357
403
|
rmSync(base, { recursive: true, force: true });
|
|
358
404
|
}
|
|
359
405
|
});
|
|
360
|
-
|