@opengsd/gsd-pi 1.9.0-dev.d2070876 → 1.9.0-dev.e10b60d4
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/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +4 -24
- package/dist/resources/extensions/gsd/auto/run-unit.js +1 -19
- package/dist/resources/extensions/gsd/auto-start.js +9 -29
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +3 -7
- package/dist/resources/extensions/gsd/auto.js +1 -12
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +2 -21
- package/dist/resources/extensions/gsd/flat-phase-migration.js +0 -12
- package/dist/resources/extensions/gsd/gsd-db.js +31 -75
- package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -20
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.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 +12 -12
- package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/README.md +3 -9
- package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cli.js +15 -121
- package/packages/gsd-cloud/dist/cli.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.js +1 -7
- package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts +3 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.js +7 -9
- package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
- package/packages/gsd-cloud/package.json +2 -2
- package/packages/mcp-server/dist/cli-runner.d.ts +0 -3
- package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
- package/packages/mcp-server/dist/cli-runner.js +33 -119
- package/packages/mcp-server/dist/cli-runner.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +0 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +0 -17
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts +0 -5
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +0 -54
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop.ts +4 -48
- package/src/resources/extensions/gsd/auto/run-unit.ts +1 -20
- package/src/resources/extensions/gsd/auto-start.ts +8 -31
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +3 -10
- package/src/resources/extensions/gsd/auto.ts +1 -13
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -27
- package/src/resources/extensions/gsd/flat-phase-migration.ts +0 -14
- package/src/resources/extensions/gsd/gsd-db.ts +33 -85
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +0 -156
- package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +1 -21
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +1 -74
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +0 -83
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +9 -8
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +0 -32
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +0 -13
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +0 -60
- package/src/resources/extensions/gsd/worktree-state-projection.ts +24 -21
- package/packages/gsd-cloud/dist/background-cli.test.d.ts +0 -2
- package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +0 -1
- package/packages/gsd-cloud/dist/background-cli.test.js +0 -248
- package/packages/gsd-cloud/dist/background-cli.test.js.map +0 -1
- package/packages/gsd-cloud/dist/runtime-process.d.ts +0 -27
- package/packages/gsd-cloud/dist/runtime-process.d.ts.map +0 -1
- package/packages/gsd-cloud/dist/runtime-process.js +0 -272
- package/packages/gsd-cloud/dist/runtime-process.js.map +0 -1
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts +0 -2
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +0 -1
- package/packages/gsd-cloud/dist/runtime-process.test.js +0 -97
- package/packages/gsd-cloud/dist/runtime-process.test.js.map +0 -1
- /package/dist/web/standalone/.next/static/{sggX-h41OjePo-FjdArP5 → V9MAVyYVPboQUB9KTTAMi}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{sggX-h41OjePo-FjdArP5 → V9MAVyYVPboQUB9KTTAMi}/_ssgManifest.js +0 -0
|
@@ -155,19 +155,6 @@ function isInsideFlatProjectionBackup(backupDir: string, src: string): boolean {
|
|
|
155
155
|
return src === phaseBackupDir || src.startsWith(`${phaseBackupDir}/`) || src.startsWith(`${phaseBackupDir}\\`);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
function removeFlatProjectionBackup(backupDir: string): void {
|
|
159
|
-
const phaseBackupDir = join(backupDir, "__phases");
|
|
160
|
-
if (!existsSync(phaseBackupDir)) return;
|
|
161
|
-
try {
|
|
162
|
-
removePathWithRetries(phaseBackupDir);
|
|
163
|
-
} catch (err) {
|
|
164
|
-
logWarning(
|
|
165
|
-
"migration",
|
|
166
|
-
`flat-phase migration succeeded but could not remove temporary ${LAYOUT_SEGMENTS.level1}/ backup: ${(err as Error).message}`,
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
158
|
function restoreFlatProjectionFromBackup(basePath: string, backupDir: string): void {
|
|
172
159
|
const phaseBackupDir = join(backupDir, "__phases");
|
|
173
160
|
if (!existsSync(phaseBackupDir)) return;
|
|
@@ -507,5 +494,4 @@ export async function migrateToFlatPhase(basePath: string): Promise<void> {
|
|
|
507
494
|
`flat-phase migration succeeded but could not remove ${LEGACY_MIGRATING_SEGMENT}: ${(err as Error).message}`,
|
|
508
495
|
);
|
|
509
496
|
}
|
|
510
|
-
removeFlatProjectionBackup(backupDir);
|
|
511
497
|
}
|
|
@@ -67,13 +67,6 @@ export type { SliceRow, TaskRow } from "./db-task-slice-rows.js";
|
|
|
67
67
|
|
|
68
68
|
import { TERMINAL_STATUS_SQL } from "./db/sql-constants.js";
|
|
69
69
|
import { applyStatusTransition } from "./db/writers/status.js";
|
|
70
|
-
import {
|
|
71
|
-
LAYOUT_SEGMENTS,
|
|
72
|
-
derivePhaseSlug,
|
|
73
|
-
milestoneIdToPhaseNum,
|
|
74
|
-
milestoneIdUniqueSuffix,
|
|
75
|
-
phaseDirName,
|
|
76
|
-
} from "./layout-policy.js";
|
|
77
70
|
|
|
78
71
|
export function insertDecision(d: Omit<Decision, "seq">): void {
|
|
79
72
|
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
@@ -213,47 +206,6 @@ export function insertArtifact(a: {
|
|
|
213
206
|
});
|
|
214
207
|
}
|
|
215
208
|
|
|
216
|
-
function canonicalPhaseDirNameForDb(milestoneId: string, title: string): string {
|
|
217
|
-
const phaseNum = milestoneIdToPhaseNum(milestoneId);
|
|
218
|
-
const slug = derivePhaseSlug(title || milestoneId);
|
|
219
|
-
const suffix = milestoneIdUniqueSuffix(milestoneId);
|
|
220
|
-
return phaseDirName(phaseNum, suffix ? `${suffix}-${slug}` : slug);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function reconcileMilestonePhaseArtifactPaths(milestoneId: string, title: string): void {
|
|
224
|
-
const db = getDbOrNull()!;
|
|
225
|
-
const phaseNumPrefix = String(milestoneIdToPhaseNum(milestoneId)).padStart(2, "0");
|
|
226
|
-
const canonicalDir = canonicalPhaseDirNameForDb(milestoneId, title);
|
|
227
|
-
const staleRows = db.prepare(
|
|
228
|
-
`SELECT path
|
|
229
|
-
FROM artifacts
|
|
230
|
-
WHERE milestone_id = :milestone_id
|
|
231
|
-
AND path LIKE :phase_prefix`,
|
|
232
|
-
).all({
|
|
233
|
-
":milestone_id": milestoneId,
|
|
234
|
-
":phase_prefix": `${LAYOUT_SEGMENTS.level1}/${phaseNumPrefix}-%/%`,
|
|
235
|
-
}) as Array<{ path: string }>;
|
|
236
|
-
|
|
237
|
-
const updatePath = db.prepare("UPDATE artifacts SET path = :new_path WHERE path = :old_path");
|
|
238
|
-
const deletePath = db.prepare("DELETE FROM artifacts WHERE path = :path");
|
|
239
|
-
const existingPath = db.prepare("SELECT 1 AS present FROM artifacts WHERE path = :path");
|
|
240
|
-
|
|
241
|
-
for (const row of staleRows) {
|
|
242
|
-
const parts = row.path.split("/");
|
|
243
|
-
if (parts.length < 3) continue;
|
|
244
|
-
if (parts[0] !== LAYOUT_SEGMENTS.level1) continue;
|
|
245
|
-
if (parts[1] === canonicalDir) continue;
|
|
246
|
-
if (!parts[1]?.startsWith(`${phaseNumPrefix}-`)) continue;
|
|
247
|
-
|
|
248
|
-
const newPath = [LAYOUT_SEGMENTS.level1, canonicalDir, ...parts.slice(2)].join("/");
|
|
249
|
-
if (existingPath.get({ ":path": newPath })) {
|
|
250
|
-
deletePath.run({ ":path": row.path });
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
updatePath.run({ ":new_path": newPath, ":old_path": row.path });
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
209
|
export interface MilestonePlanningRecord {
|
|
258
210
|
vision: string;
|
|
259
211
|
successCriteria: string[];
|
|
@@ -335,43 +287,39 @@ export function insertMilestone(m: {
|
|
|
335
287
|
|
|
336
288
|
export function upsertMilestonePlanning(milestoneId: string, planning: Partial<MilestonePlanningRecord> & { title?: string; status?: string; depends_on?: string[] }): void {
|
|
337
289
|
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
":boundary_map_markdown": planning.boundaryMapMarkdown ?? null,
|
|
372
|
-
});
|
|
373
|
-
const finalTitle = planning.title?.trim();
|
|
374
|
-
if (finalTitle) reconcileMilestonePhaseArtifactPaths(milestoneId, finalTitle);
|
|
290
|
+
getDbOrNull()!.prepare(
|
|
291
|
+
`UPDATE milestones SET
|
|
292
|
+
title = COALESCE(NULLIF(:title, ''), title),
|
|
293
|
+
status = COALESCE(NULLIF(:status, ''), status),
|
|
294
|
+
depends_on = COALESCE(:depends_on, depends_on),
|
|
295
|
+
vision = COALESCE(:vision, vision),
|
|
296
|
+
success_criteria = COALESCE(:success_criteria, success_criteria),
|
|
297
|
+
key_risks = COALESCE(:key_risks, key_risks),
|
|
298
|
+
proof_strategy = COALESCE(:proof_strategy, proof_strategy),
|
|
299
|
+
verification_contract = COALESCE(:verification_contract, verification_contract),
|
|
300
|
+
verification_integration = COALESCE(:verification_integration, verification_integration),
|
|
301
|
+
verification_operational = COALESCE(:verification_operational, verification_operational),
|
|
302
|
+
verification_uat = COALESCE(:verification_uat, verification_uat),
|
|
303
|
+
definition_of_done = COALESCE(:definition_of_done, definition_of_done),
|
|
304
|
+
requirement_coverage = COALESCE(:requirement_coverage, requirement_coverage),
|
|
305
|
+
boundary_map_markdown = COALESCE(:boundary_map_markdown, boundary_map_markdown)
|
|
306
|
+
WHERE id = :id`,
|
|
307
|
+
).run({
|
|
308
|
+
":id": milestoneId,
|
|
309
|
+
":title": planning.title ?? "",
|
|
310
|
+
":status": planning.status ?? "",
|
|
311
|
+
":depends_on": planning.depends_on ? JSON.stringify(planning.depends_on) : null,
|
|
312
|
+
":vision": planning.vision ?? null,
|
|
313
|
+
":success_criteria": planning.successCriteria ? JSON.stringify(planning.successCriteria) : null,
|
|
314
|
+
":key_risks": planning.keyRisks ? JSON.stringify(planning.keyRisks) : null,
|
|
315
|
+
":proof_strategy": planning.proofStrategy ? JSON.stringify(planning.proofStrategy) : null,
|
|
316
|
+
":verification_contract": planning.verificationContract ?? null,
|
|
317
|
+
":verification_integration": planning.verificationIntegration ?? null,
|
|
318
|
+
":verification_operational": planning.verificationOperational ?? null,
|
|
319
|
+
":verification_uat": planning.verificationUat ?? null,
|
|
320
|
+
":definition_of_done": planning.definitionOfDone ? JSON.stringify(planning.definitionOfDone) : null,
|
|
321
|
+
":requirement_coverage": planning.requirementCoverage ?? null,
|
|
322
|
+
":boundary_map_markdown": planning.boundaryMapMarkdown ?? null,
|
|
375
323
|
});
|
|
376
324
|
}
|
|
377
325
|
|
|
@@ -41,7 +41,6 @@ import { claimMilestoneLease } from "../db/milestone-leases.js";
|
|
|
41
41
|
import { recordDispatchClaim, markCanceled } from "../db/unit-dispatches.js";
|
|
42
42
|
import { setRuntimeKv, getRuntimeKv } from "../db/runtime-kv.js";
|
|
43
43
|
import { SourceObservationStore } from "../source-observations.js";
|
|
44
|
-
import { autoCommitCurrentBranch } from "../worktree.js";
|
|
45
44
|
|
|
46
45
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
47
46
|
|
|
@@ -833,37 +832,6 @@ test("runUnit returns cancelled when session creation fails", async () => {
|
|
|
833
832
|
assert.equal(pi.calls.length, 0);
|
|
834
833
|
});
|
|
835
834
|
|
|
836
|
-
test("runUnit returns cancelled when command context lacks newSession", async () => {
|
|
837
|
-
_resetPendingResolve();
|
|
838
|
-
|
|
839
|
-
const ctx = {
|
|
840
|
-
...makeMockCtx(),
|
|
841
|
-
ui: {
|
|
842
|
-
notify: () => {},
|
|
843
|
-
setStatus: () => {},
|
|
844
|
-
setWorkingMessage: () => {},
|
|
845
|
-
},
|
|
846
|
-
sessionManager: {
|
|
847
|
-
getEntries: () => [],
|
|
848
|
-
},
|
|
849
|
-
modelRegistry: {
|
|
850
|
-
getProviderAuthMode: () => undefined,
|
|
851
|
-
isProviderRequestReady: () => true,
|
|
852
|
-
},
|
|
853
|
-
} as any;
|
|
854
|
-
const pi = makeMockPi();
|
|
855
|
-
const s = makeMockSession();
|
|
856
|
-
s.cmdCtx = {} as any;
|
|
857
|
-
|
|
858
|
-
const result = await runUnit(ctx, pi, s, "task", "T01", "prompt");
|
|
859
|
-
|
|
860
|
-
assert.equal(result.status, "cancelled");
|
|
861
|
-
assert.equal(result.errorContext?.category, "session-failed");
|
|
862
|
-
assert.equal(result.errorContext?.isTransient, false);
|
|
863
|
-
assert.match(result.errorContext?.message ?? "", /missing newSession/);
|
|
864
|
-
assert.equal(pi.calls.length, 0);
|
|
865
|
-
});
|
|
866
|
-
|
|
867
835
|
test("runUnit: TypeError from newSession is classified as structural (isTransient: false)", async () => {
|
|
868
836
|
// Regression for #572: a TypeError thrown from newSession (e.g. "something is
|
|
869
837
|
// not a function") indicates a programming error, not a transient provider
|
|
@@ -1614,130 +1582,6 @@ test("autoLoop exits when s.active is set to false", async (t) => {
|
|
|
1614
1582
|
);
|
|
1615
1583
|
});
|
|
1616
1584
|
|
|
1617
|
-
test("autoLoop stops before dispatch when command context lacks newSession", async () => {
|
|
1618
|
-
_resetPendingResolve();
|
|
1619
|
-
|
|
1620
|
-
const ctx = makeMockCtx();
|
|
1621
|
-
ctx.ui.setStatus = () => {};
|
|
1622
|
-
const pi = makeMockPi();
|
|
1623
|
-
const s = makeLoopSession({
|
|
1624
|
-
cmdCtx: {
|
|
1625
|
-
getContextUsage: () => ({ percent: 10, tokens: 1000, limit: 10000 }),
|
|
1626
|
-
},
|
|
1627
|
-
});
|
|
1628
|
-
let stopReason: string | undefined;
|
|
1629
|
-
let preserveWorktree: boolean | undefined;
|
|
1630
|
-
let deriveCalled = false;
|
|
1631
|
-
|
|
1632
|
-
try {
|
|
1633
|
-
const deps = makeMockDeps({
|
|
1634
|
-
stopAuto: async (_ctx, _pi, reason, options) => {
|
|
1635
|
-
stopReason = reason;
|
|
1636
|
-
preserveWorktree = options?.preserveWorktree;
|
|
1637
|
-
s.active = false;
|
|
1638
|
-
},
|
|
1639
|
-
deriveState: async () => {
|
|
1640
|
-
deriveCalled = true;
|
|
1641
|
-
throw new Error("deriveState should not run without command session support");
|
|
1642
|
-
},
|
|
1643
|
-
});
|
|
1644
|
-
|
|
1645
|
-
await autoLoop(ctx, pi, s, deps);
|
|
1646
|
-
|
|
1647
|
-
assert.equal(stopReason, "Auto-mode has no command context for dispatch.");
|
|
1648
|
-
assert.equal(preserveWorktree, true);
|
|
1649
|
-
assert.equal(deriveCalled, false);
|
|
1650
|
-
assert.equal(pi.calls.length, 0);
|
|
1651
|
-
} finally {
|
|
1652
|
-
rmSync(s.basePath, { recursive: true, force: true });
|
|
1653
|
-
}
|
|
1654
|
-
});
|
|
1655
|
-
|
|
1656
|
-
test("autoLoop commits open unit work when command context lacks newSession", async () => {
|
|
1657
|
-
_resetPendingResolve();
|
|
1658
|
-
|
|
1659
|
-
const ctx = makeMockCtx();
|
|
1660
|
-
ctx.ui.setStatus = () => {};
|
|
1661
|
-
const pi = makeMockPi();
|
|
1662
|
-
const s = makeLoopSession({
|
|
1663
|
-
cmdCtx: {
|
|
1664
|
-
getContextUsage: () => ({ percent: 10, tokens: 1000, limit: 10000 }),
|
|
1665
|
-
},
|
|
1666
|
-
currentUnit: { type: "execute-task", id: "T01", startedAt: Date.now() },
|
|
1667
|
-
});
|
|
1668
|
-
const outputPath = join(s.basePath, "executor-output.txt");
|
|
1669
|
-
let autoCommitArgs: { unitType: string; unitId: string } | undefined;
|
|
1670
|
-
let preserveWorktree: boolean | undefined;
|
|
1671
|
-
|
|
1672
|
-
try {
|
|
1673
|
-
const deps = makeMockDeps({
|
|
1674
|
-
autoCommitUnit: async (basePath, unitType, unitId) => {
|
|
1675
|
-
autoCommitArgs = { unitType, unitId };
|
|
1676
|
-
return autoCommitCurrentBranch(basePath, unitType, unitId);
|
|
1677
|
-
},
|
|
1678
|
-
stopAuto: async (_ctx, _pi, _reason, options) => {
|
|
1679
|
-
preserveWorktree = options?.preserveWorktree;
|
|
1680
|
-
s.active = false;
|
|
1681
|
-
},
|
|
1682
|
-
deriveState: async () => {
|
|
1683
|
-
throw new Error("deriveState should not run without command session support");
|
|
1684
|
-
},
|
|
1685
|
-
});
|
|
1686
|
-
|
|
1687
|
-
writeFileSync(outputPath, "open unit work before stop\n", "utf-8");
|
|
1688
|
-
|
|
1689
|
-
await autoLoop(ctx, pi, s, deps);
|
|
1690
|
-
|
|
1691
|
-
assert.deepEqual(autoCommitArgs, { unitType: "execute-task", unitId: "T01" });
|
|
1692
|
-
assert.equal(preserveWorktree, true);
|
|
1693
|
-
const committed = execSync("git show HEAD:executor-output.txt", {
|
|
1694
|
-
cwd: s.basePath,
|
|
1695
|
-
encoding: "utf-8",
|
|
1696
|
-
});
|
|
1697
|
-
assert.equal(committed, "open unit work before stop\n");
|
|
1698
|
-
assert.equal(pi.calls.length, 0);
|
|
1699
|
-
} finally {
|
|
1700
|
-
rmSync(s.basePath, { recursive: true, force: true });
|
|
1701
|
-
}
|
|
1702
|
-
});
|
|
1703
|
-
|
|
1704
|
-
test("autoLoop snapshots dirty work when unit dispatch crashes", async () => {
|
|
1705
|
-
_resetPendingResolve();
|
|
1706
|
-
|
|
1707
|
-
const ctx = makeMockCtx();
|
|
1708
|
-
ctx.ui.setStatus = () => {};
|
|
1709
|
-
const pi = makeMockPi();
|
|
1710
|
-
const s = makeLoopSession();
|
|
1711
|
-
const outputPath = join(s.basePath, "executor-output.txt");
|
|
1712
|
-
let autoCommitCalls = 0;
|
|
1713
|
-
|
|
1714
|
-
try {
|
|
1715
|
-
const deps = makeMockDeps({
|
|
1716
|
-
ensurePreconditions: () => {
|
|
1717
|
-
writeFileSync(outputPath, "saved before crash\n", "utf-8");
|
|
1718
|
-
throw new Error("dispatch crash after file write");
|
|
1719
|
-
},
|
|
1720
|
-
autoCommitUnit: async (basePath, unitType, unitId) => {
|
|
1721
|
-
autoCommitCalls++;
|
|
1722
|
-
const commitMsg = autoCommitCurrentBranch(basePath, unitType, unitId);
|
|
1723
|
-
s.active = false;
|
|
1724
|
-
return commitMsg;
|
|
1725
|
-
},
|
|
1726
|
-
});
|
|
1727
|
-
|
|
1728
|
-
await autoLoop(ctx, pi, s, deps);
|
|
1729
|
-
|
|
1730
|
-
assert.equal(autoCommitCalls, 1);
|
|
1731
|
-
const committed = execSync("git show HEAD:executor-output.txt", {
|
|
1732
|
-
cwd: s.basePath,
|
|
1733
|
-
encoding: "utf-8",
|
|
1734
|
-
});
|
|
1735
|
-
assert.equal(committed, "saved before crash\n");
|
|
1736
|
-
} finally {
|
|
1737
|
-
rmSync(s.basePath, { recursive: true, force: true });
|
|
1738
|
-
}
|
|
1739
|
-
});
|
|
1740
|
-
|
|
1741
1585
|
test("autoLoop pauses visibly when Auto Orchestration Module is not wired", async () => {
|
|
1742
1586
|
_resetPendingResolve();
|
|
1743
1587
|
|
|
@@ -4,7 +4,7 @@ import { existsSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
|
|
7
|
-
import { hasGitIndexLockForTest
|
|
7
|
+
import { hasGitIndexLockForTest } from "../auto-start.ts";
|
|
8
8
|
|
|
9
9
|
test("bootstrapAutoSession detects .git/index.lock without deleting it", (t) => {
|
|
10
10
|
const base = mkdtempSync(join(tmpdir(), "gsd-index-lock-"));
|
|
@@ -24,23 +24,3 @@ test("bootstrapAutoSession reports no index lock when the lock file is absent",
|
|
|
24
24
|
|
|
25
25
|
assert.equal(hasGitIndexLockForTest(base), false);
|
|
26
26
|
});
|
|
27
|
-
|
|
28
|
-
test("bootstrapAutoSession removes empty legacy milestones dir in flat-phase projects", (t) => {
|
|
29
|
-
const base = mkdtempSync(join(tmpdir(), "gsd-flat-bootstrap-clean-"));
|
|
30
|
-
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
31
|
-
mkdirSync(join(base, ".gsd", "phases"), { recursive: true });
|
|
32
|
-
mkdirSync(join(base, ".gsd", "milestones"), { recursive: true });
|
|
33
|
-
|
|
34
|
-
assert.equal(reconcileFlatPhaseBootstrapLayout(base), true);
|
|
35
|
-
assert.equal(existsSync(join(base, ".gsd", "phases")), true);
|
|
36
|
-
assert.equal(existsSync(join(base, ".gsd", "milestones")), false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("bootstrapAutoSession creates phases instead of milestones for empty GSD state", (t) => {
|
|
40
|
-
const base = mkdtempSync(join(tmpdir(), "gsd-flat-bootstrap-init-"));
|
|
41
|
-
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
42
|
-
|
|
43
|
-
assert.equal(reconcileFlatPhaseBootstrapLayout(base), true);
|
|
44
|
-
assert.equal(existsSync(join(base, ".gsd", "phases")), true);
|
|
45
|
-
assert.equal(existsSync(join(base, ".gsd", "milestones")), false);
|
|
46
|
-
});
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { describe, test } from "node:test";
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
|
-
import { mkdtempSync, mkdirSync, realpathSync, rmSync
|
|
7
|
-
import { execSync } from "node:child_process";
|
|
6
|
+
import { mkdtempSync, mkdirSync, realpathSync, rmSync } from "node:fs";
|
|
8
7
|
import { join } from "node:path";
|
|
9
8
|
import { tmpdir } from "node:os";
|
|
10
9
|
|
|
@@ -97,78 +96,6 @@ describe("hook dispatch session workspace root", () => {
|
|
|
97
96
|
assert.equal(dispatched, true);
|
|
98
97
|
assert.deepEqual(newSessionOptions, { workspaceRoot: basePath });
|
|
99
98
|
});
|
|
100
|
-
|
|
101
|
-
test("dispatchHookUnit commits in-flight work when command context lacks newSession", async (t) => {
|
|
102
|
-
const originalCwd = process.cwd();
|
|
103
|
-
const basePath = realpathSync(mkdtempSync(join(tmpdir(), "gsd-hook-nosnap-")));
|
|
104
|
-
mkdirSync(join(basePath, ".gsd"), { recursive: true });
|
|
105
|
-
execSync("git init --initial-branch=main", { cwd: basePath, stdio: "ignore" });
|
|
106
|
-
execSync("git config user.email test@test.com", { cwd: basePath, stdio: "ignore" });
|
|
107
|
-
execSync("git config user.name Test", { cwd: basePath, stdio: "ignore" });
|
|
108
|
-
execSync("git commit --allow-empty -m init", { cwd: basePath, stdio: "ignore" });
|
|
109
|
-
|
|
110
|
-
autoSession.reset();
|
|
111
|
-
autoSession.active = true;
|
|
112
|
-
autoSession.basePath = basePath;
|
|
113
|
-
autoSession.originalBasePath = basePath;
|
|
114
|
-
// Simulate an in-flight unit whose executor already wrote files, then a hook
|
|
115
|
-
// fires with a command context missing newSession.
|
|
116
|
-
autoSession.currentUnit = {
|
|
117
|
-
type: "execute-task",
|
|
118
|
-
id: "M001/S01/T01",
|
|
119
|
-
startedAt: Date.now(),
|
|
120
|
-
};
|
|
121
|
-
t.after(() => {
|
|
122
|
-
try {
|
|
123
|
-
process.chdir(originalCwd);
|
|
124
|
-
} catch {
|
|
125
|
-
// best effort cleanup after cwd-sensitive dispatch tests
|
|
126
|
-
}
|
|
127
|
-
autoSession.reset();
|
|
128
|
-
rmSync(basePath, { recursive: true, force: true });
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
const outputPath = join(basePath, "executor-output.txt");
|
|
132
|
-
writeFileSync(outputPath, "in-flight executor work\n", "utf-8");
|
|
133
|
-
|
|
134
|
-
// Command context intentionally lacks newSession — the guard must snapshot
|
|
135
|
-
// the dirty work before stopping instead of discarding it.
|
|
136
|
-
const ctx = {
|
|
137
|
-
ui: {
|
|
138
|
-
notify: () => {},
|
|
139
|
-
setStatus: () => {},
|
|
140
|
-
setWidget: () => {},
|
|
141
|
-
},
|
|
142
|
-
modelRegistry: {
|
|
143
|
-
getAvailable: () => [],
|
|
144
|
-
},
|
|
145
|
-
sessionManager: {
|
|
146
|
-
getSessionFile: () => join(basePath, "session.jsonl"),
|
|
147
|
-
},
|
|
148
|
-
};
|
|
149
|
-
const pi = {
|
|
150
|
-
sendMessage: () => {},
|
|
151
|
-
setModel: async () => true,
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const dispatched = await dispatchHookUnit(
|
|
155
|
-
ctx as any,
|
|
156
|
-
pi as any,
|
|
157
|
-
"review",
|
|
158
|
-
"execute-task",
|
|
159
|
-
"M001/S01/T01",
|
|
160
|
-
"review the completed unit",
|
|
161
|
-
undefined,
|
|
162
|
-
basePath,
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
assert.equal(dispatched, false);
|
|
166
|
-
const committed = execSync("git show HEAD:executor-output.txt", {
|
|
167
|
-
cwd: basePath,
|
|
168
|
-
encoding: "utf-8",
|
|
169
|
-
});
|
|
170
|
-
assert.equal(committed, "in-flight executor work\n");
|
|
171
|
-
});
|
|
172
99
|
});
|
|
173
100
|
|
|
174
101
|
describe("deep setup approval questions pause immediately", () => {
|
|
@@ -167,45 +167,6 @@ test("checkEngineHealth keeps PLAN checkbox divergence after stale projection fl
|
|
|
167
167
|
assert.match(readFileSync(plan.planPath, "utf-8"), /- \[ \] \*\*T01\*\*:/);
|
|
168
168
|
});
|
|
169
169
|
|
|
170
|
-
test("checkEngineHealth ignores stale suffixed flat-phase duplicate when bare milestone exists", async (t) => {
|
|
171
|
-
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-checkbox-flat-duplicate-"));
|
|
172
|
-
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
173
|
-
|
|
174
|
-
const gsdDir = join(base, ".gsd");
|
|
175
|
-
mkdirSync(gsdDir, { recursive: true });
|
|
176
|
-
|
|
177
|
-
openDatabase(join(gsdDir, "gsd.db"));
|
|
178
|
-
insertMilestone({ id: "M003", title: "M003-vaz73w: New milestone M003-vaz73w", status: "active" });
|
|
179
|
-
insertSlice({ id: "S01", milestoneId: "M003", title: "Slice", status: "pending", risk: "low", depends: [], sequence: 1 });
|
|
180
|
-
for (const taskId of ["T01", "T02", "T03"]) {
|
|
181
|
-
insertTask({ id: taskId, milestoneId: "M003", sliceId: "S01", title: taskId, status: "complete", sequence: Number(taskId.slice(1)) });
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
insertMilestone({ id: "M003-vaz73w", title: "New milestone M003-vaz73w", status: "active" });
|
|
185
|
-
insertSlice({ id: "S01", milestoneId: "M003-vaz73w", title: "Slice", status: "pending", risk: "low", depends: [], sequence: 1 });
|
|
186
|
-
for (const taskId of ["T01", "T02", "T03"]) {
|
|
187
|
-
insertTask({ id: taskId, milestoneId: "M003-vaz73w", sliceId: "S01", title: taskId, status: "complete", sequence: Number(taskId.slice(1)) });
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const canonicalPlan = await renderPlanFromDb(base, "M003", "S01");
|
|
191
|
-
const staleDir = join(gsdDir, "phases", "03-vaz73w-new-milestone-m003-vaz73w");
|
|
192
|
-
mkdirSync(staleDir, { recursive: true });
|
|
193
|
-
writeFileSync(
|
|
194
|
-
join(staleDir, "03-01-PLAN.md"),
|
|
195
|
-
canonicalPlan.content.replace("- [x] **T03**:", "- [ ] **T03**:"),
|
|
196
|
-
"utf-8",
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
const issues: any[] = [];
|
|
200
|
-
await checkEngineHealth(base, issues, []);
|
|
201
|
-
|
|
202
|
-
assert.deepEqual(
|
|
203
|
-
issues.filter((issue) => issue.code === "checkbox_db_status_divergence").map((issue) => issue.unitId),
|
|
204
|
-
[],
|
|
205
|
-
"stale suffixed duplicate phase projection must not compete with the bare milestone projection",
|
|
206
|
-
);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
170
|
test("checkEngineHealth reads task checkboxes from the <tasks> block, not a stray line above it", async (t) => {
|
|
210
171
|
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-task-section-"));
|
|
211
172
|
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
@@ -467,50 +428,6 @@ test("checkEngineHealth repair prunes stale phases artifact rows with present mi
|
|
|
467
428
|
assert.deepEqual(rows.map((row) => row.path), []);
|
|
468
429
|
});
|
|
469
430
|
|
|
470
|
-
test("checkEngineHealth repair prunes stale phases artifact rows with renamed flat-phase files", async (t) => {
|
|
471
|
-
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-prune-renamed-flat-phase-artifact-"));
|
|
472
|
-
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
473
|
-
|
|
474
|
-
const gsdDir = join(base, ".gsd");
|
|
475
|
-
const stalePath = "phases/01-new-milestone-m001/01-01-PLAN.md";
|
|
476
|
-
const replacementPath = "phases/01-lokably-brand-foundation-and-welcome-pag/01-01-PLAN.md";
|
|
477
|
-
mkdirSync(join(gsdDir, "phases", "01-lokably-brand-foundation-and-welcome-pag"), { recursive: true });
|
|
478
|
-
writeFileSync(join(gsdDir, replacementPath), "# Plan\n", "utf-8");
|
|
479
|
-
|
|
480
|
-
openDatabase(join(gsdDir, "gsd.db"));
|
|
481
|
-
insertArtifact({
|
|
482
|
-
path: stalePath,
|
|
483
|
-
artifact_type: "PLAN",
|
|
484
|
-
milestone_id: "M001",
|
|
485
|
-
slice_id: "S01",
|
|
486
|
-
task_id: null,
|
|
487
|
-
full_content: "# stale plan\n",
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
const beforeIssues: any[] = [];
|
|
491
|
-
await checkEngineHealth(base, beforeIssues, []);
|
|
492
|
-
|
|
493
|
-
const beforeIssue = beforeIssues.find((issue) => issue.code === "artifact_file_missing" && issue.file === stalePath);
|
|
494
|
-
assert.ok(beforeIssue, "stale renamed phases row should be reported when repair is off");
|
|
495
|
-
assert.equal(beforeIssue.fixable, true, "renamed phases rows with a flat replacement should be fixable");
|
|
496
|
-
|
|
497
|
-
const repairIssues: any[] = [];
|
|
498
|
-
const fixes: string[] = [];
|
|
499
|
-
await checkEngineHealth(base, repairIssues, fixes, { repair: true });
|
|
500
|
-
|
|
501
|
-
assert.equal(
|
|
502
|
-
repairIssues.some((issue) => issue.code === "artifact_file_missing" && issue.file === stalePath),
|
|
503
|
-
false,
|
|
504
|
-
"repair should not report stale rows it pruned",
|
|
505
|
-
);
|
|
506
|
-
assert.ok(fixes.includes(`pruned stale flat-phase artifact row ${stalePath}`));
|
|
507
|
-
|
|
508
|
-
const rows = _getAdapter()!
|
|
509
|
-
.prepare("SELECT path FROM artifacts ORDER BY path")
|
|
510
|
-
.all() as Array<{ path: string }>;
|
|
511
|
-
assert.deepEqual(rows.map((row) => row.path), []);
|
|
512
|
-
});
|
|
513
|
-
|
|
514
431
|
test("checkEngineHealth repair keeps phases rows whose own file is still present", async (t) => {
|
|
515
432
|
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-keep-present-phase-artifact-"));
|
|
516
433
|
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
@@ -122,7 +122,7 @@ test("migrateToFlatPhase creates a backup", async () => {
|
|
|
122
122
|
assert.ok(backups.length >= 1, "at least one migrate-* backup dir should exist");
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
test("migrateToFlatPhase
|
|
125
|
+
test("migrateToFlatPhase backs up existing phases projection before clearing it", async () => {
|
|
126
126
|
const base = makeTmp();
|
|
127
127
|
const reviewPath = join(base, ".gsd", "phases", "01-foundation", "PLAN-REVIEW.md");
|
|
128
128
|
mkdirSync(join(base, ".gsd", "phases", "01-foundation"), { recursive: true });
|
|
@@ -133,10 +133,11 @@ test("migrateToFlatPhase removes disposable phases snapshot after successful mig
|
|
|
133
133
|
const backupRoot = join(base, ".gsd-backups");
|
|
134
134
|
const backups = readdirSync(backupRoot).filter(d => d.startsWith("migrate-"));
|
|
135
135
|
assert.equal(backups.length, 1, "one migrate backup should exist");
|
|
136
|
+
const backedUpReview = join(backupRoot, backups[0]!, "__phases", "01-foundation", "PLAN-REVIEW.md");
|
|
136
137
|
assert.equal(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
"
|
|
138
|
+
readFileSync(backedUpReview, "utf-8"),
|
|
139
|
+
"# Plan Review\n\nHand-authored review.",
|
|
140
|
+
"pre-existing phases/ files should have a recovery copy",
|
|
140
141
|
);
|
|
141
142
|
});
|
|
142
143
|
|
|
@@ -201,7 +202,7 @@ test("failed migration rolls back without leaking a .gsd-backups/migrate-* dir",
|
|
|
201
202
|
assert.equal(leaked.length, 0, "rollback must delete the migrate-* backup it created");
|
|
202
203
|
});
|
|
203
204
|
|
|
204
|
-
test("resumed migration
|
|
205
|
+
test("resumed migration stores phases backup in retained migrate snapshot", async () => {
|
|
205
206
|
const base = makeTmp();
|
|
206
207
|
const milestonesPath = join(base, ".gsd", "milestones");
|
|
207
208
|
const migratingPath = join(base, ".gsd", "milestones.migrating");
|
|
@@ -217,9 +218,9 @@ test("resumed migration removes disposable phases snapshot after success", async
|
|
|
217
218
|
const backups = readdirSync(backupRoot).filter((d) => d.startsWith("migrate-"));
|
|
218
219
|
assert.equal(backups.length, 1, "resumed migration should create one retained migrate backup");
|
|
219
220
|
assert.equal(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
"
|
|
221
|
+
readFileSync(join(backupRoot, backups[0]!, "__phases", "01-foundation", "PLAN-REVIEW.md"), "utf-8"),
|
|
222
|
+
"# Plan Review\n\nResume recovery copy.",
|
|
223
|
+
"resume should retain the phases snapshot under .gsd-backups",
|
|
223
224
|
);
|
|
224
225
|
assert.equal(
|
|
225
226
|
existsSync(join(migratingPath, "__phases")),
|
|
@@ -46,9 +46,6 @@ import {
|
|
|
46
46
|
tryCreateMemoriesFts,
|
|
47
47
|
_isLikelyWslDrvFsPathForTest,
|
|
48
48
|
_shouldAttemptVacuumRecoveryForTest,
|
|
49
|
-
insertArtifact,
|
|
50
|
-
getArtifact,
|
|
51
|
-
upsertMilestonePlanning,
|
|
52
49
|
} from '../gsd-db.ts';
|
|
53
50
|
import { MigrationBackupError } from '../db-migration-backup.ts';
|
|
54
51
|
import { _resetLogs, peekLogs, setStderrLoggingEnabled } from '../workflow-logger.ts';
|
|
@@ -360,35 +357,6 @@ describe('gsd-db', () => {
|
|
|
360
357
|
closeDatabase();
|
|
361
358
|
});
|
|
362
359
|
|
|
363
|
-
test("gsd-db: upsertMilestonePlanning rewrites placeholder phase artifact paths to final title slug", () => {
|
|
364
|
-
openDatabase(":memory:");
|
|
365
|
-
|
|
366
|
-
const stalePath = "phases/01-new-milestone-m001/01-ROADMAP.md";
|
|
367
|
-
const finalPath = "phases/01-lokably-brand-foundation-and-welcome-pag/01-ROADMAP.md";
|
|
368
|
-
insertMilestone({ id: "M001", title: "New milestone M001", status: "active" });
|
|
369
|
-
insertArtifact({
|
|
370
|
-
path: stalePath,
|
|
371
|
-
artifact_type: "ROADMAP",
|
|
372
|
-
milestone_id: "M001",
|
|
373
|
-
slice_id: null,
|
|
374
|
-
task_id: null,
|
|
375
|
-
full_content: "# Placeholder roadmap\n",
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
upsertMilestonePlanning("M001", {
|
|
379
|
-
title: "Lokably brand foundation and welcome page rebuild",
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
assert.equal(getArtifact(stalePath), null, "placeholder-derived artifact row should be removed");
|
|
383
|
-
assert.equal(
|
|
384
|
-
getArtifact(finalPath)?.full_content,
|
|
385
|
-
"# Placeholder roadmap\n",
|
|
386
|
-
"artifact row should move to the final title-derived phase path",
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
closeDatabase();
|
|
390
|
-
});
|
|
391
|
-
|
|
392
360
|
test('gsd-db: active_requirements view excludes superseded', () => {
|
|
393
361
|
openDatabase(':memory:');
|
|
394
362
|
|