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
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createTestContext } from './test-helpers.ts';
|
|
2
1
|
import * as path from 'node:path';
|
|
3
2
|
import * as os from 'node:os';
|
|
4
3
|
import * as fs from 'node:fs';
|
|
@@ -38,8 +37,8 @@ import {
|
|
|
38
37
|
} from '../files.ts';
|
|
39
38
|
import { clearPathCache, _clearGsdRootCache } from '../paths.ts';
|
|
40
39
|
import { invalidateStateCache } from '../state.ts';
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
import { describe, test, beforeEach, afterEach } from 'node:test';
|
|
41
|
+
import assert from 'node:assert/strict';
|
|
43
42
|
|
|
44
43
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
45
44
|
// Helpers
|
|
@@ -174,29 +173,27 @@ function makeTaskSummaryContent(taskId: string): string {
|
|
|
174
173
|
// DB Accessor Tests
|
|
175
174
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
176
175
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
{
|
|
176
|
+
test('── markdown-renderer: DB accessor basics ──', () => {
|
|
180
177
|
openDatabase(':memory:');
|
|
181
178
|
|
|
182
179
|
// getAllMilestones — empty
|
|
183
180
|
const empty = getAllMilestones();
|
|
184
|
-
|
|
181
|
+
assert.deepStrictEqual(empty.length, 0, 'getAllMilestones returns empty when no milestones');
|
|
185
182
|
|
|
186
183
|
// Insert and retrieve
|
|
187
184
|
insertMilestone({ id: 'M001', title: 'Test MS', status: 'active' });
|
|
188
185
|
insertMilestone({ id: 'M002', title: 'Second MS', status: 'active' });
|
|
189
186
|
|
|
190
187
|
const all = getAllMilestones();
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
188
|
+
assert.deepStrictEqual(all.length, 2, 'getAllMilestones returns 2 milestones');
|
|
189
|
+
assert.deepStrictEqual(all[0].id, 'M001', 'first milestone is M001');
|
|
190
|
+
assert.deepStrictEqual(all[1].id, 'M002', 'second milestone is M002');
|
|
191
|
+
assert.deepStrictEqual(all[0].title, 'Test MS', 'milestone title correct');
|
|
192
|
+
assert.deepStrictEqual(all[0].status, 'active', 'milestone status correct');
|
|
196
193
|
|
|
197
194
|
// getMilestoneSlices — empty
|
|
198
195
|
const noSlices = getMilestoneSlices('M001');
|
|
199
|
-
|
|
196
|
+
assert.deepStrictEqual(noSlices.length, 0, 'getMilestoneSlices returns empty when no slices');
|
|
200
197
|
|
|
201
198
|
// Insert slices and retrieve
|
|
202
199
|
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice 1', status: 'complete' });
|
|
@@ -204,26 +201,24 @@ console.log('\n── markdown-renderer: DB accessor basics ──');
|
|
|
204
201
|
insertSlice({ id: 'S01', milestoneId: 'M002', title: 'M2 Slice', status: 'pending' });
|
|
205
202
|
|
|
206
203
|
const m1Slices = getMilestoneSlices('M001');
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
204
|
+
assert.deepStrictEqual(m1Slices.length, 2, 'M001 has 2 slices');
|
|
205
|
+
assert.deepStrictEqual(m1Slices[0].id, 'S01', 'first slice is S01');
|
|
206
|
+
assert.deepStrictEqual(m1Slices[0].status, 'complete', 'S01 status is complete');
|
|
207
|
+
assert.deepStrictEqual(m1Slices[1].id, 'S02', 'second slice is S02');
|
|
208
|
+
assert.deepStrictEqual(m1Slices[1].status, 'pending', 'S02 status is pending');
|
|
212
209
|
|
|
213
210
|
const m2Slices = getMilestoneSlices('M002');
|
|
214
|
-
|
|
211
|
+
assert.deepStrictEqual(m2Slices.length, 1, 'M002 has 1 slice');
|
|
215
212
|
|
|
216
213
|
closeDatabase();
|
|
217
|
-
}
|
|
214
|
+
});
|
|
218
215
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
{
|
|
216
|
+
test('── markdown-renderer: getArtifact accessor ──', () => {
|
|
222
217
|
openDatabase(':memory:');
|
|
223
218
|
|
|
224
219
|
// Not found
|
|
225
220
|
const missing = getArtifact('nonexistent/path');
|
|
226
|
-
|
|
221
|
+
assert.deepStrictEqual(missing, null, 'getArtifact returns null for missing path');
|
|
227
222
|
|
|
228
223
|
// Insert and retrieve
|
|
229
224
|
insertArtifact({
|
|
@@ -236,21 +231,19 @@ console.log('\n── markdown-renderer: getArtifact accessor ──');
|
|
|
236
231
|
});
|
|
237
232
|
|
|
238
233
|
const found = getArtifact('milestones/M001/M001-ROADMAP.md');
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
234
|
+
assert.ok(found !== null, 'getArtifact returns non-null for existing path');
|
|
235
|
+
assert.deepStrictEqual(found!.artifact_type, 'ROADMAP', 'artifact type correct');
|
|
236
|
+
assert.deepStrictEqual(found!.milestone_id, 'M001', 'milestone_id correct');
|
|
237
|
+
assert.deepStrictEqual(found!.full_content, '# Roadmap content', 'content correct');
|
|
243
238
|
|
|
244
239
|
closeDatabase();
|
|
245
|
-
}
|
|
240
|
+
});
|
|
246
241
|
|
|
247
242
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
248
243
|
// Roadmap Checkbox Round-Trip
|
|
249
244
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
250
245
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
{
|
|
246
|
+
test('── markdown-renderer: renderRoadmapCheckboxes round-trip ──', async () => {
|
|
254
247
|
const tmpDir = makeTmpDir();
|
|
255
248
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
256
249
|
openDatabase(dbPath);
|
|
@@ -275,36 +268,34 @@ console.log('\n── markdown-renderer: renderRoadmapCheckboxes round-trip ─
|
|
|
275
268
|
|
|
276
269
|
// Render — should set S01 [x] and leave S02 [ ]
|
|
277
270
|
const ok = await renderRoadmapCheckboxes(tmpDir, 'M001');
|
|
278
|
-
|
|
271
|
+
assert.ok(ok, 'renderRoadmapCheckboxes returns true');
|
|
279
272
|
|
|
280
273
|
// Read rendered file and parse
|
|
281
274
|
const rendered = fs.readFileSync(roadmapPath, 'utf-8');
|
|
282
275
|
clearAllCaches();
|
|
283
276
|
const parsed = parseRoadmap(rendered);
|
|
284
277
|
|
|
285
|
-
|
|
278
|
+
assert.deepStrictEqual(parsed.slices.length, 2, 'roadmap has 2 slices after render');
|
|
286
279
|
|
|
287
280
|
const s01 = parsed.slices.find(s => s.id === 'S01');
|
|
288
281
|
const s02 = parsed.slices.find(s => s.id === 'S02');
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
282
|
+
assert.ok(!!s01, 'S01 found in parsed roadmap');
|
|
283
|
+
assert.ok(!!s02, 'S02 found in parsed roadmap');
|
|
284
|
+
assert.ok(s01!.done, 'S01 is checked (done) after render');
|
|
285
|
+
assert.ok(!s02!.done, 'S02 is unchecked (pending) after render');
|
|
293
286
|
|
|
294
287
|
// Verify artifact stored in DB
|
|
295
288
|
const artifact = getArtifact('milestones/M001/M001-ROADMAP.md');
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
289
|
+
assert.ok(artifact !== null, 'roadmap artifact stored in DB after render');
|
|
290
|
+
assert.ok(artifact!.full_content.includes('[x] **S01:'), 'DB artifact has S01 checked');
|
|
291
|
+
assert.ok(artifact!.full_content.includes('[ ] **S02:'), 'DB artifact has S02 unchecked');
|
|
299
292
|
} finally {
|
|
300
293
|
closeDatabase();
|
|
301
294
|
cleanupDir(tmpDir);
|
|
302
295
|
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
console.log('\n── markdown-renderer: renderRoadmapCheckboxes bidirectional ──');
|
|
296
|
+
});
|
|
306
297
|
|
|
307
|
-
{
|
|
298
|
+
test('── markdown-renderer: renderRoadmapCheckboxes bidirectional ──', async () => {
|
|
308
299
|
const tmpDir = makeTmpDir();
|
|
309
300
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
310
301
|
openDatabase(dbPath);
|
|
@@ -328,7 +319,7 @@ console.log('\n── markdown-renderer: renderRoadmapCheckboxes bidirectional
|
|
|
328
319
|
clearAllCaches();
|
|
329
320
|
|
|
330
321
|
const ok = await renderRoadmapCheckboxes(tmpDir, 'M001');
|
|
331
|
-
|
|
322
|
+
assert.ok(ok, 'bidirectional render returns true');
|
|
332
323
|
|
|
333
324
|
const rendered = fs.readFileSync(roadmapPath, 'utf-8');
|
|
334
325
|
clearAllCaches();
|
|
@@ -336,21 +327,19 @@ console.log('\n── markdown-renderer: renderRoadmapCheckboxes bidirectional
|
|
|
336
327
|
|
|
337
328
|
const s01 = parsed.slices.find(s => s.id === 'S01');
|
|
338
329
|
const s02 = parsed.slices.find(s => s.id === 'S02');
|
|
339
|
-
|
|
340
|
-
|
|
330
|
+
assert.ok(!s01!.done, 'S01 unchecked (DB says pending, was checked on disk)');
|
|
331
|
+
assert.ok(s02!.done, 'S02 checked (DB says complete, was unchecked on disk)');
|
|
341
332
|
} finally {
|
|
342
333
|
closeDatabase();
|
|
343
334
|
cleanupDir(tmpDir);
|
|
344
335
|
}
|
|
345
|
-
}
|
|
336
|
+
});
|
|
346
337
|
|
|
347
338
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
348
339
|
// Plan Checkbox Round-Trip
|
|
349
340
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
350
341
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
{
|
|
342
|
+
test('── markdown-renderer: renderPlanCheckboxes round-trip ──', async () => {
|
|
354
343
|
const tmpDir = makeTmpDir();
|
|
355
344
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
356
345
|
openDatabase(dbPath);
|
|
@@ -376,29 +365,27 @@ console.log('\n── markdown-renderer: renderPlanCheckboxes round-trip ──'
|
|
|
376
365
|
clearAllCaches();
|
|
377
366
|
|
|
378
367
|
const ok = await renderPlanCheckboxes(tmpDir, 'M001', 'S01');
|
|
379
|
-
|
|
368
|
+
assert.ok(ok, 'renderPlanCheckboxes returns true');
|
|
380
369
|
|
|
381
370
|
const rendered = fs.readFileSync(planPath, 'utf-8');
|
|
382
371
|
clearAllCaches();
|
|
383
372
|
const parsed = parsePlan(rendered);
|
|
384
373
|
|
|
385
|
-
|
|
374
|
+
assert.deepStrictEqual(parsed.tasks.length, 3, 'plan has 3 tasks after render');
|
|
386
375
|
|
|
387
376
|
const t01 = parsed.tasks.find(t => t.id === 'T01');
|
|
388
377
|
const t02 = parsed.tasks.find(t => t.id === 'T02');
|
|
389
378
|
const t03 = parsed.tasks.find(t => t.id === 'T03');
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
379
|
+
assert.ok(t01!.done, 'T01 checked (done in DB)');
|
|
380
|
+
assert.ok(t02!.done, 'T02 checked (done in DB)');
|
|
381
|
+
assert.ok(!t03!.done, 'T03 unchecked (pending in DB)');
|
|
393
382
|
} finally {
|
|
394
383
|
closeDatabase();
|
|
395
384
|
cleanupDir(tmpDir);
|
|
396
385
|
}
|
|
397
|
-
}
|
|
386
|
+
});
|
|
398
387
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
{
|
|
388
|
+
test('── markdown-renderer: renderPlanCheckboxes bidirectional ──', async () => {
|
|
402
389
|
const tmpDir = makeTmpDir();
|
|
403
390
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
404
391
|
openDatabase(dbPath);
|
|
@@ -422,7 +409,7 @@ console.log('\n── markdown-renderer: renderPlanCheckboxes bidirectional ─
|
|
|
422
409
|
clearAllCaches();
|
|
423
410
|
|
|
424
411
|
const ok = await renderPlanCheckboxes(tmpDir, 'M001', 'S01');
|
|
425
|
-
|
|
412
|
+
assert.ok(ok, 'bidirectional plan render returns true');
|
|
426
413
|
|
|
427
414
|
const rendered = fs.readFileSync(planPath, 'utf-8');
|
|
428
415
|
clearAllCaches();
|
|
@@ -430,17 +417,15 @@ console.log('\n── markdown-renderer: renderPlanCheckboxes bidirectional ─
|
|
|
430
417
|
|
|
431
418
|
const t01 = parsed.tasks.find(t => t.id === 'T01');
|
|
432
419
|
const t02 = parsed.tasks.find(t => t.id === 'T02');
|
|
433
|
-
|
|
434
|
-
|
|
420
|
+
assert.ok(!t01!.done, 'T01 unchecked (DB says pending, was checked)');
|
|
421
|
+
assert.ok(t02!.done, 'T02 checked (DB says done, was unchecked)');
|
|
435
422
|
} finally {
|
|
436
423
|
closeDatabase();
|
|
437
424
|
cleanupDir(tmpDir);
|
|
438
425
|
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
console.log('\n── markdown-renderer: renderPlanFromDb creates parse-compatible slice plan + task plan files ──');
|
|
426
|
+
});
|
|
442
427
|
|
|
443
|
-
{
|
|
428
|
+
test('── markdown-renderer: renderPlanFromDb creates parse-compatible slice plan + task plan files ──', async () => {
|
|
444
429
|
const tmpDir = makeTmpDir();
|
|
445
430
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
446
431
|
openDatabase(dbPath);
|
|
@@ -498,50 +483,48 @@ console.log('\n── markdown-renderer: renderPlanFromDb creates parse-compatib
|
|
|
498
483
|
});
|
|
499
484
|
|
|
500
485
|
const rendered = await renderPlanFromDb(tmpDir, 'M001', 'S02');
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
486
|
+
assert.ok(fs.existsSync(rendered.planPath), 'slice plan written to disk');
|
|
487
|
+
assert.strictEqual(rendered.taskPlanPaths.length, 2, 'task plan paths returned for each task');
|
|
488
|
+
assert.ok(rendered.taskPlanPaths.every((p) => fs.existsSync(p)), 'all task plan files written to disk');
|
|
504
489
|
|
|
505
490
|
const planContent = fs.readFileSync(rendered.planPath, 'utf-8');
|
|
506
491
|
clearAllCaches();
|
|
507
492
|
const parsedPlan = parsePlan(planContent);
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
493
|
+
assert.strictEqual(parsedPlan.id, 'S02', 'rendered slice plan parses with correct slice id');
|
|
494
|
+
assert.strictEqual(parsedPlan.goal, 'Render slice plans from DB state.', 'rendered slice plan preserves goal');
|
|
495
|
+
assert.strictEqual(parsedPlan.demo, 'Rendered plans exist on disk.', 'rendered slice plan preserves demo');
|
|
496
|
+
assert.strictEqual(parsedPlan.mustHaves.length, 2, 'rendered slice plan exposes must-haves');
|
|
497
|
+
assert.strictEqual(parsedPlan.tasks.length, 2, 'rendered slice plan exposes all tasks');
|
|
498
|
+
assert.strictEqual(parsedPlan.tasks[0].id, 'T01', 'first task parses correctly');
|
|
499
|
+
assert.ok(parsedPlan.tasks[0].description.includes('DB-backed slice plan renderer'), 'task description preserved in slice plan');
|
|
500
|
+
assert.strictEqual(parsedPlan.tasks[0].files?.[0], 'src/resources/extensions/gsd/markdown-renderer.ts', 'files list preserved in slice plan');
|
|
501
|
+
assert.strictEqual(parsedPlan.tasks[0].verify, 'node --test markdown-renderer.test.ts', 'verify line preserved in slice plan');
|
|
517
502
|
|
|
518
503
|
const planArtifact = getArtifact('milestones/M001/slices/S02/S02-PLAN.md');
|
|
519
|
-
|
|
520
|
-
|
|
504
|
+
assert.ok(planArtifact !== null, 'slice plan artifact stored in DB');
|
|
505
|
+
assert.ok(planArtifact!.full_content.includes('## Tasks'), 'stored plan artifact contains task section');
|
|
521
506
|
|
|
522
507
|
const taskPlanPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S02', 'tasks', 'T01-PLAN.md');
|
|
523
508
|
const taskPlanContent = fs.readFileSync(taskPlanPath, 'utf-8');
|
|
524
509
|
const taskPlanFile = parseTaskPlanFile(taskPlanContent);
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
510
|
+
assert.strictEqual(taskPlanFile.frontmatter.estimated_steps, 1, 'task plan frontmatter exposes estimated_steps');
|
|
511
|
+
assert.strictEqual(taskPlanFile.frontmatter.estimated_files, 1, 'task plan frontmatter exposes estimated_files');
|
|
512
|
+
assert.strictEqual(taskPlanFile.frontmatter.skills_used.length, 0, 'task plan frontmatter uses conservative empty skills list');
|
|
513
|
+
assert.match(taskPlanContent, /^# T01: Render slice plan/m, 'task plan renders task heading');
|
|
514
|
+
assert.match(taskPlanContent, /^## Inputs$/m, 'task plan renders Inputs section');
|
|
515
|
+
assert.match(taskPlanContent, /^## Expected Output$/m, 'task plan renders Expected Output section');
|
|
516
|
+
assert.match(taskPlanContent, /^## Verification$/m, 'task plan renders Verification section');
|
|
532
517
|
|
|
533
518
|
const taskArtifact = getArtifact('milestones/M001/slices/S02/tasks/T01-PLAN.md');
|
|
534
|
-
|
|
535
|
-
|
|
519
|
+
assert.ok(taskArtifact !== null, 'task plan artifact stored in DB');
|
|
520
|
+
assert.ok(taskArtifact!.full_content.includes('skills_used: []'), 'stored task plan artifact preserves conservative skills_used');
|
|
536
521
|
} finally {
|
|
537
522
|
closeDatabase();
|
|
538
523
|
cleanupDir(tmpDir);
|
|
539
524
|
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
console.log('\n── markdown-renderer: renderTaskPlanFromDb throws for missing task ──');
|
|
525
|
+
});
|
|
543
526
|
|
|
544
|
-
{
|
|
527
|
+
test('── markdown-renderer: renderTaskPlanFromDb throws for missing task ──', async () => {
|
|
545
528
|
const tmpDir = makeTmpDir();
|
|
546
529
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
547
530
|
openDatabase(dbPath);
|
|
@@ -557,23 +540,21 @@ console.log('\n── markdown-renderer: renderTaskPlanFromDb throws for missing
|
|
|
557
540
|
await renderTaskPlanFromDb(tmpDir, 'M001', 'S02', 'T99');
|
|
558
541
|
} catch (error) {
|
|
559
542
|
threw = true;
|
|
560
|
-
|
|
543
|
+
assert.match(String((error as Error).message), /task M001\/S02\/T99 not found/, 'renderTaskPlanFromDb should fail clearly when task row is missing');
|
|
561
544
|
}
|
|
562
|
-
|
|
545
|
+
assert.ok(threw, 'renderTaskPlanFromDb throws when the task row is missing');
|
|
563
546
|
} finally {
|
|
564
547
|
closeDatabase();
|
|
565
548
|
cleanupDir(tmpDir);
|
|
566
549
|
}
|
|
567
|
-
}
|
|
550
|
+
});
|
|
568
551
|
|
|
569
552
|
|
|
570
553
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
571
554
|
// Task Summary Rendering
|
|
572
555
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
573
556
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
{
|
|
557
|
+
test('── markdown-renderer: renderTaskSummary round-trip ──', async () => {
|
|
577
558
|
const tmpDir = makeTmpDir();
|
|
578
559
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
579
560
|
openDatabase(dbPath);
|
|
@@ -596,33 +577,31 @@ console.log('\n── markdown-renderer: renderTaskSummary round-trip ──');
|
|
|
596
577
|
});
|
|
597
578
|
|
|
598
579
|
const ok = await renderTaskSummary(tmpDir, 'M001', 'S01', 'T01');
|
|
599
|
-
|
|
580
|
+
assert.ok(ok, 'renderTaskSummary returns true');
|
|
600
581
|
|
|
601
582
|
// Verify file exists on disk
|
|
602
583
|
const summaryPath = path.join(
|
|
603
584
|
tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'tasks', 'T01-SUMMARY.md',
|
|
604
585
|
);
|
|
605
|
-
|
|
586
|
+
assert.ok(fs.existsSync(summaryPath), 'T01-SUMMARY.md written to disk');
|
|
606
587
|
|
|
607
588
|
// Parse and verify
|
|
608
589
|
const rendered = fs.readFileSync(summaryPath, 'utf-8');
|
|
609
590
|
clearAllCaches();
|
|
610
591
|
const parsed = parseSummary(rendered);
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
592
|
+
assert.deepStrictEqual(parsed.frontmatter.id, 'T01', 'parsed summary has correct id');
|
|
593
|
+
assert.deepStrictEqual(parsed.frontmatter.parent, 'S01', 'parsed summary has correct parent');
|
|
594
|
+
assert.deepStrictEqual(parsed.frontmatter.milestone, 'M001', 'parsed summary has correct milestone');
|
|
595
|
+
assert.deepStrictEqual(parsed.frontmatter.duration, '45m', 'parsed summary has correct duration');
|
|
596
|
+
assert.ok(parsed.title.includes('T01'), 'parsed summary title contains task ID');
|
|
597
|
+
assert.ok(parsed.whatHappened.includes('Built the test feature'), 'whatHappened content preserved');
|
|
617
598
|
} finally {
|
|
618
599
|
closeDatabase();
|
|
619
600
|
cleanupDir(tmpDir);
|
|
620
601
|
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
console.log('\n── markdown-renderer: renderTaskSummary skips empty ──');
|
|
602
|
+
});
|
|
624
603
|
|
|
625
|
-
{
|
|
604
|
+
test('── markdown-renderer: renderTaskSummary skips empty ──', async () => {
|
|
626
605
|
const tmpDir = makeTmpDir();
|
|
627
606
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
628
607
|
openDatabase(dbPath);
|
|
@@ -643,20 +622,18 @@ console.log('\n── markdown-renderer: renderTaskSummary skips empty ──');
|
|
|
643
622
|
});
|
|
644
623
|
|
|
645
624
|
const ok = await renderTaskSummary(tmpDir, 'M001', 'S01', 'T01');
|
|
646
|
-
|
|
625
|
+
assert.ok(!ok, 'renderTaskSummary returns false for empty summary');
|
|
647
626
|
} finally {
|
|
648
627
|
closeDatabase();
|
|
649
628
|
cleanupDir(tmpDir);
|
|
650
629
|
}
|
|
651
|
-
}
|
|
630
|
+
});
|
|
652
631
|
|
|
653
632
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
654
633
|
// Slice Summary Rendering
|
|
655
634
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
656
635
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
{
|
|
636
|
+
test('── markdown-renderer: renderSliceSummary round-trip ──', async () => {
|
|
660
637
|
const tmpDir = makeTmpDir();
|
|
661
638
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
662
639
|
openDatabase(dbPath);
|
|
@@ -680,38 +657,36 @@ console.log('\n── markdown-renderer: renderSliceSummary round-trip ──');
|
|
|
680
657
|
});
|
|
681
658
|
|
|
682
659
|
const ok = await renderSliceSummary(tmpDir, 'M001', 'S01');
|
|
683
|
-
|
|
660
|
+
assert.ok(ok, 'renderSliceSummary returns true');
|
|
684
661
|
|
|
685
662
|
// Verify SUMMARY file
|
|
686
663
|
const summaryPath = path.join(
|
|
687
664
|
tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-SUMMARY.md',
|
|
688
665
|
);
|
|
689
|
-
|
|
666
|
+
assert.ok(fs.existsSync(summaryPath), 'S01-SUMMARY.md written to disk');
|
|
690
667
|
|
|
691
668
|
const summaryContent = fs.readFileSync(summaryPath, 'utf-8');
|
|
692
|
-
|
|
669
|
+
assert.ok(summaryContent.includes('Test Slice Summary'), 'summary content correct');
|
|
693
670
|
|
|
694
671
|
// Verify UAT file
|
|
695
672
|
const uatPath = path.join(
|
|
696
673
|
tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-UAT.md',
|
|
697
674
|
);
|
|
698
|
-
|
|
675
|
+
assert.ok(fs.existsSync(uatPath), 'S01-UAT.md written to disk');
|
|
699
676
|
|
|
700
677
|
const uatContent = fs.readFileSync(uatPath, 'utf-8');
|
|
701
|
-
|
|
678
|
+
assert.ok(uatContent.includes('artifact-driven'), 'UAT content correct');
|
|
702
679
|
} finally {
|
|
703
680
|
closeDatabase();
|
|
704
681
|
cleanupDir(tmpDir);
|
|
705
682
|
}
|
|
706
|
-
}
|
|
683
|
+
});
|
|
707
684
|
|
|
708
685
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
709
686
|
// renderAllFromDb
|
|
710
687
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
711
688
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
{
|
|
689
|
+
test('── markdown-renderer: renderAllFromDb produces all files ──', async () => {
|
|
715
690
|
const tmpDir = makeTmpDir();
|
|
716
691
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
717
692
|
openDatabase(dbPath);
|
|
@@ -779,8 +754,8 @@ console.log('\n── markdown-renderer: renderAllFromDb produces all files ─
|
|
|
779
754
|
|
|
780
755
|
const result = await renderAllFromDb(tmpDir);
|
|
781
756
|
|
|
782
|
-
|
|
783
|
-
|
|
757
|
+
assert.ok(result.rendered > 0, 'renderAllFromDb rendered some files');
|
|
758
|
+
assert.deepStrictEqual(result.errors.length, 0, 'renderAllFromDb had no errors');
|
|
784
759
|
|
|
785
760
|
// Verify M001 roadmap has S01 checked
|
|
786
761
|
const m1Roadmap = fs.readFileSync(
|
|
@@ -789,7 +764,7 @@ console.log('\n── markdown-renderer: renderAllFromDb produces all files ─
|
|
|
789
764
|
clearAllCaches();
|
|
790
765
|
const parsed1 = parseRoadmap(m1Roadmap);
|
|
791
766
|
const s01 = parsed1.slices.find(s => s.id === 'S01');
|
|
792
|
-
|
|
767
|
+
assert.ok(s01!.done, 'M001 S01 checked after renderAll');
|
|
793
768
|
|
|
794
769
|
// Verify M001/S01 plan has T01 checked
|
|
795
770
|
const m1s1Plan = fs.readFileSync(
|
|
@@ -797,26 +772,24 @@ console.log('\n── markdown-renderer: renderAllFromDb produces all files ─
|
|
|
797
772
|
);
|
|
798
773
|
clearAllCaches();
|
|
799
774
|
const parsedPlan = parsePlan(m1s1Plan);
|
|
800
|
-
|
|
775
|
+
assert.ok(parsedPlan.tasks[0].done, 'M001/S01 T01 checked after renderAll');
|
|
801
776
|
|
|
802
777
|
// Verify task summary written
|
|
803
778
|
const taskSummaryPath = path.join(
|
|
804
779
|
tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'tasks', 'T01-SUMMARY.md',
|
|
805
780
|
);
|
|
806
|
-
|
|
781
|
+
assert.ok(fs.existsSync(taskSummaryPath), 'T01 summary written by renderAll');
|
|
807
782
|
} finally {
|
|
808
783
|
closeDatabase();
|
|
809
784
|
cleanupDir(tmpDir);
|
|
810
785
|
}
|
|
811
|
-
}
|
|
786
|
+
});
|
|
812
787
|
|
|
813
788
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
814
789
|
// Graceful Degradation (Disk Fallback)
|
|
815
790
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
816
791
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
{
|
|
792
|
+
test('── markdown-renderer: graceful fallback reads from disk when artifact not in DB ──', async () => {
|
|
820
793
|
const tmpDir = makeTmpDir();
|
|
821
794
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
822
795
|
openDatabase(dbPath);
|
|
@@ -838,29 +811,27 @@ console.log('\n── markdown-renderer: graceful fallback reads from disk when
|
|
|
838
811
|
|
|
839
812
|
// Verify no artifact in DB
|
|
840
813
|
const before = getArtifact('milestones/M001/M001-ROADMAP.md');
|
|
841
|
-
|
|
814
|
+
assert.deepStrictEqual(before, null, 'artifact not in DB before render');
|
|
842
815
|
|
|
843
816
|
// Render — should read from disk, store in DB
|
|
844
817
|
const ok = await renderRoadmapCheckboxes(tmpDir, 'M001');
|
|
845
|
-
|
|
818
|
+
assert.ok(ok, 'render succeeds with disk fallback');
|
|
846
819
|
|
|
847
820
|
// Verify artifact now in DB (stored after reading from disk)
|
|
848
821
|
const after = getArtifact('milestones/M001/M001-ROADMAP.md');
|
|
849
|
-
|
|
850
|
-
|
|
822
|
+
assert.ok(after !== null, 'artifact stored in DB after disk fallback render');
|
|
823
|
+
assert.ok(after!.full_content.includes('[x] **S01:'), 'DB artifact reflects rendered state');
|
|
851
824
|
} finally {
|
|
852
825
|
closeDatabase();
|
|
853
826
|
cleanupDir(tmpDir);
|
|
854
827
|
}
|
|
855
|
-
}
|
|
828
|
+
});
|
|
856
829
|
|
|
857
830
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
858
831
|
// stderr warnings (graceful degradation diagnostics)
|
|
859
832
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
860
833
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
{
|
|
834
|
+
test('── markdown-renderer: stderr warning on missing content ──', async () => {
|
|
864
835
|
openDatabase(':memory:');
|
|
865
836
|
|
|
866
837
|
// No milestone/slices in DB, no files on disk — should return false and emit stderr
|
|
@@ -868,18 +839,16 @@ console.log('\n── markdown-renderer: stderr warning on missing content ─
|
|
|
868
839
|
// No slices inserted — should warn about no slices
|
|
869
840
|
|
|
870
841
|
const ok = await renderRoadmapCheckboxes('/nonexistent/path', 'M001');
|
|
871
|
-
|
|
842
|
+
assert.ok(!ok, 'returns false when no slices in DB');
|
|
872
843
|
|
|
873
844
|
closeDatabase();
|
|
874
|
-
}
|
|
845
|
+
});
|
|
875
846
|
|
|
876
847
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
877
848
|
// Stale Detection — Plan Checkbox Mismatch
|
|
878
849
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
879
850
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
{
|
|
851
|
+
test('── markdown-renderer: detectStaleRenders finds plan checkbox mismatch ──', () => {
|
|
883
852
|
const tmpDir = makeTmpDir();
|
|
884
853
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
885
854
|
openDatabase(dbPath);
|
|
@@ -910,27 +879,25 @@ console.log('\n── markdown-renderer: detectStaleRenders finds plan checkbox
|
|
|
910
879
|
// The stale detection should find T02 as stale.
|
|
911
880
|
const stale = detectStaleRenders(tmpDir);
|
|
912
881
|
|
|
913
|
-
|
|
882
|
+
assert.ok(stale.length > 0, 'detectStaleRenders should find stale entries');
|
|
914
883
|
const t02Stale = stale.find(s => s.reason.includes('T02'));
|
|
915
|
-
|
|
916
|
-
|
|
884
|
+
assert.ok(!!t02Stale, 'should detect T02 as stale (done in DB, unchecked in plan)');
|
|
885
|
+
assert.ok(t02Stale!.reason.includes('done in DB but unchecked'), 'reason should explain the mismatch');
|
|
917
886
|
|
|
918
887
|
// T01 should NOT be stale — it's checked and done
|
|
919
888
|
const t01Stale = stale.find(s => s.reason.includes('T01'));
|
|
920
|
-
|
|
889
|
+
assert.deepStrictEqual(t01Stale, undefined, 'T01 should not be stale (done and checked)');
|
|
921
890
|
} finally {
|
|
922
891
|
closeDatabase();
|
|
923
892
|
cleanupDir(tmpDir);
|
|
924
893
|
}
|
|
925
|
-
}
|
|
894
|
+
});
|
|
926
895
|
|
|
927
896
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
928
897
|
// Stale Repair — Plan Checkbox
|
|
929
898
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
930
899
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
{
|
|
900
|
+
test('── markdown-renderer: repairStaleRenders fixes plan and second detect returns empty ──', async () => {
|
|
934
901
|
const tmpDir = makeTmpDir();
|
|
935
902
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
936
903
|
openDatabase(dbPath);
|
|
@@ -956,34 +923,32 @@ console.log('\n── markdown-renderer: repairStaleRenders fixes plan and secon
|
|
|
956
923
|
|
|
957
924
|
// Verify stale before repair
|
|
958
925
|
const staleBefore = detectStaleRenders(tmpDir);
|
|
959
|
-
|
|
926
|
+
assert.ok(staleBefore.length > 0, 'should have stale entries before repair');
|
|
960
927
|
|
|
961
928
|
// Repair
|
|
962
929
|
const repaired = await repairStaleRenders(tmpDir);
|
|
963
|
-
|
|
930
|
+
assert.ok(repaired > 0, 'repairStaleRenders should repair at least 1 file');
|
|
964
931
|
|
|
965
932
|
// After repair, detect again — should be empty
|
|
966
933
|
clearAllCaches();
|
|
967
934
|
const staleAfter = detectStaleRenders(tmpDir);
|
|
968
|
-
|
|
935
|
+
assert.deepStrictEqual(staleAfter.length, 0, 'detectStaleRenders should return empty after repair');
|
|
969
936
|
|
|
970
937
|
// Verify the plan file was actually updated
|
|
971
938
|
const repairedContent = fs.readFileSync(planPath, 'utf-8');
|
|
972
|
-
|
|
973
|
-
|
|
939
|
+
assert.ok(repairedContent.includes('[x] **T01:'), 'T01 should be checked after repair');
|
|
940
|
+
assert.ok(repairedContent.includes('[x] **T02:'), 'T02 should be checked after repair');
|
|
974
941
|
} finally {
|
|
975
942
|
closeDatabase();
|
|
976
943
|
cleanupDir(tmpDir);
|
|
977
944
|
}
|
|
978
|
-
}
|
|
945
|
+
});
|
|
979
946
|
|
|
980
947
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
981
948
|
// Stale Detection — Roadmap Checkbox Mismatch
|
|
982
949
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
983
950
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
{
|
|
951
|
+
test('── markdown-renderer: detectStaleRenders finds roadmap checkbox mismatch ──', () => {
|
|
987
952
|
const tmpDir = makeTmpDir();
|
|
988
953
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
989
954
|
openDatabase(dbPath);
|
|
@@ -1007,23 +972,21 @@ console.log('\n── markdown-renderer: detectStaleRenders finds roadmap checkb
|
|
|
1007
972
|
|
|
1008
973
|
const stale = detectStaleRenders(tmpDir);
|
|
1009
974
|
const s01Stale = stale.find(s => s.reason.includes('S01'));
|
|
1010
|
-
|
|
975
|
+
assert.ok(!!s01Stale, 'should detect S01 as stale (complete in DB, unchecked in roadmap)');
|
|
1011
976
|
|
|
1012
977
|
const s02Stale = stale.find(s => s.reason.includes('S02'));
|
|
1013
|
-
|
|
978
|
+
assert.deepStrictEqual(s02Stale, undefined, 'S02 should not be stale (pending and unchecked — matches)');
|
|
1014
979
|
} finally {
|
|
1015
980
|
closeDatabase();
|
|
1016
981
|
cleanupDir(tmpDir);
|
|
1017
982
|
}
|
|
1018
|
-
}
|
|
983
|
+
});
|
|
1019
984
|
|
|
1020
985
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1021
986
|
// Stale Detection — Missing Task Summary
|
|
1022
987
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1023
988
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
{
|
|
989
|
+
test('── markdown-renderer: detectStaleRenders finds missing task summary ──', () => {
|
|
1027
990
|
const tmpDir = makeTmpDir();
|
|
1028
991
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
1029
992
|
openDatabase(dbPath);
|
|
@@ -1058,21 +1021,19 @@ console.log('\n── markdown-renderer: detectStaleRenders finds missing task s
|
|
|
1058
1021
|
|
|
1059
1022
|
const stale = detectStaleRenders(tmpDir);
|
|
1060
1023
|
const summaryStale = stale.find(s => s.reason.includes('SUMMARY.md missing'));
|
|
1061
|
-
|
|
1062
|
-
|
|
1024
|
+
assert.ok(!!summaryStale, 'should detect missing T01-SUMMARY.md');
|
|
1025
|
+
assert.ok(summaryStale!.reason.includes('T01'), 'reason should mention T01');
|
|
1063
1026
|
} finally {
|
|
1064
1027
|
closeDatabase();
|
|
1065
1028
|
cleanupDir(tmpDir);
|
|
1066
1029
|
}
|
|
1067
|
-
}
|
|
1030
|
+
});
|
|
1068
1031
|
|
|
1069
1032
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1070
1033
|
// Stale Repair — Missing Task Summary
|
|
1071
1034
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1072
1035
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
{
|
|
1036
|
+
test('── markdown-renderer: repairStaleRenders writes missing task summary ──', async () => {
|
|
1076
1037
|
const tmpDir = makeTmpDir();
|
|
1077
1038
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
1078
1039
|
openDatabase(dbPath);
|
|
@@ -1104,32 +1065,30 @@ console.log('\n── markdown-renderer: repairStaleRenders writes missing task
|
|
|
1104
1065
|
|
|
1105
1066
|
// Repair
|
|
1106
1067
|
const repaired = await repairStaleRenders(tmpDir);
|
|
1107
|
-
|
|
1068
|
+
assert.ok(repaired > 0, 'should repair missing summary');
|
|
1108
1069
|
|
|
1109
1070
|
// Verify file written
|
|
1110
1071
|
const summaryPath = path.join(
|
|
1111
1072
|
tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'tasks', 'T01-SUMMARY.md',
|
|
1112
1073
|
);
|
|
1113
|
-
|
|
1074
|
+
assert.ok(fs.existsSync(summaryPath), 'T01-SUMMARY.md should exist after repair');
|
|
1114
1075
|
|
|
1115
1076
|
// Second detect should be empty
|
|
1116
1077
|
clearAllCaches();
|
|
1117
1078
|
const staleAfter = detectStaleRenders(tmpDir);
|
|
1118
1079
|
const summaryStale = staleAfter.find(s => s.reason.includes('SUMMARY.md missing') && s.reason.includes('T01'));
|
|
1119
|
-
|
|
1080
|
+
assert.deepStrictEqual(summaryStale, undefined, 'missing summary should be fixed after repair');
|
|
1120
1081
|
} finally {
|
|
1121
1082
|
closeDatabase();
|
|
1122
1083
|
cleanupDir(tmpDir);
|
|
1123
1084
|
}
|
|
1124
|
-
}
|
|
1085
|
+
});
|
|
1125
1086
|
|
|
1126
1087
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1127
1088
|
// Stale Repair — Idempotency
|
|
1128
1089
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1129
1090
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
{
|
|
1091
|
+
test('── markdown-renderer: repairStaleRenders idempotency — fully synced returns 0 ──', async () => {
|
|
1133
1092
|
const tmpDir = makeTmpDir();
|
|
1134
1093
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
1135
1094
|
openDatabase(dbPath);
|
|
@@ -1152,20 +1111,18 @@ console.log('\n── markdown-renderer: repairStaleRenders idempotency — full
|
|
|
1152
1111
|
|
|
1153
1112
|
// No stale entries when everything is in sync (no summary to check since no fullSummaryMd)
|
|
1154
1113
|
const repaired = await repairStaleRenders(tmpDir);
|
|
1155
|
-
|
|
1114
|
+
assert.deepStrictEqual(repaired, 0, 'repairStaleRenders should return 0 on fully synced project');
|
|
1156
1115
|
} finally {
|
|
1157
1116
|
closeDatabase();
|
|
1158
1117
|
cleanupDir(tmpDir);
|
|
1159
1118
|
}
|
|
1160
|
-
}
|
|
1119
|
+
});
|
|
1161
1120
|
|
|
1162
1121
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1163
1122
|
// Stale Detection — Missing Slice Summary + UAT
|
|
1164
1123
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1165
1124
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
{
|
|
1125
|
+
test('── markdown-renderer: detectStaleRenders finds missing slice summary and UAT ──', () => {
|
|
1169
1126
|
const tmpDir = makeTmpDir();
|
|
1170
1127
|
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
|
1171
1128
|
openDatabase(dbPath);
|
|
@@ -1192,14 +1149,13 @@ console.log('\n── markdown-renderer: detectStaleRenders finds missing slice
|
|
|
1192
1149
|
const summaryStale = stale.find(s => s.reason.includes('SUMMARY.md missing') && s.reason.includes('S01'));
|
|
1193
1150
|
const uatStale = stale.find(s => s.reason.includes('UAT.md missing') && s.reason.includes('S01'));
|
|
1194
1151
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1152
|
+
assert.ok(!!summaryStale, 'should detect missing S01-SUMMARY.md');
|
|
1153
|
+
assert.ok(!!uatStale, 'should detect missing S01-UAT.md');
|
|
1197
1154
|
} finally {
|
|
1198
1155
|
closeDatabase();
|
|
1199
1156
|
cleanupDir(tmpDir);
|
|
1200
1157
|
}
|
|
1201
|
-
}
|
|
1158
|
+
});
|
|
1202
1159
|
|
|
1203
1160
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1204
1161
|
|
|
1205
|
-
report();
|