gsd-pi 2.45.0-dev.fdcf73c → 2.46.0-dev.cc9d310
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/dist/resources/extensions/gsd/auto/phases.js +14 -35
- package/dist/resources/extensions/gsd/auto/session.js +0 -11
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +112 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +25 -96
- package/dist/resources/extensions/gsd/auto-start.js +2 -3
- package/dist/resources/extensions/gsd/auto.js +8 -52
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +18 -0
- package/dist/resources/extensions/gsd/commands/context.js +0 -4
- package/dist/resources/extensions/gsd/commands/handlers/parallel.js +1 -1
- package/dist/resources/extensions/gsd/crash-recovery.js +2 -4
- package/dist/resources/extensions/gsd/dashboard-overlay.js +0 -44
- package/dist/resources/extensions/gsd/doctor-checks.js +166 -1
- package/dist/resources/extensions/gsd/doctor.js +3 -1
- package/dist/resources/extensions/gsd/gsd-db.js +11 -2
- package/dist/resources/extensions/gsd/guided-flow.js +1 -2
- package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +5 -18
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +10 -23
- package/dist/resources/extensions/gsd/prompts/discuss.md +2 -2
- package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -15
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/dist/resources/extensions/gsd/prompts/queue.md +2 -2
- package/dist/resources/extensions/gsd/prompts/quick-task.md +2 -0
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -3
- package/dist/resources/extensions/gsd/prompts/rethink.md +7 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/session-lock.js +1 -3
- package/dist/resources/extensions/gsd/state.js +7 -0
- package/dist/resources/extensions/gsd/sync-lock.js +89 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +58 -12
- package/dist/resources/extensions/gsd/tools/complete-slice.js +56 -11
- package/dist/resources/extensions/gsd/tools/complete-task.js +50 -2
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +37 -1
- package/dist/resources/extensions/gsd/tools/plan-slice.js +30 -1
- package/dist/resources/extensions/gsd/tools/plan-task.js +27 -1
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +32 -2
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +86 -0
- package/dist/resources/extensions/gsd/tools/reopen-task.js +90 -0
- package/dist/resources/extensions/gsd/tools/replan-slice.js +32 -2
- package/dist/resources/extensions/gsd/unit-ownership.js +85 -0
- package/dist/resources/extensions/gsd/workflow-events.js +102 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +56 -1
- package/dist/resources/extensions/gsd/workflow-manifest.js +244 -0
- package/dist/resources/extensions/gsd/workflow-migration.js +280 -0
- package/dist/resources/extensions/gsd/workflow-projections.js +373 -0
- package/dist/resources/extensions/gsd/workflow-reconcile.js +411 -0
- package/dist/resources/extensions/gsd/write-intercept.js +84 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
- 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 +17 -17
- 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/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -19
- package/src/resources/extensions/gsd/auto/phases.ts +11 -35
- package/src/resources/extensions/gsd/auto/session.ts +0 -18
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +131 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +0 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +25 -106
- package/src/resources/extensions/gsd/auto-start.ts +1 -3
- package/src/resources/extensions/gsd/auto.ts +4 -80
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
- package/src/resources/extensions/gsd/commands/context.ts +0 -5
- package/src/resources/extensions/gsd/commands/handlers/parallel.ts +1 -1
- package/src/resources/extensions/gsd/crash-recovery.ts +1 -5
- package/src/resources/extensions/gsd/dashboard-overlay.ts +0 -50
- package/src/resources/extensions/gsd/doctor-checks.ts +179 -1
- package/src/resources/extensions/gsd/doctor-types.ts +7 -1
- package/src/resources/extensions/gsd/doctor.ts +4 -1
- package/src/resources/extensions/gsd/gsd-db.ts +11 -2
- package/src/resources/extensions/gsd/guided-flow.ts +1 -2
- package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +5 -21
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +10 -23
- package/src/resources/extensions/gsd/prompts/discuss.md +2 -2
- package/src/resources/extensions/gsd/prompts/execute-task.md +5 -15
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/src/resources/extensions/gsd/prompts/queue.md +2 -2
- package/src/resources/extensions/gsd/prompts/quick-task.md +2 -0
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/rethink.md +7 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/session-lock.ts +0 -4
- package/src/resources/extensions/gsd/state.ts +8 -0
- package/src/resources/extensions/gsd/sync-lock.ts +94 -0
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +5 -13
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +6 -10
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +264 -228
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +317 -250
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +2 -8
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration-proof.test.ts +15 -24
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +8 -9
- package/src/resources/extensions/gsd/tests/parallel-budget-atomicity.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +0 -7
- package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +20 -24
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +0 -2
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +9 -6
- package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/projection-regression.test.ts +174 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +15 -14
- package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +155 -0
- package/src/resources/extensions/gsd/tests/reopen-task.test.ts +165 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +1 -4
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/sync-lock.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/unit-ownership.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/workflow-events.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +76 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +70 -13
- package/src/resources/extensions/gsd/tools/complete-slice.ts +68 -11
- package/src/resources/extensions/gsd/tools/complete-task.ts +63 -1
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +45 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +38 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +35 -1
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +39 -1
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +125 -0
- package/src/resources/extensions/gsd/tools/reopen-task.ts +129 -0
- package/src/resources/extensions/gsd/tools/replan-slice.ts +38 -1
- package/src/resources/extensions/gsd/types.ts +8 -0
- package/src/resources/extensions/gsd/unit-ownership.ts +104 -0
- package/src/resources/extensions/gsd/workflow-events.ts +154 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +51 -1
- package/src/resources/extensions/gsd/workflow-manifest.ts +334 -0
- package/src/resources/extensions/gsd/workflow-migration.ts +345 -0
- package/src/resources/extensions/gsd/workflow-projections.ts +425 -0
- package/src/resources/extensions/gsd/workflow-reconcile.ts +503 -0
- package/src/resources/extensions/gsd/write-intercept.ts +90 -0
- /package/dist/web/standalone/.next/static/{zWYDSwB-terOjfhmWzqk1 → ZIDqryyYDroh_8AnaAOSG}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{zWYDSwB-terOjfhmWzqk1 → ZIDqryyYDroh_8AnaAOSG}/_ssgManifest.js +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import assert from "node:assert/strict";
|
|
1
|
+
import { createTestContext } from './test-helpers.ts';
|
|
3
2
|
import * as fs from 'node:fs';
|
|
4
3
|
import * as path from 'node:path';
|
|
5
4
|
import * as os from 'node:os';
|
|
@@ -18,6 +17,8 @@ import {
|
|
|
18
17
|
} from '../gsd-db.ts';
|
|
19
18
|
import { handleCompleteTask } from '../tools/complete-task.ts';
|
|
20
19
|
|
|
20
|
+
const { assertEq, assertTrue, assertMatch, report } = createTestContext();
|
|
21
|
+
|
|
21
22
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
22
23
|
// Helpers
|
|
23
24
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -98,290 +99,356 @@ function makeValidParams() {
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
101
|
-
//
|
|
102
|
+
// complete-task: Schema v5 migration
|
|
102
103
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
console.log('\n=== complete-task: schema v5 migration ===');
|
|
106
|
+
{
|
|
107
|
+
const dbPath = tempDbPath();
|
|
108
|
+
openDatabase(dbPath);
|
|
108
109
|
|
|
109
|
-
|
|
110
|
+
const adapter = _getAdapter()!;
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
// Verify schema version is current (v11 after state machine migration)
|
|
113
|
+
const versionRow = adapter.prepare('SELECT MAX(version) as v FROM schema_version').get();
|
|
114
|
+
assertEq(versionRow?.['v'], 11, 'schema version should be 11');
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
116
|
+
// Verify all 4 new tables exist
|
|
117
|
+
const tables = adapter.prepare(
|
|
118
|
+
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
|
|
119
|
+
).all();
|
|
120
|
+
const tableNames = tables.map(t => t['name'] as string);
|
|
121
|
+
assertTrue(tableNames.includes('milestones'), 'milestones table should exist');
|
|
122
|
+
assertTrue(tableNames.includes('slices'), 'slices table should exist');
|
|
123
|
+
assertTrue(tableNames.includes('tasks'), 'tasks table should exist');
|
|
124
|
+
assertTrue(tableNames.includes('verification_evidence'), 'verification_evidence table should exist');
|
|
124
125
|
|
|
125
|
-
|
|
126
|
+
cleanup(dbPath);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
130
|
+
// complete-task: Accessor CRUD
|
|
131
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
132
|
+
|
|
133
|
+
console.log('\n=== complete-task: accessor CRUD ===');
|
|
134
|
+
{
|
|
135
|
+
const dbPath = tempDbPath();
|
|
136
|
+
openDatabase(dbPath);
|
|
137
|
+
|
|
138
|
+
// Insert milestone
|
|
139
|
+
insertMilestone({ id: 'M001', title: 'Test Milestone' });
|
|
140
|
+
const adapter = _getAdapter()!;
|
|
141
|
+
const mRow = adapter.prepare("SELECT * FROM milestones WHERE id = 'M001'").get();
|
|
142
|
+
assertEq(mRow?.['id'], 'M001', 'milestone id should be M001');
|
|
143
|
+
assertEq(mRow?.['title'], 'Test Milestone', 'milestone title should match');
|
|
144
|
+
|
|
145
|
+
// Insert slice
|
|
146
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice', risk: 'high' });
|
|
147
|
+
const sRow = adapter.prepare("SELECT * FROM slices WHERE id = 'S01' AND milestone_id = 'M001'").get();
|
|
148
|
+
assertEq(sRow?.['id'], 'S01', 'slice id should be S01');
|
|
149
|
+
assertEq(sRow?.['risk'], 'high', 'slice risk should be high');
|
|
150
|
+
|
|
151
|
+
// Insert task with all fields
|
|
152
|
+
insertTask({
|
|
153
|
+
id: 'T01',
|
|
154
|
+
sliceId: 'S01',
|
|
155
|
+
milestoneId: 'M001',
|
|
156
|
+
title: 'Test Task',
|
|
157
|
+
status: 'complete',
|
|
158
|
+
oneLiner: 'Did the thing',
|
|
159
|
+
narrative: 'Full story here.',
|
|
160
|
+
verificationResult: 'passed',
|
|
161
|
+
duration: '30m',
|
|
162
|
+
blockerDiscovered: false,
|
|
163
|
+
deviations: 'None',
|
|
164
|
+
knownIssues: 'None',
|
|
165
|
+
keyFiles: ['file1.ts', 'file2.ts'],
|
|
166
|
+
keyDecisions: ['D001'],
|
|
167
|
+
fullSummaryMd: '# Summary',
|
|
126
168
|
});
|
|
127
|
-
});
|
|
128
169
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
170
|
+
// getTask verifies all fields
|
|
171
|
+
const task = getTask('M001', 'S01', 'T01');
|
|
172
|
+
assertTrue(task !== null, 'task should not be null');
|
|
173
|
+
assertEq(task!.id, 'T01', 'task id');
|
|
174
|
+
assertEq(task!.slice_id, 'S01', 'task slice_id');
|
|
175
|
+
assertEq(task!.milestone_id, 'M001', 'task milestone_id');
|
|
176
|
+
assertEq(task!.title, 'Test Task', 'task title');
|
|
177
|
+
assertEq(task!.status, 'complete', 'task status');
|
|
178
|
+
assertEq(task!.one_liner, 'Did the thing', 'task one_liner');
|
|
179
|
+
assertEq(task!.narrative, 'Full story here.', 'task narrative');
|
|
180
|
+
assertEq(task!.verification_result, 'passed', 'task verification_result');
|
|
181
|
+
assertEq(task!.blocker_discovered, false, 'task blocker_discovered');
|
|
182
|
+
assertEq(task!.key_files, ['file1.ts', 'file2.ts'], 'task key_files JSON round-trip');
|
|
183
|
+
assertEq(task!.key_decisions, ['D001'], 'task key_decisions JSON round-trip');
|
|
184
|
+
assertEq(task!.full_summary_md, '# Summary', 'task full_summary_md');
|
|
185
|
+
|
|
186
|
+
// getTask returns null for non-existent
|
|
187
|
+
const noTask = getTask('M001', 'S01', 'T99');
|
|
188
|
+
assertEq(noTask, null, 'non-existent task should return null');
|
|
189
|
+
|
|
190
|
+
// Insert verification evidence
|
|
191
|
+
insertVerificationEvidence({
|
|
192
|
+
taskId: 'T01',
|
|
193
|
+
sliceId: 'S01',
|
|
194
|
+
milestoneId: 'M001',
|
|
195
|
+
command: 'npm test',
|
|
196
|
+
exitCode: 0,
|
|
197
|
+
verdict: '✅ pass',
|
|
198
|
+
durationMs: 3000,
|
|
199
|
+
});
|
|
200
|
+
const evRows = adapter.prepare(
|
|
201
|
+
"SELECT * FROM verification_evidence WHERE task_id = 'T01' AND slice_id = 'S01' AND milestone_id = 'M001'"
|
|
202
|
+
).all();
|
|
203
|
+
assertEq(evRows.length, 1, 'should have 1 verification evidence row');
|
|
204
|
+
assertEq(evRows[0]['command'], 'npm test', 'evidence command');
|
|
205
|
+
assertEq(evRows[0]['exit_code'], 0, 'evidence exit_code');
|
|
206
|
+
assertEq(evRows[0]['verdict'], '✅ pass', 'evidence verdict');
|
|
207
|
+
assertEq(evRows[0]['duration_ms'], 3000, 'evidence duration_ms');
|
|
208
|
+
|
|
209
|
+
// getSliceTasks returns array
|
|
210
|
+
const sliceTasks = getSliceTasks('M001', 'S01');
|
|
211
|
+
assertEq(sliceTasks.length, 1, 'getSliceTasks should return 1 task');
|
|
212
|
+
assertEq(sliceTasks[0].id, 'T01', 'getSliceTasks first task id');
|
|
213
|
+
|
|
214
|
+
// updateTaskStatus changes status
|
|
215
|
+
updateTaskStatus('M001', 'S01', 'T01', 'failed', new Date().toISOString());
|
|
216
|
+
const updatedTask = getTask('M001', 'S01', 'T01');
|
|
217
|
+
assertEq(updatedTask!.status, 'failed', 'task status should be updated to failed');
|
|
218
|
+
assertTrue(updatedTask!.completed_at !== null, 'completed_at should be set after status update');
|
|
219
|
+
|
|
220
|
+
cleanup(dbPath);
|
|
221
|
+
}
|
|
133
222
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const mRow = adapter.prepare("SELECT * FROM milestones WHERE id = 'M001'").get();
|
|
138
|
-
assert.strictEqual(mRow?.['id'], 'M001', 'milestone id should be M001');
|
|
139
|
-
assert.strictEqual(mRow?.['title'], 'Test Milestone', 'milestone title should match');
|
|
140
|
-
|
|
141
|
-
// Insert slice
|
|
142
|
-
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice', risk: 'high' });
|
|
143
|
-
const sRow = adapter.prepare("SELECT * FROM slices WHERE id = 'S01' AND milestone_id = 'M001'").get();
|
|
144
|
-
assert.strictEqual(sRow?.['id'], 'S01', 'slice id should be S01');
|
|
145
|
-
assert.strictEqual(sRow?.['risk'], 'high', 'slice risk should be high');
|
|
146
|
-
|
|
147
|
-
// Insert task with all fields
|
|
148
|
-
insertTask({
|
|
149
|
-
id: 'T01',
|
|
150
|
-
sliceId: 'S01',
|
|
151
|
-
milestoneId: 'M001',
|
|
152
|
-
title: 'Test Task',
|
|
153
|
-
status: 'complete',
|
|
154
|
-
oneLiner: 'Did the thing',
|
|
155
|
-
narrative: 'Full story here.',
|
|
156
|
-
verificationResult: 'passed',
|
|
157
|
-
duration: '30m',
|
|
158
|
-
blockerDiscovered: false,
|
|
159
|
-
deviations: 'None',
|
|
160
|
-
knownIssues: 'None',
|
|
161
|
-
keyFiles: ['file1.ts', 'file2.ts'],
|
|
162
|
-
keyDecisions: ['D001'],
|
|
163
|
-
fullSummaryMd: '# Summary',
|
|
164
|
-
});
|
|
223
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
224
|
+
// complete-task: Accessor stale-state error
|
|
225
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
165
226
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
227
|
+
console.log('\n=== complete-task: accessor stale-state error ===');
|
|
228
|
+
{
|
|
229
|
+
// No DB open — accessors should throw GSD_STALE_STATE
|
|
230
|
+
closeDatabase();
|
|
231
|
+
let threw = false;
|
|
232
|
+
try {
|
|
233
|
+
insertMilestone({ id: 'M001' });
|
|
234
|
+
} catch (err: any) {
|
|
235
|
+
threw = true;
|
|
236
|
+
assertTrue(err.code === 'GSD_STALE_STATE' || err.message.includes('No database open'),
|
|
237
|
+
'should throw GSD_STALE_STATE when no DB open');
|
|
238
|
+
}
|
|
239
|
+
assertTrue(threw, 'insertMilestone should throw when no DB open');
|
|
240
|
+
|
|
241
|
+
threw = false;
|
|
242
|
+
try {
|
|
243
|
+
insertSlice({ id: 'S01', milestoneId: 'M001' });
|
|
244
|
+
} catch (err: any) {
|
|
245
|
+
threw = true;
|
|
246
|
+
assertTrue(err.code === 'GSD_STALE_STATE' || err.message.includes('No database open'),
|
|
247
|
+
'insertSlice should throw GSD_STALE_STATE');
|
|
248
|
+
}
|
|
249
|
+
assertTrue(threw, 'insertSlice should throw when no DB open');
|
|
250
|
+
|
|
251
|
+
threw = false;
|
|
252
|
+
try {
|
|
253
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001' });
|
|
254
|
+
} catch (err: any) {
|
|
255
|
+
threw = true;
|
|
256
|
+
assertTrue(err.code === 'GSD_STALE_STATE' || err.message.includes('No database open'),
|
|
257
|
+
'insertTask should throw GSD_STALE_STATE');
|
|
258
|
+
}
|
|
259
|
+
assertTrue(threw, 'insertTask should throw when no DB open');
|
|
260
|
+
|
|
261
|
+
threw = false;
|
|
262
|
+
try {
|
|
187
263
|
insertVerificationEvidence({
|
|
188
|
-
taskId: 'T01',
|
|
189
|
-
|
|
190
|
-
milestoneId: 'M001',
|
|
191
|
-
command: 'npm test',
|
|
192
|
-
exitCode: 0,
|
|
193
|
-
verdict: '✅ pass',
|
|
194
|
-
durationMs: 3000,
|
|
264
|
+
taskId: 'T01', sliceId: 'S01', milestoneId: 'M001',
|
|
265
|
+
command: 'test', exitCode: 0, verdict: 'pass', durationMs: 0,
|
|
195
266
|
});
|
|
267
|
+
} catch (err: any) {
|
|
268
|
+
threw = true;
|
|
269
|
+
assertTrue(err.code === 'GSD_STALE_STATE' || err.message.includes('No database open'),
|
|
270
|
+
'insertVerificationEvidence should throw GSD_STALE_STATE');
|
|
271
|
+
}
|
|
272
|
+
assertTrue(threw, 'insertVerificationEvidence should throw when no DB open');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
276
|
+
// complete-task: Handler happy path
|
|
277
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
278
|
+
|
|
279
|
+
console.log('\n=== complete-task: handler happy path ===');
|
|
280
|
+
{
|
|
281
|
+
const dbPath = tempDbPath();
|
|
282
|
+
openDatabase(dbPath);
|
|
283
|
+
|
|
284
|
+
const { basePath, planPath } = createTempProject();
|
|
285
|
+
|
|
286
|
+
// Seed milestone + slice + both tasks so projection renders T01 ([x]) and T02 ([ ])
|
|
287
|
+
insertMilestone({ id: 'M001', title: 'Test Milestone' });
|
|
288
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice' });
|
|
289
|
+
insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', status: 'pending', title: 'Second task' });
|
|
290
|
+
|
|
291
|
+
const params = makeValidParams();
|
|
292
|
+
const result = await handleCompleteTask(params, basePath);
|
|
293
|
+
|
|
294
|
+
assertTrue(!('error' in result), 'handler should succeed without error');
|
|
295
|
+
if (!('error' in result)) {
|
|
296
|
+
assertEq(result.taskId, 'T01', 'result taskId');
|
|
297
|
+
assertEq(result.sliceId, 'S01', 'result sliceId');
|
|
298
|
+
assertEq(result.milestoneId, 'M001', 'result milestoneId');
|
|
299
|
+
assertTrue(result.summaryPath.endsWith('T01-SUMMARY.md'), 'summaryPath should end with T01-SUMMARY.md');
|
|
300
|
+
|
|
301
|
+
// (a) Verify task row in DB with status 'complete'
|
|
302
|
+
const task = getTask('M001', 'S01', 'T01');
|
|
303
|
+
assertTrue(task !== null, 'task should exist in DB after handler');
|
|
304
|
+
assertEq(task!.status, 'complete', 'task status should be complete');
|
|
305
|
+
assertEq(task!.one_liner, 'Added test functionality', 'task one_liner in DB');
|
|
306
|
+
assertEq(task!.key_files, ['src/test.ts', 'src/test.test.ts'], 'task key_files in DB');
|
|
307
|
+
|
|
308
|
+
// (b) Verify verification_evidence rows in DB
|
|
309
|
+
const adapter = _getAdapter()!;
|
|
196
310
|
const evRows = adapter.prepare(
|
|
197
|
-
"SELECT * FROM verification_evidence WHERE task_id = 'T01' AND
|
|
311
|
+
"SELECT * FROM verification_evidence WHERE task_id = 'T01' AND milestone_id = 'M001'"
|
|
198
312
|
).all();
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
313
|
+
assertEq(evRows.length, 1, 'should have 1 verification evidence row after handler');
|
|
314
|
+
assertEq(evRows[0]['command'], 'npm run test:unit', 'evidence command from handler');
|
|
315
|
+
|
|
316
|
+
// (c) Verify T01-SUMMARY.md file on disk with correct YAML frontmatter
|
|
317
|
+
assertTrue(fs.existsSync(result.summaryPath), 'summary file should exist on disk');
|
|
318
|
+
const summaryContent = fs.readFileSync(result.summaryPath, 'utf-8');
|
|
319
|
+
assertMatch(summaryContent, /^---\n/, 'summary should start with YAML frontmatter');
|
|
320
|
+
assertMatch(summaryContent, /id: T01/, 'summary should contain id: T01');
|
|
321
|
+
assertMatch(summaryContent, /parent: S01/, 'summary should contain parent: S01');
|
|
322
|
+
assertMatch(summaryContent, /milestone: M001/, 'summary should contain milestone: M001');
|
|
323
|
+
assertMatch(summaryContent, /blocker_discovered: false/, 'summary should contain blocker_discovered');
|
|
324
|
+
assertMatch(summaryContent, /# T01:/, 'summary should have H1 with task ID');
|
|
325
|
+
assertMatch(summaryContent, /\*\*Added test functionality\*\*/, 'summary should have one-liner in bold');
|
|
326
|
+
assertMatch(summaryContent, /## What Happened/, 'summary should have What Happened section');
|
|
327
|
+
assertMatch(summaryContent, /## Verification Evidence/, 'summary should have Verification Evidence section');
|
|
328
|
+
assertMatch(summaryContent, /npm run test:unit/, 'summary evidence should contain command');
|
|
329
|
+
|
|
330
|
+
// (d) Verify plan checkbox changed to [x]
|
|
331
|
+
const planContent = fs.readFileSync(planPath, 'utf-8');
|
|
332
|
+
assertMatch(planContent, /\[x\]\s+\*\*T01:/, 'T01 should be checked in plan');
|
|
333
|
+
// T02 should still be unchecked
|
|
334
|
+
assertMatch(planContent, /\[ \]\s+\*\*T02:/, 'T02 should still be unchecked in plan');
|
|
335
|
+
|
|
336
|
+
// (e) Verify full_summary_md stored in DB for D004 recovery
|
|
337
|
+
const taskAfter = getTask('M001', 'S01', 'T01');
|
|
338
|
+
assertTrue(taskAfter!.full_summary_md.length > 0, 'full_summary_md should be non-empty in DB');
|
|
339
|
+
assertMatch(taskAfter!.full_summary_md, /id: T01/, 'full_summary_md should contain frontmatter');
|
|
340
|
+
}
|
|
219
341
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
342
|
+
cleanupDir(basePath);
|
|
343
|
+
cleanup(dbPath);
|
|
344
|
+
}
|
|
223
345
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
346
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
347
|
+
// complete-task: Handler validation errors
|
|
348
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
227
349
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
350
|
+
console.log('\n=== complete-task: handler validation errors ===');
|
|
351
|
+
{
|
|
352
|
+
const dbPath = tempDbPath();
|
|
353
|
+
openDatabase(dbPath);
|
|
231
354
|
|
|
232
|
-
|
|
233
|
-
(err: any) => err.code === 'GSD_STALE_STATE' || err.message.includes('No database open'),
|
|
234
|
-
'insertTask should throw when no DB open');
|
|
355
|
+
const params = makeValidParams();
|
|
235
356
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
describe("complete-task: handler", () => {
|
|
246
|
-
test("happy path", async () => {
|
|
247
|
-
const dbPath = tempDbPath();
|
|
248
|
-
openDatabase(dbPath);
|
|
249
|
-
|
|
250
|
-
const { basePath, planPath } = createTempProject();
|
|
251
|
-
|
|
252
|
-
const params = makeValidParams();
|
|
253
|
-
const result = await handleCompleteTask(params, basePath);
|
|
254
|
-
|
|
255
|
-
assert.ok(!('error' in result), 'handler should succeed without error');
|
|
256
|
-
if (!('error' in result)) {
|
|
257
|
-
assert.strictEqual(result.taskId, 'T01', 'result taskId');
|
|
258
|
-
assert.strictEqual(result.sliceId, 'S01', 'result sliceId');
|
|
259
|
-
assert.strictEqual(result.milestoneId, 'M001', 'result milestoneId');
|
|
260
|
-
assert.ok(result.summaryPath.endsWith('T01-SUMMARY.md'), 'summaryPath should end with T01-SUMMARY.md');
|
|
261
|
-
|
|
262
|
-
// (a) Verify task row in DB with status 'complete'
|
|
263
|
-
const task = getTask('M001', 'S01', 'T01');
|
|
264
|
-
assert.ok(task !== null, 'task should exist in DB after handler');
|
|
265
|
-
assert.strictEqual(task!.status, 'complete', 'task status should be complete');
|
|
266
|
-
assert.strictEqual(task!.one_liner, 'Added test functionality', 'task one_liner in DB');
|
|
267
|
-
assert.deepStrictEqual(task!.key_files, ['src/test.ts', 'src/test.test.ts'], 'task key_files in DB');
|
|
268
|
-
|
|
269
|
-
// (b) Verify verification_evidence rows in DB
|
|
270
|
-
const adapter = _getAdapter()!;
|
|
271
|
-
const evRows = adapter.prepare(
|
|
272
|
-
"SELECT * FROM verification_evidence WHERE task_id = 'T01' AND milestone_id = 'M001'"
|
|
273
|
-
).all();
|
|
274
|
-
assert.strictEqual(evRows.length, 1, 'should have 1 verification evidence row after handler');
|
|
275
|
-
assert.strictEqual(evRows[0]['command'], 'npm run test:unit', 'evidence command from handler');
|
|
276
|
-
|
|
277
|
-
// (c) Verify T01-SUMMARY.md file on disk with correct YAML frontmatter
|
|
278
|
-
assert.ok(fs.existsSync(result.summaryPath), 'summary file should exist on disk');
|
|
279
|
-
const summaryContent = fs.readFileSync(result.summaryPath, 'utf-8');
|
|
280
|
-
assert.match(summaryContent, /^---\n/, 'summary should start with YAML frontmatter');
|
|
281
|
-
assert.match(summaryContent, /id: T01/, 'summary should contain id: T01');
|
|
282
|
-
assert.match(summaryContent, /parent: S01/, 'summary should contain parent: S01');
|
|
283
|
-
assert.match(summaryContent, /milestone: M001/, 'summary should contain milestone: M001');
|
|
284
|
-
assert.match(summaryContent, /blocker_discovered: false/, 'summary should contain blocker_discovered');
|
|
285
|
-
assert.match(summaryContent, /# T01:/, 'summary should have H1 with task ID');
|
|
286
|
-
assert.match(summaryContent, /\*\*Added test functionality\*\*/, 'summary should have one-liner in bold');
|
|
287
|
-
assert.match(summaryContent, /## What Happened/, 'summary should have What Happened section');
|
|
288
|
-
assert.match(summaryContent, /## Verification Evidence/, 'summary should have Verification Evidence section');
|
|
289
|
-
assert.match(summaryContent, /npm run test:unit/, 'summary evidence should contain command');
|
|
290
|
-
|
|
291
|
-
// (d) Verify plan checkbox changed to [x]
|
|
292
|
-
const planContent = fs.readFileSync(planPath, 'utf-8');
|
|
293
|
-
assert.match(planContent, /\[x\]\s+\*\*T01:/, 'T01 should be checked in plan');
|
|
294
|
-
// T02 should still be unchecked
|
|
295
|
-
assert.match(planContent, /\[ \]\s+\*\*T02:/, 'T02 should still be unchecked in plan');
|
|
296
|
-
|
|
297
|
-
// (e) Verify full_summary_md stored in DB for D004 recovery
|
|
298
|
-
const taskAfter = getTask('M001', 'S01', 'T01');
|
|
299
|
-
assert.ok(taskAfter!.full_summary_md.length > 0, 'full_summary_md should be non-empty in DB');
|
|
300
|
-
assert.match(taskAfter!.full_summary_md, /id: T01/, 'full_summary_md should contain frontmatter');
|
|
301
|
-
}
|
|
357
|
+
// Empty taskId
|
|
358
|
+
const r1 = await handleCompleteTask({ ...params, taskId: '' }, '/tmp/fake');
|
|
359
|
+
assertTrue('error' in r1, 'should return error for empty taskId');
|
|
360
|
+
if ('error' in r1) {
|
|
361
|
+
assertMatch(r1.error, /taskId/, 'error should mention taskId');
|
|
362
|
+
}
|
|
302
363
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
364
|
+
// Empty milestoneId
|
|
365
|
+
const r2 = await handleCompleteTask({ ...params, milestoneId: '' }, '/tmp/fake');
|
|
366
|
+
assertTrue('error' in r2, 'should return error for empty milestoneId');
|
|
367
|
+
if ('error' in r2) {
|
|
368
|
+
assertMatch(r2.error, /milestoneId/, 'error should mention milestoneId');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Empty sliceId
|
|
372
|
+
const r3 = await handleCompleteTask({ ...params, sliceId: '' }, '/tmp/fake');
|
|
373
|
+
assertTrue('error' in r3, 'should return error for empty sliceId');
|
|
374
|
+
if ('error' in r3) {
|
|
375
|
+
assertMatch(r3.error, /sliceId/, 'error should mention sliceId');
|
|
376
|
+
}
|
|
306
377
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
openDatabase(dbPath);
|
|
378
|
+
cleanup(dbPath);
|
|
379
|
+
}
|
|
310
380
|
|
|
311
|
-
|
|
381
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
382
|
+
// complete-task: Handler idempotency
|
|
383
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
312
384
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
assert.match(r1.error, /taskId/, 'error should mention taskId');
|
|
318
|
-
}
|
|
385
|
+
console.log('\n=== complete-task: handler idempotency ===');
|
|
386
|
+
{
|
|
387
|
+
const dbPath = tempDbPath();
|
|
388
|
+
openDatabase(dbPath);
|
|
319
389
|
|
|
320
|
-
|
|
321
|
-
const r2 = await handleCompleteTask({ ...params, milestoneId: '' }, '/tmp/fake');
|
|
322
|
-
assert.ok('error' in r2, 'should return error for empty milestoneId');
|
|
323
|
-
if ('error' in r2) {
|
|
324
|
-
assert.match(r2.error, /milestoneId/, 'error should mention milestoneId');
|
|
325
|
-
}
|
|
390
|
+
const { basePath, planPath } = createTempProject();
|
|
326
391
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if ('error' in r3) {
|
|
331
|
-
assert.match(r3.error, /sliceId/, 'error should mention sliceId');
|
|
332
|
-
}
|
|
392
|
+
// Seed milestone + slice so state machine guards pass
|
|
393
|
+
insertMilestone({ id: 'M001', title: 'Test Milestone' });
|
|
394
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice' });
|
|
333
395
|
|
|
334
|
-
|
|
335
|
-
});
|
|
396
|
+
const params = makeValidParams();
|
|
336
397
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
398
|
+
// First call should succeed
|
|
399
|
+
const r1 = await handleCompleteTask(params, basePath);
|
|
400
|
+
assertTrue(!('error' in r1), 'first call should succeed');
|
|
340
401
|
|
|
341
|
-
|
|
402
|
+
// Verify only 1 task row
|
|
403
|
+
const tasks = getSliceTasks('M001', 'S01');
|
|
404
|
+
assertEq(tasks.length, 1, 'should have exactly 1 task row after first call');
|
|
342
405
|
|
|
343
|
-
|
|
406
|
+
// Second call with same params — state machine guard rejects (task is already complete)
|
|
407
|
+
const r2 = await handleCompleteTask(params, basePath);
|
|
408
|
+
assertTrue('error' in r2, 'second call should return error (task already complete)');
|
|
409
|
+
if ('error' in r2) {
|
|
410
|
+
assertMatch(r2.error, /already complete/, 'error should mention already complete');
|
|
411
|
+
}
|
|
344
412
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
413
|
+
// Still only 1 task row (no duplication from rejected second call)
|
|
414
|
+
const tasksAfter = getSliceTasks('M001', 'S01');
|
|
415
|
+
assertEq(tasksAfter.length, 1, 'should still have exactly 1 task row after rejected second call');
|
|
348
416
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
417
|
+
cleanupDir(basePath);
|
|
418
|
+
cleanup(dbPath);
|
|
419
|
+
}
|
|
352
420
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
421
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
422
|
+
// complete-task: Handler with missing plan file (graceful)
|
|
423
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
356
424
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
425
|
+
console.log('\n=== complete-task: handler with missing plan file ===');
|
|
426
|
+
{
|
|
427
|
+
const dbPath = tempDbPath();
|
|
428
|
+
openDatabase(dbPath);
|
|
361
429
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
430
|
+
// Create a temp dir WITHOUT a plan file
|
|
431
|
+
const basePath = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-no-plan-'));
|
|
432
|
+
const tasksDir = path.join(basePath, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'tasks');
|
|
433
|
+
fs.mkdirSync(tasksDir, { recursive: true });
|
|
434
|
+
|
|
435
|
+
// Seed milestone + slice so state machine guards pass
|
|
436
|
+
insertMilestone({ id: 'M001', title: 'Test Milestone' });
|
|
437
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice' });
|
|
365
438
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
openDatabase(dbPath);
|
|
439
|
+
const params = makeValidParams();
|
|
440
|
+
const result = await handleCompleteTask(params, basePath);
|
|
369
441
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
fs.
|
|
442
|
+
// Should succeed even without plan file — just skip checkbox toggle
|
|
443
|
+
assertTrue(!('error' in result), 'handler should succeed without plan file');
|
|
444
|
+
if (!('error' in result)) {
|
|
445
|
+
assertTrue(fs.existsSync(result.summaryPath), 'summary should be written even without plan file');
|
|
446
|
+
}
|
|
374
447
|
|
|
375
|
-
|
|
376
|
-
|
|
448
|
+
cleanupDir(basePath);
|
|
449
|
+
cleanup(dbPath);
|
|
450
|
+
}
|
|
377
451
|
|
|
378
|
-
|
|
379
|
-
assert.ok(!('error' in result), 'handler should succeed without plan file');
|
|
380
|
-
if (!('error' in result)) {
|
|
381
|
-
assert.ok(fs.existsSync(result.summaryPath), 'summary should be written even without plan file');
|
|
382
|
-
}
|
|
452
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
383
453
|
|
|
384
|
-
|
|
385
|
-
cleanup(dbPath);
|
|
386
|
-
});
|
|
387
|
-
});
|
|
454
|
+
report();
|