gsd-pi 2.44.0-dev.62b5d6c → 2.44.0-dev.848dd4c
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/commands/handlers/workflow.js +5 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
- 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 +18 -18
- 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/commands/handlers/workflow.ts +8 -0
- 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 +152 -183
- 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/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/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 → -zps1Q9mQmioAKLcQiCr8}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → -zps1Q9mQmioAKLcQiCr8}/_ssgManifest.js +0 -0
|
@@ -6,15 +6,13 @@ import fs from "node:fs";
|
|
|
6
6
|
|
|
7
7
|
import { loadFile } from "../files.ts";
|
|
8
8
|
|
|
9
|
-
test("loadFile returns null for directory paths instead of throwing EISDIR", async () => {
|
|
9
|
+
test("loadFile returns null for directory paths instead of throwing EISDIR", async (t) => {
|
|
10
10
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "gsd-loadfile-eisdir-"));
|
|
11
11
|
const dirPath = path.join(tmp, "tasks");
|
|
12
12
|
fs.mkdirSync(dirPath);
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
fs.rmSync(tmp, { recursive: true, force: true });
|
|
19
|
-
}
|
|
14
|
+
t.after(() => { fs.rmSync(tmp, { recursive: true, force: true }); });
|
|
15
|
+
|
|
16
|
+
const result = await loadFile(dirPath);
|
|
17
|
+
assert.equal(result, null);
|
|
20
18
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
1
3
|
/**
|
|
2
4
|
* flag-file-db.test.ts — Verify that REPLAN.md and REPLAN-TRIGGER.md
|
|
3
5
|
* flag-file detection in deriveStateFromDb() works from DB-only data
|
|
@@ -24,10 +26,6 @@ import {
|
|
|
24
26
|
insertReplanHistory,
|
|
25
27
|
_getAdapter,
|
|
26
28
|
} from '../gsd-db.ts';
|
|
27
|
-
import { createTestContext } from './test-helpers.ts';
|
|
28
|
-
|
|
29
|
-
const { assertEq, assertTrue, report } = createTestContext();
|
|
30
|
-
|
|
31
29
|
// ─── Fixture Helpers ───────────────────────────────────────────────────────
|
|
32
30
|
|
|
33
31
|
function createFixtureBase(): string {
|
|
@@ -78,11 +76,10 @@ const TASK_SUMMARY_STUB = `---\nblocker_discovered: false\n---\n# T01 Summary\nD
|
|
|
78
76
|
// Tests
|
|
79
77
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
80
78
|
|
|
81
|
-
async
|
|
79
|
+
describe('flag-file-db', async () => {
|
|
82
80
|
|
|
83
81
|
// ─── Test 1: blocker_discovered + no replan_history → replanning-slice ──
|
|
84
|
-
|
|
85
|
-
{
|
|
82
|
+
test('flag-file-db: blocker + no history → replanning', async () => {
|
|
86
83
|
const base = createFixtureBase();
|
|
87
84
|
try {
|
|
88
85
|
// Write disk files needed by deriveStateFromDb (roadmap check, task dir check)
|
|
@@ -91,7 +88,7 @@ async function main(): Promise<void> {
|
|
|
91
88
|
writeFile(base, 'milestones/M001/slices/S01/tasks/T02-PLAN.md', TASK_PLAN_STUB);
|
|
92
89
|
|
|
93
90
|
openDatabase(':memory:');
|
|
94
|
-
|
|
91
|
+
assert.ok(isDbAvailable(), 'test1: DB is available');
|
|
95
92
|
|
|
96
93
|
insertMilestone({ id: 'M001', title: 'Flag-File DB Test', status: 'active' });
|
|
97
94
|
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice', status: 'active', risk: 'low', depends: [] });
|
|
@@ -102,20 +99,19 @@ async function main(): Promise<void> {
|
|
|
102
99
|
invalidateStateCache();
|
|
103
100
|
const state = await deriveStateFromDb(base);
|
|
104
101
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
assert.deepStrictEqual(state.phase, 'replanning-slice', 'test1: phase is replanning-slice');
|
|
103
|
+
assert.ok(state.blockers.length > 0, 'test1: has blockers');
|
|
104
|
+
assert.ok(state.blockers[0]?.includes('blocker'), 'test1: blocker message mentions blocker');
|
|
108
105
|
|
|
109
106
|
closeDatabase();
|
|
110
107
|
} finally {
|
|
111
108
|
closeDatabase();
|
|
112
109
|
cleanup(base);
|
|
113
110
|
}
|
|
114
|
-
}
|
|
111
|
+
});
|
|
115
112
|
|
|
116
113
|
// ─── Test 2: blocker_discovered + replan_history exists → loop protection → executing ──
|
|
117
|
-
|
|
118
|
-
{
|
|
114
|
+
test('flag-file-db: blocker + history → loop protection', async () => {
|
|
119
115
|
const base = createFixtureBase();
|
|
120
116
|
try {
|
|
121
117
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
@@ -139,18 +135,17 @@ async function main(): Promise<void> {
|
|
|
139
135
|
invalidateStateCache();
|
|
140
136
|
const state = await deriveStateFromDb(base);
|
|
141
137
|
|
|
142
|
-
|
|
138
|
+
assert.deepStrictEqual(state.phase, 'executing', 'test2: phase is executing (loop protection)');
|
|
143
139
|
|
|
144
140
|
closeDatabase();
|
|
145
141
|
} finally {
|
|
146
142
|
closeDatabase();
|
|
147
143
|
cleanup(base);
|
|
148
144
|
}
|
|
149
|
-
}
|
|
145
|
+
});
|
|
150
146
|
|
|
151
147
|
// ─── Test 3: replan_triggered_at set + no replan_history → replanning-slice ──
|
|
152
|
-
|
|
153
|
-
{
|
|
148
|
+
test('flag-file-db: trigger column + no history → replanning', async () => {
|
|
154
149
|
const base = createFixtureBase();
|
|
155
150
|
try {
|
|
156
151
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
@@ -173,20 +168,19 @@ async function main(): Promise<void> {
|
|
|
173
168
|
invalidateStateCache();
|
|
174
169
|
const state = await deriveStateFromDb(base);
|
|
175
170
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
171
|
+
assert.deepStrictEqual(state.phase, 'replanning-slice', 'test3: phase is replanning-slice');
|
|
172
|
+
assert.ok(state.blockers.length > 0, 'test3: has blockers');
|
|
173
|
+
assert.ok(state.blockers[0]?.includes('Triage replan trigger'), 'test3: blocker message mentions triage trigger');
|
|
179
174
|
|
|
180
175
|
closeDatabase();
|
|
181
176
|
} finally {
|
|
182
177
|
closeDatabase();
|
|
183
178
|
cleanup(base);
|
|
184
179
|
}
|
|
185
|
-
}
|
|
180
|
+
});
|
|
186
181
|
|
|
187
182
|
// ─── Test 4: replan_triggered_at set + replan_history exists → loop protection ──
|
|
188
|
-
|
|
189
|
-
{
|
|
183
|
+
test('flag-file-db: trigger column + history → loop protection', async () => {
|
|
190
184
|
const base = createFixtureBase();
|
|
191
185
|
try {
|
|
192
186
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
@@ -216,18 +210,17 @@ async function main(): Promise<void> {
|
|
|
216
210
|
invalidateStateCache();
|
|
217
211
|
const state = await deriveStateFromDb(base);
|
|
218
212
|
|
|
219
|
-
|
|
213
|
+
assert.deepStrictEqual(state.phase, 'executing', 'test4: phase is executing (loop protection)');
|
|
220
214
|
|
|
221
215
|
closeDatabase();
|
|
222
216
|
} finally {
|
|
223
217
|
closeDatabase();
|
|
224
218
|
cleanup(base);
|
|
225
219
|
}
|
|
226
|
-
}
|
|
220
|
+
});
|
|
227
221
|
|
|
228
222
|
// ─── Test 5: no blocker, no trigger → phase is executing ──────────────
|
|
229
|
-
|
|
230
|
-
{
|
|
223
|
+
test('flag-file-db: no blocker, no trigger → executing', async () => {
|
|
231
224
|
const base = createFixtureBase();
|
|
232
225
|
try {
|
|
233
226
|
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
@@ -245,20 +238,19 @@ async function main(): Promise<void> {
|
|
|
245
238
|
invalidateStateCache();
|
|
246
239
|
const state = await deriveStateFromDb(base);
|
|
247
240
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
241
|
+
assert.deepStrictEqual(state.phase, 'executing', 'test5: phase is executing');
|
|
242
|
+
assert.deepStrictEqual(state.activeTask?.id, 'T02', 'test5: activeTask is T02');
|
|
243
|
+
assert.deepStrictEqual(state.blockers.length, 0, 'test5: no blockers');
|
|
251
244
|
|
|
252
245
|
closeDatabase();
|
|
253
246
|
} finally {
|
|
254
247
|
closeDatabase();
|
|
255
248
|
cleanup(base);
|
|
256
249
|
}
|
|
257
|
-
}
|
|
250
|
+
});
|
|
258
251
|
|
|
259
252
|
// ─── Diagnostic test: DB column inspection ──────────────────────────
|
|
260
|
-
|
|
261
|
-
{
|
|
253
|
+
test('flag-file-db: replan_triggered_at column is queryable', () => {
|
|
262
254
|
openDatabase(':memory:');
|
|
263
255
|
|
|
264
256
|
insertMilestone({ id: 'M001', title: 'Diagnostic', status: 'active' });
|
|
@@ -269,7 +261,7 @@ async function main(): Promise<void> {
|
|
|
269
261
|
const before = adapter!.prepare(
|
|
270
262
|
"SELECT id, replan_triggered_at FROM slices WHERE milestone_id = :mid",
|
|
271
263
|
).get({ ":mid": "M001" }) as Record<string, unknown>;
|
|
272
|
-
|
|
264
|
+
assert.deepStrictEqual(before["replan_triggered_at"], null, 'diagnostic: replan_triggered_at initially null');
|
|
273
265
|
|
|
274
266
|
// After setting
|
|
275
267
|
adapter!.prepare(
|
|
@@ -279,12 +271,8 @@ async function main(): Promise<void> {
|
|
|
279
271
|
const after = adapter!.prepare(
|
|
280
272
|
"SELECT id, replan_triggered_at FROM slices WHERE milestone_id = :mid",
|
|
281
273
|
).get({ ":mid": "M001" }) as Record<string, unknown>;
|
|
282
|
-
|
|
274
|
+
assert.deepStrictEqual(after["replan_triggered_at"], "2025-01-01T00:00:00Z", 'diagnostic: replan_triggered_at is set');
|
|
283
275
|
|
|
284
276
|
closeDatabase();
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
report();
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
main();
|
|
277
|
+
});
|
|
278
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
2
3
|
import * as path from 'node:path';
|
|
3
4
|
import * as os from 'node:os';
|
|
4
5
|
import * as fs from 'node:fs';
|
|
@@ -13,8 +14,6 @@ import {
|
|
|
13
14
|
saveDecisionToDb,
|
|
14
15
|
} from '../db-writer.ts';
|
|
15
16
|
|
|
16
|
-
const { assertEq, assertTrue, report } = createTestContext();
|
|
17
|
-
|
|
18
17
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
19
18
|
// Helpers
|
|
20
19
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -35,206 +34,199 @@ function cleanupDir(dir: string): void {
|
|
|
35
34
|
// Bug reproduction: freeform DECISIONS.md content destroyed (#2301)
|
|
36
35
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
##
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
##
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
cleanupDir(tmpDir);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
239
|
-
|
|
240
|
-
report();
|
|
37
|
+
describe('freeform-decisions', () => {
|
|
38
|
+
test('parseDecisionsTable silently drops freeform content', () => {
|
|
39
|
+
const freeform = `# Project Decisions
|
|
40
|
+
|
|
41
|
+
## Architecture
|
|
42
|
+
We decided to use a microservices architecture because monoliths don't scale.
|
|
43
|
+
|
|
44
|
+
## Database
|
|
45
|
+
PostgreSQL was chosen for its reliability and JSONB support.
|
|
46
|
+
|
|
47
|
+
## Deployment
|
|
48
|
+
- Kubernetes for orchestration
|
|
49
|
+
- Helm charts for packaging
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
const parsed = parseDecisionsTable(freeform);
|
|
53
|
+
assert.deepStrictEqual(parsed.length, 0, 'freeform content yields zero parsed decisions (expected — it is not a table)');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('saveDecisionToDb destroys freeform DECISIONS.md content', async () => {
|
|
57
|
+
const tmpDir = makeTmpDir();
|
|
58
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
59
|
+
const mdPath = path.join(tmpDir, '.gsd', 'DECISIONS.md');
|
|
60
|
+
openDatabase(dbPath);
|
|
61
|
+
|
|
62
|
+
const freeformContent = `# Project Decisions
|
|
63
|
+
|
|
64
|
+
## Architecture
|
|
65
|
+
We decided to use a microservices architecture because monoliths don't scale.
|
|
66
|
+
|
|
67
|
+
## Database
|
|
68
|
+
PostgreSQL was chosen for its reliability and JSONB support.
|
|
69
|
+
|
|
70
|
+
## Deployment
|
|
71
|
+
- Kubernetes for orchestration
|
|
72
|
+
- Helm charts for packaging
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
// Pre-populate DECISIONS.md with freeform content
|
|
76
|
+
fs.writeFileSync(mdPath, freeformContent, 'utf-8');
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
// Save a new decision — this should NOT destroy the freeform content
|
|
80
|
+
const result = await saveDecisionToDb({
|
|
81
|
+
scope: 'testing',
|
|
82
|
+
decision: 'Use Jest for unit tests',
|
|
83
|
+
choice: 'Jest',
|
|
84
|
+
rationale: 'Well-known, good DX',
|
|
85
|
+
when_context: 'M001',
|
|
86
|
+
}, tmpDir);
|
|
87
|
+
|
|
88
|
+
assert.deepStrictEqual(result.id, 'D001', 'decision ID assigned correctly');
|
|
89
|
+
|
|
90
|
+
// Read back the file
|
|
91
|
+
const afterContent = fs.readFileSync(mdPath, 'utf-8');
|
|
92
|
+
|
|
93
|
+
// The freeform content MUST still be present
|
|
94
|
+
assert.ok(
|
|
95
|
+
afterContent.includes('microservices architecture'),
|
|
96
|
+
'freeform architecture section preserved after saveDecisionToDb',
|
|
97
|
+
);
|
|
98
|
+
assert.ok(
|
|
99
|
+
afterContent.includes('PostgreSQL was chosen'),
|
|
100
|
+
'freeform database section preserved after saveDecisionToDb',
|
|
101
|
+
);
|
|
102
|
+
assert.ok(
|
|
103
|
+
afterContent.includes('Kubernetes for orchestration'),
|
|
104
|
+
'freeform deployment section preserved after saveDecisionToDb',
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// The new decision MUST also be present
|
|
108
|
+
assert.ok(
|
|
109
|
+
afterContent.includes('D001'),
|
|
110
|
+
'new decision D001 present in file',
|
|
111
|
+
);
|
|
112
|
+
assert.ok(
|
|
113
|
+
afterContent.includes('Use Jest for unit tests'),
|
|
114
|
+
'new decision text present in file',
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Save a second decision — freeform content must still survive
|
|
118
|
+
const result2 = await saveDecisionToDb({
|
|
119
|
+
scope: 'ci',
|
|
120
|
+
decision: 'Use GitHub Actions for CI',
|
|
121
|
+
choice: 'GitHub Actions',
|
|
122
|
+
rationale: 'Native integration',
|
|
123
|
+
when_context: 'M001',
|
|
124
|
+
}, tmpDir);
|
|
125
|
+
|
|
126
|
+
assert.deepStrictEqual(result2.id, 'D002', 'second decision ID assigned correctly');
|
|
127
|
+
|
|
128
|
+
const afterContent2 = fs.readFileSync(mdPath, 'utf-8');
|
|
129
|
+
|
|
130
|
+
assert.ok(
|
|
131
|
+
afterContent2.includes('microservices architecture'),
|
|
132
|
+
'freeform content still preserved after second save',
|
|
133
|
+
);
|
|
134
|
+
assert.ok(
|
|
135
|
+
afterContent2.includes('D001'),
|
|
136
|
+
'first decision still present after second save',
|
|
137
|
+
);
|
|
138
|
+
assert.ok(
|
|
139
|
+
afterContent2.includes('D002'),
|
|
140
|
+
'second decision present after second save',
|
|
141
|
+
);
|
|
142
|
+
assert.ok(
|
|
143
|
+
afterContent2.includes('Use GitHub Actions for CI'),
|
|
144
|
+
'second decision text present in file',
|
|
145
|
+
);
|
|
146
|
+
} finally {
|
|
147
|
+
closeDatabase();
|
|
148
|
+
cleanupDir(tmpDir);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('saveDecisionToDb with table-format DECISIONS.md still regenerates normally', async () => {
|
|
153
|
+
const tmpDir = makeTmpDir();
|
|
154
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
155
|
+
const mdPath = path.join(tmpDir, '.gsd', 'DECISIONS.md');
|
|
156
|
+
openDatabase(dbPath);
|
|
157
|
+
|
|
158
|
+
// Pre-populate with canonical table format
|
|
159
|
+
const tableContent = `# Decisions Register
|
|
160
|
+
|
|
161
|
+
<!-- Append-only. Never edit or remove existing rows.
|
|
162
|
+
To reverse a decision, add a new row that supersedes it.
|
|
163
|
+
Read this file at the start of any planning or research phase. -->
|
|
164
|
+
|
|
165
|
+
| # | When | Scope | Decision | Choice | Rationale | Revisable? | Made By |
|
|
166
|
+
|---|------|-------|----------|--------|-----------|------------|---------|
|
|
167
|
+
| D001 | M001 | arch | Use REST API | REST | Simpler | Yes | human |
|
|
168
|
+
`;
|
|
169
|
+
|
|
170
|
+
fs.writeFileSync(mdPath, tableContent, 'utf-8');
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const result = await saveDecisionToDb({
|
|
174
|
+
scope: 'testing',
|
|
175
|
+
decision: 'Use Vitest',
|
|
176
|
+
choice: 'Vitest',
|
|
177
|
+
rationale: 'Fast',
|
|
178
|
+
when_context: 'M001',
|
|
179
|
+
}, tmpDir);
|
|
180
|
+
|
|
181
|
+
// The pre-existing table decision was NOT in DB, so it won't appear after regen.
|
|
182
|
+
// But the new decision should be there.
|
|
183
|
+
assert.deepStrictEqual(result.id, 'D001', 'gets D001 since DB was empty');
|
|
184
|
+
|
|
185
|
+
const afterContent = fs.readFileSync(mdPath, 'utf-8');
|
|
186
|
+
// Table-format file gets fully regenerated — this is the normal path
|
|
187
|
+
assert.ok(
|
|
188
|
+
afterContent.includes('# Decisions Register'),
|
|
189
|
+
'table-format file still has header after save',
|
|
190
|
+
);
|
|
191
|
+
assert.ok(
|
|
192
|
+
afterContent.includes('Use Vitest'),
|
|
193
|
+
'new decision present in regenerated table',
|
|
194
|
+
);
|
|
195
|
+
} finally {
|
|
196
|
+
closeDatabase();
|
|
197
|
+
cleanupDir(tmpDir);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('saveDecisionToDb with no existing DECISIONS.md creates table', async () => {
|
|
202
|
+
const tmpDir = makeTmpDir();
|
|
203
|
+
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
204
|
+
const mdPath = path.join(tmpDir, '.gsd', 'DECISIONS.md');
|
|
205
|
+
openDatabase(dbPath);
|
|
206
|
+
|
|
207
|
+
// No DECISIONS.md exists at all
|
|
208
|
+
assert.ok(!fs.existsSync(mdPath), 'DECISIONS.md does not exist initially');
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const result = await saveDecisionToDb({
|
|
212
|
+
scope: 'arch',
|
|
213
|
+
decision: 'Brand new decision',
|
|
214
|
+
choice: 'Option A',
|
|
215
|
+
rationale: 'Best fit',
|
|
216
|
+
}, tmpDir);
|
|
217
|
+
|
|
218
|
+
assert.deepStrictEqual(result.id, 'D001', 'first decision gets D001');
|
|
219
|
+
assert.ok(fs.existsSync(mdPath), 'DECISIONS.md created');
|
|
220
|
+
|
|
221
|
+
const content = fs.readFileSync(mdPath, 'utf-8');
|
|
222
|
+
assert.ok(content.includes('# Decisions Register'), 'new file has header');
|
|
223
|
+
assert.ok(content.includes('Brand new decision'), 'new file has decision');
|
|
224
|
+
} finally {
|
|
225
|
+
closeDatabase();
|
|
226
|
+
cleanupDir(tmpDir);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
231
|
+
|
|
232
|
+
});
|