gsd-pi 2.78.1-dev.b0759e59b → 2.78.1-dev.e9d88a536
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 +8 -5
- package/dist/headless-recover.d.ts +23 -0
- package/dist/headless-recover.js +93 -0
- package/dist/headless.js +9 -0
- package/dist/help-text.js +1 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/tools/intent.js +8 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +4 -56
- package/dist/resources/extensions/gsd/auto-post-unit.js +7 -27
- package/dist/resources/extensions/gsd/auto-start.js +1 -8
- package/dist/resources/extensions/gsd/auto-worktree.js +59 -176
- package/dist/resources/extensions/gsd/auto.js +24 -6
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -77
- package/dist/resources/extensions/gsd/commands-codebase.js +2 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +5 -5
- package/dist/resources/extensions/gsd/commands-logs.js +2 -2
- package/dist/resources/extensions/gsd/commands-scan.js +2 -2
- package/dist/resources/extensions/gsd/commands-ship.js +2 -2
- package/dist/resources/extensions/gsd/commands-workflow-templates.js +5 -5
- package/dist/resources/extensions/gsd/db-writer.js +16 -85
- package/dist/resources/extensions/gsd/dispatch-guard.js +6 -10
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +2 -2
- package/dist/resources/extensions/gsd/gsd-db.js +74 -8
- package/dist/resources/extensions/gsd/guided-flow.js +31 -8
- package/dist/resources/extensions/gsd/markdown-renderer.js +14 -51
- package/dist/resources/extensions/gsd/parallel-merge.js +14 -13
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +5 -2
- package/dist/resources/extensions/gsd/paths.js +35 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
- package/dist/resources/extensions/gsd/queue-order.js +6 -1
- package/dist/resources/extensions/gsd/rethink.js +2 -2
- package/dist/resources/extensions/gsd/state.js +91 -372
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +6 -5
- package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -12
- package/dist/resources/extensions/gsd/tools/complete-task.js +19 -31
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -5
- package/dist/resources/extensions/gsd/workflow-manifest.js +2 -1
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -21
- package/dist/resources/extensions/gsd/workflow-reconcile.js +3 -3
- package/dist/resources/extensions/gsd/worktree-command.js +4 -3
- 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/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +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/6336.js +1 -0
- package/dist/web/standalone/.next/server/chunks/6897.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/mcp-server/dist/workflow-tools.d.ts +6 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +56 -2
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/parse-workflow-args.test.ts +80 -0
- package/packages/mcp-server/src/workflow-tools.ts +61 -2
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/browser-tools/tools/intent.ts +13 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +4 -60
- package/src/resources/extensions/gsd/auto-post-unit.ts +7 -26
- package/src/resources/extensions/gsd/auto-start.ts +1 -8
- package/src/resources/extensions/gsd/auto-worktree.ts +61 -204
- package/src/resources/extensions/gsd/auto.ts +23 -6
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +9 -84
- package/src/resources/extensions/gsd/commands-codebase.ts +2 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +5 -5
- package/src/resources/extensions/gsd/commands-logs.ts +2 -2
- package/src/resources/extensions/gsd/commands-scan.ts +2 -2
- package/src/resources/extensions/gsd/commands-ship.ts +2 -2
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +5 -5
- package/src/resources/extensions/gsd/db-writer.ts +16 -83
- package/src/resources/extensions/gsd/dispatch-guard.ts +6 -11
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -2
- package/src/resources/extensions/gsd/gsd-db.ts +85 -8
- package/src/resources/extensions/gsd/guided-flow.ts +35 -8
- package/src/resources/extensions/gsd/markdown-renderer.ts +13 -64
- package/src/resources/extensions/gsd/parallel-merge.ts +14 -13
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +5 -2
- package/src/resources/extensions/gsd/paths.ts +55 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +6 -0
- package/src/resources/extensions/gsd/queue-order.ts +6 -1
- package/src/resources/extensions/gsd/rethink.ts +2 -2
- package/src/resources/extensions/gsd/state.ts +91 -389
- package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +21 -34
- package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +6 -7
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +8 -6
- package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +12 -27
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +18 -5
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +14 -16
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +10 -38
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +136 -56
- package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +119 -61
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +6 -20
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +4 -5
- package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +14 -15
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +11 -16
- package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +15 -36
- package/src/resources/extensions/gsd/tests/handler-worktree-write-isolation.test.ts +57 -0
- package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +15 -15
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +15 -5
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +14 -8
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +25 -16
- package/src/resources/extensions/gsd/tests/projection-regression.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +184 -0
- package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +4 -0
- package/src/resources/extensions/gsd/tests/rogue-file-detection.test.ts +3 -4
- package/src/resources/extensions/gsd/tests/slice-disk-reconcile.test.ts +10 -56
- package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +15 -16
- package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +23 -27
- package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +13 -14
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +10 -33
- package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +12 -7
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +24 -1
- package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +65 -71
- package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +26 -151
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -5
- package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -14
- package/src/resources/extensions/gsd/tools/complete-task.ts +19 -34
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +7 -5
- package/src/resources/extensions/gsd/workflow-manifest.ts +4 -1
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -18
- package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -3
- package/src/resources/extensions/gsd/worktree-command.ts +4 -3
- package/dist/web/standalone/.next/server/chunks/8527.js +0 -1
- /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → oZGTPvJBQX_IDKKnuV8Bt}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{rk1EN3FQTE6Z1yalkW_GE → oZGTPvJBQX_IDKKnuV8Bt}/_ssgManifest.js +0 -0
|
@@ -181,7 +181,7 @@ function openRawDb(path: string): unknown {
|
|
|
181
181
|
return new Database(path);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
export const SCHEMA_VERSION =
|
|
184
|
+
export const SCHEMA_VERSION = 23;
|
|
185
185
|
|
|
186
186
|
function indexExists(db: DbAdapter, name: string): boolean {
|
|
187
187
|
return !!db.prepare(
|
|
@@ -354,7 +354,8 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
|
|
|
354
354
|
verification_uat TEXT NOT NULL DEFAULT '',
|
|
355
355
|
definition_of_done TEXT NOT NULL DEFAULT '[]',
|
|
356
356
|
requirement_coverage TEXT NOT NULL DEFAULT '',
|
|
357
|
-
boundary_map_markdown TEXT NOT NULL DEFAULT ''
|
|
357
|
+
boundary_map_markdown TEXT NOT NULL DEFAULT '',
|
|
358
|
+
sequence INTEGER DEFAULT 0
|
|
358
359
|
)
|
|
359
360
|
`);
|
|
360
361
|
|
|
@@ -1233,6 +1234,17 @@ function migrateSchema(db: DbAdapter): void {
|
|
|
1233
1234
|
});
|
|
1234
1235
|
}
|
|
1235
1236
|
|
|
1237
|
+
if (currentVersion < 23) {
|
|
1238
|
+
// v23: milestone queue ordering moves into the canonical DB. The
|
|
1239
|
+
// historical QUEUE-ORDER.json file remains a projection, but runtime
|
|
1240
|
+
// derivation must not read it as authoritative state.
|
|
1241
|
+
ensureColumn(db, "milestones", "sequence", "ALTER TABLE milestones ADD COLUMN sequence INTEGER DEFAULT 0");
|
|
1242
|
+
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1243
|
+
":version": 23,
|
|
1244
|
+
":applied_at": new Date().toISOString(),
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1236
1248
|
db.exec("COMMIT");
|
|
1237
1249
|
} catch (err) {
|
|
1238
1250
|
db.exec("ROLLBACK");
|
|
@@ -1602,6 +1614,34 @@ export function getActiveRequirements(): Requirement[] {
|
|
|
1602
1614
|
}));
|
|
1603
1615
|
}
|
|
1604
1616
|
|
|
1617
|
+
export function getRequirementCounts(): {
|
|
1618
|
+
active: number;
|
|
1619
|
+
validated: number;
|
|
1620
|
+
deferred: number;
|
|
1621
|
+
outOfScope: number;
|
|
1622
|
+
blocked: number;
|
|
1623
|
+
total: number;
|
|
1624
|
+
} {
|
|
1625
|
+
if (!currentDb) {
|
|
1626
|
+
return { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 };
|
|
1627
|
+
}
|
|
1628
|
+
const rows = currentDb
|
|
1629
|
+
.prepare("SELECT lower(status) as status, COUNT(*) as count FROM requirements GROUP BY lower(status)")
|
|
1630
|
+
.all();
|
|
1631
|
+
const counts = { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 };
|
|
1632
|
+
for (const row of rows) {
|
|
1633
|
+
const status = String(row["status"] ?? "");
|
|
1634
|
+
const count = Number(row["count"] ?? 0);
|
|
1635
|
+
counts.total += count;
|
|
1636
|
+
if (status === "active") counts.active += count;
|
|
1637
|
+
else if (status === "validated") counts.validated += count;
|
|
1638
|
+
else if (status === "deferred") counts.deferred += count;
|
|
1639
|
+
else if (status === "out-of-scope" || status === "out_of_scope") counts.outOfScope += count;
|
|
1640
|
+
else if (status === "blocked") counts.blocked += count;
|
|
1641
|
+
}
|
|
1642
|
+
return counts;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1605
1645
|
export function getDbOwnerPid(): number {
|
|
1606
1646
|
return currentPid;
|
|
1607
1647
|
}
|
|
@@ -2469,6 +2509,7 @@ export interface MilestoneRow {
|
|
|
2469
2509
|
definition_of_done: string[];
|
|
2470
2510
|
requirement_coverage: string;
|
|
2471
2511
|
boundary_map_markdown: string;
|
|
2512
|
+
sequence: number;
|
|
2472
2513
|
}
|
|
2473
2514
|
|
|
2474
2515
|
function rowToMilestone(row: Record<string, unknown>): MilestoneRow {
|
|
@@ -2490,6 +2531,7 @@ function rowToMilestone(row: Record<string, unknown>): MilestoneRow {
|
|
|
2490
2531
|
definition_of_done: JSON.parse((row["definition_of_done"] as string) || "[]"),
|
|
2491
2532
|
requirement_coverage: (row["requirement_coverage"] as string) ?? "",
|
|
2492
2533
|
boundary_map_markdown: (row["boundary_map_markdown"] as string) ?? "",
|
|
2534
|
+
sequence: Number(row["sequence"] ?? 0),
|
|
2493
2535
|
};
|
|
2494
2536
|
}
|
|
2495
2537
|
|
|
@@ -2517,7 +2559,9 @@ function rowToArtifact(row: Record<string, unknown>): ArtifactRow {
|
|
|
2517
2559
|
|
|
2518
2560
|
export function getAllMilestones(): MilestoneRow[] {
|
|
2519
2561
|
if (!currentDb) return [];
|
|
2520
|
-
const rows = currentDb.prepare(
|
|
2562
|
+
const rows = currentDb.prepare(
|
|
2563
|
+
"SELECT * FROM milestones ORDER BY CASE WHEN sequence > 0 THEN 0 ELSE 1 END, sequence, id",
|
|
2564
|
+
).all();
|
|
2521
2565
|
return rows.map(rowToMilestone);
|
|
2522
2566
|
}
|
|
2523
2567
|
|
|
@@ -2528,6 +2572,22 @@ export function getMilestone(id: string): MilestoneRow | null {
|
|
|
2528
2572
|
return rowToMilestone(row);
|
|
2529
2573
|
}
|
|
2530
2574
|
|
|
2575
|
+
export function setMilestoneQueueOrder(order: string[]): void {
|
|
2576
|
+
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2577
|
+
currentDb.exec("BEGIN IMMEDIATE");
|
|
2578
|
+
try {
|
|
2579
|
+
currentDb.prepare("UPDATE milestones SET sequence = 0").run();
|
|
2580
|
+
const stmt = currentDb.prepare("UPDATE milestones SET sequence = :sequence WHERE id = :id");
|
|
2581
|
+
order.forEach((id, index) => {
|
|
2582
|
+
stmt.run({ ":id": id, ":sequence": index + 1 });
|
|
2583
|
+
});
|
|
2584
|
+
currentDb.exec("COMMIT");
|
|
2585
|
+
} catch (err) {
|
|
2586
|
+
currentDb.exec("ROLLBACK");
|
|
2587
|
+
throw err;
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2531
2591
|
/**
|
|
2532
2592
|
* Update a milestone's status in the database.
|
|
2533
2593
|
* Used by park/unpark to keep the DB in sync with the filesystem marker.
|
|
@@ -2723,6 +2783,8 @@ export function reconcileWorktreeDb(
|
|
|
2723
2783
|
// fall through to the main DB's existing value (not a literal default)
|
|
2724
2784
|
// so reconcile never silently clears state the main tree has recorded.
|
|
2725
2785
|
const hasDecisionSource = wtInfo.some((col) => col["name"] === "source");
|
|
2786
|
+
const wtMilestoneInfo = adapter.prepare("PRAGMA wt.table_info('milestones')").all();
|
|
2787
|
+
const hasMilestoneSequence = wtMilestoneInfo.some((col) => col["name"] === "sequence");
|
|
2726
2788
|
const wtSliceInfo = adapter.prepare("PRAGMA wt.table_info('slices')").all();
|
|
2727
2789
|
const hasIsSketch = wtSliceInfo.some((col) => col["name"] === "is_sketch");
|
|
2728
2790
|
const hasSketchScope = wtSliceInfo.some((col) => col["name"] === "sketch_scope");
|
|
@@ -2796,7 +2858,7 @@ export function reconcileWorktreeDb(
|
|
|
2796
2858
|
id, title, status, depends_on, created_at, completed_at,
|
|
2797
2859
|
vision, success_criteria, key_risks, proof_strategy,
|
|
2798
2860
|
verification_contract, verification_integration, verification_operational, verification_uat,
|
|
2799
|
-
definition_of_done, requirement_coverage, boundary_map_markdown
|
|
2861
|
+
definition_of_done, requirement_coverage, boundary_map_markdown, sequence
|
|
2800
2862
|
)
|
|
2801
2863
|
SELECT w.id, w.title,
|
|
2802
2864
|
CASE
|
|
@@ -2814,7 +2876,8 @@ export function reconcileWorktreeDb(
|
|
|
2814
2876
|
END,
|
|
2815
2877
|
w.vision, w.success_criteria, w.key_risks, w.proof_strategy,
|
|
2816
2878
|
w.verification_contract, w.verification_integration, w.verification_operational, w.verification_uat,
|
|
2817
|
-
w.definition_of_done, w.requirement_coverage, w.boundary_map_markdown
|
|
2879
|
+
w.definition_of_done, w.requirement_coverage, w.boundary_map_markdown,
|
|
2880
|
+
${hasMilestoneSequence ? "COALESCE(w.sequence, 0)" : "COALESCE(m.sequence, 0)"}
|
|
2818
2881
|
FROM wt.milestones w
|
|
2819
2882
|
LEFT JOIN milestones m ON m.id = w.id
|
|
2820
2883
|
`).run());
|
|
@@ -3104,6 +3167,20 @@ export function getAssessment(path: string): Record<string, unknown> | null {
|
|
|
3104
3167
|
return row ?? null;
|
|
3105
3168
|
}
|
|
3106
3169
|
|
|
3170
|
+
export function getLatestAssessmentByScope(
|
|
3171
|
+
milestoneId: string,
|
|
3172
|
+
scope: string,
|
|
3173
|
+
): Record<string, unknown> | null {
|
|
3174
|
+
if (!currentDb) return null;
|
|
3175
|
+
const row = currentDb.prepare(
|
|
3176
|
+
`SELECT * FROM assessments
|
|
3177
|
+
WHERE milestone_id = :mid AND scope = :scope
|
|
3178
|
+
ORDER BY created_at DESC
|
|
3179
|
+
LIMIT 1`,
|
|
3180
|
+
).get({ ":mid": milestoneId, ":scope": scope });
|
|
3181
|
+
return row ?? null;
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3107
3184
|
// ─── Quality Gates ───────────────────────────────────────────────────────
|
|
3108
3185
|
|
|
3109
3186
|
function rowToGate(row: Record<string, unknown>): GateRow {
|
|
@@ -3591,8 +3668,8 @@ export function restoreManifest(manifest: StateManifest): void {
|
|
|
3591
3668
|
`INSERT INTO milestones (id, title, status, depends_on, created_at, completed_at,
|
|
3592
3669
|
vision, success_criteria, key_risks, proof_strategy,
|
|
3593
3670
|
verification_contract, verification_integration, verification_operational, verification_uat,
|
|
3594
|
-
definition_of_done, requirement_coverage, boundary_map_markdown)
|
|
3595
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3671
|
+
definition_of_done, requirement_coverage, boundary_map_markdown, sequence)
|
|
3672
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3596
3673
|
);
|
|
3597
3674
|
for (const m of manifest.milestones) {
|
|
3598
3675
|
msStmt.run(
|
|
@@ -3601,7 +3678,7 @@ export function restoreManifest(manifest: StateManifest): void {
|
|
|
3601
3678
|
m.vision, JSON.stringify(m.success_criteria), JSON.stringify(m.key_risks),
|
|
3602
3679
|
JSON.stringify(m.proof_strategy),
|
|
3603
3680
|
m.verification_contract, m.verification_integration, m.verification_operational, m.verification_uat,
|
|
3604
|
-
JSON.stringify(m.definition_of_done), m.requirement_coverage, m.boundary_map_markdown,
|
|
3681
|
+
JSON.stringify(m.definition_of_done), m.requirement_coverage, m.boundary_map_markdown, m.sequence ?? 0,
|
|
3605
3682
|
);
|
|
3606
3683
|
}
|
|
3607
3684
|
|
|
@@ -36,7 +36,7 @@ import { gsdHome } from "./gsd-home.js";
|
|
|
36
36
|
import {
|
|
37
37
|
gsdRoot, milestonesDir, resolveMilestoneFile, resolveMilestonePath,
|
|
38
38
|
resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile,
|
|
39
|
-
relMilestoneFile, relSliceFile,
|
|
39
|
+
relMilestoneFile, relSliceFile, clearPathCache,
|
|
40
40
|
} from "./paths.js";
|
|
41
41
|
import { join } from "node:path";
|
|
42
42
|
import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from "node:fs";
|
|
@@ -588,12 +588,39 @@ export function maybeHandleReadyPhraseWithoutFiles(event: { messages: any[] }):
|
|
|
588
588
|
const text = extractAssistantText(lastMsg);
|
|
589
589
|
if (!READY_PHRASE_RE.test(text)) return false;
|
|
590
590
|
|
|
591
|
+
// Bust paths.ts cached dir listings before checking for fresh writes. The
|
|
592
|
+
// LLM's Write tool calls do not invalidate paths.ts caches, so a stale
|
|
593
|
+
// listing taken before the milestone dir or its CONTEXT/ROADMAP files
|
|
594
|
+
// existed would falsely report the artifacts as missing and trigger the
|
|
595
|
+
// 3-strike "ready without files" abort even though the writes succeeded.
|
|
596
|
+
clearPathCache();
|
|
597
|
+
|
|
591
598
|
// Gate: artifacts must still be missing — if they exist, the happy path
|
|
592
599
|
// already fired and we have nothing to do.
|
|
593
600
|
const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
|
|
594
601
|
const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
595
602
|
if (contextFile || roadmapFile) return false;
|
|
596
603
|
|
|
604
|
+
// Diagnostic: when the cached resolver reports both files missing, also probe
|
|
605
|
+
// the canonical paths with uncached existsSync so we can tell whether the
|
|
606
|
+
// recovery is firing on real-missing files or a path-resolution miss
|
|
607
|
+
// (basePath/symlink mismatch, stale cache despite agent-end-recovery flush,
|
|
608
|
+
// legacy descriptor dir not matching, etc.).
|
|
609
|
+
try {
|
|
610
|
+
const mDir = resolveMilestonePath(basePath, milestoneId);
|
|
611
|
+
const canonicalCtx = mDir ? join(mDir, `${milestoneId}-CONTEXT.md`) : null;
|
|
612
|
+
const canonicalRoadmap = mDir ? join(mDir, `${milestoneId}-ROADMAP.md`) : null;
|
|
613
|
+
logWarning(
|
|
614
|
+
"guided",
|
|
615
|
+
`ready-phrase-reject diagnostic mid=${milestoneId} basePath=${basePath} ` +
|
|
616
|
+
`mDir=${mDir ?? "null"} ` +
|
|
617
|
+
`canonical-ctx=${canonicalCtx ?? "null"} ctx-exists=${canonicalCtx ? existsSync(canonicalCtx) : "n/a"} ` +
|
|
618
|
+
`canonical-roadmap=${canonicalRoadmap ?? "null"} roadmap-exists=${canonicalRoadmap ? existsSync(canonicalRoadmap) : "n/a"}`,
|
|
619
|
+
);
|
|
620
|
+
} catch (e) {
|
|
621
|
+
logWarning("guided", `ready-phrase-reject diagnostic failed: ${(e as Error).message}`);
|
|
622
|
+
}
|
|
623
|
+
|
|
597
624
|
entry.readyRejectCount = (entry.readyRejectCount ?? 0) + 1;
|
|
598
625
|
|
|
599
626
|
if (entry.readyRejectCount > MAX_READY_REJECTS) {
|
|
@@ -696,14 +723,14 @@ export function maybeHandleEmptyIntentTurn(
|
|
|
696
723
|
// path, handled by maybeHandleReadyPhraseWithoutFiles.
|
|
697
724
|
if (READY_PHRASE_RE.test(text)) return false;
|
|
698
725
|
|
|
699
|
-
// Skip if the LLM is clearly handing back to the user.
|
|
700
|
-
//
|
|
701
|
-
//
|
|
702
|
-
//
|
|
703
|
-
//
|
|
726
|
+
// Skip if the LLM is clearly handing back to the user. Discuss flows
|
|
727
|
+
// often pose a question and follow it with a conditional intent on the
|
|
728
|
+
// same line ("Did I capture that correctly? If so, I'll write the
|
|
729
|
+
// requirements."). A line-trailing `?` check misses these because the
|
|
730
|
+
// line ends in `.`. Match any sentence-terminating `?` (followed by
|
|
731
|
+
// whitespace or end-of-text) — false negatives here auto-reply to the
|
|
704
732
|
// user, which is a much worse failure mode than a missed nudge.
|
|
705
|
-
|
|
706
|
-
if (lines.some((l) => l.endsWith("?"))) return false;
|
|
733
|
+
if (/\?(?:\s|$)/.test(text)) return false;
|
|
707
734
|
|
|
708
735
|
// Must contain a commit-intent phrase — this is the stall we care about.
|
|
709
736
|
if (!COMMIT_INTENT_RE.test(text)) return false;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
// GSD Markdown Renderer — DB → Markdown file generation
|
|
2
2
|
//
|
|
3
3
|
// Transforms DB state into correct markdown files on disk.
|
|
4
|
-
// Each render function reads from DB
|
|
5
|
-
//
|
|
6
|
-
// stores updated content in the artifacts table, and invalidates caches.
|
|
4
|
+
// Each render function reads from DB, writes a markdown projection to disk,
|
|
5
|
+
// stores generated content in the artifacts table, and invalidates caches.
|
|
7
6
|
//
|
|
8
7
|
// Critical invariant: rendered markdown must round-trip through
|
|
9
8
|
// parseRoadmap(), parsePlan(), parseSummary() in files.ts.
|
|
@@ -85,58 +84,19 @@ function taskSummaryForSlicePlan(description: string): string {
|
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
/**
|
|
88
|
-
* Load artifact content from DB
|
|
89
|
-
*
|
|
90
|
-
*
|
|
87
|
+
* Load artifact content from the DB. Markdown projections are not authoritative
|
|
88
|
+
* during runtime; when the artifact row is missing, callers regenerate from DB
|
|
89
|
+
* rows instead of patching disk fallback content and storing it back.
|
|
91
90
|
*/
|
|
92
91
|
function loadArtifactContent(
|
|
93
92
|
artifactPath: string,
|
|
94
|
-
absPath: string | null,
|
|
95
|
-
opts: {
|
|
96
|
-
artifact_type: string;
|
|
97
|
-
milestone_id: string;
|
|
98
|
-
slice_id?: string;
|
|
99
|
-
task_id?: string;
|
|
100
|
-
},
|
|
101
93
|
): string | null {
|
|
102
|
-
// Try DB first
|
|
103
94
|
const artifact = getArtifact(artifactPath);
|
|
104
95
|
if (artifact && artifact.full_content) {
|
|
105
96
|
return artifact.full_content;
|
|
106
97
|
}
|
|
107
98
|
|
|
108
|
-
|
|
109
|
-
if (!absPath) {
|
|
110
|
-
process.stderr.write(
|
|
111
|
-
`markdown-renderer: artifact not found in DB or on disk: ${artifactPath}\n`,
|
|
112
|
-
);
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
let content: string;
|
|
117
|
-
try {
|
|
118
|
-
content = readFileSync(absPath, "utf-8");
|
|
119
|
-
} catch {
|
|
120
|
-
logWarning("renderer", `cannot read file from disk: ${absPath}`);
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Store in DB for future use (graceful degradation path)
|
|
125
|
-
try {
|
|
126
|
-
insertArtifact({
|
|
127
|
-
path: artifactPath,
|
|
128
|
-
artifact_type: opts.artifact_type,
|
|
129
|
-
milestone_id: opts.milestone_id,
|
|
130
|
-
slice_id: opts.slice_id ?? null,
|
|
131
|
-
task_id: opts.task_id ?? null,
|
|
132
|
-
full_content: content,
|
|
133
|
-
});
|
|
134
|
-
} catch {
|
|
135
|
-
// Non-fatal: we have the content, DB storage is best-effort
|
|
136
|
-
logWarning("renderer", `failed to store disk fallback in DB: ${artifactPath}`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return content;
|
|
99
|
+
return null;
|
|
140
100
|
}
|
|
141
101
|
|
|
142
102
|
/**
|
|
@@ -507,20 +467,15 @@ export async function renderRoadmapCheckboxes(
|
|
|
507
467
|
const absPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
508
468
|
const artifactPath = absPath ? toArtifactPath(absPath, basePath) : null;
|
|
509
469
|
|
|
510
|
-
// Load content from DB
|
|
470
|
+
// Load content from DB; regenerate from DB rows when the artifact is absent.
|
|
511
471
|
let content: string | null = null;
|
|
512
472
|
if (artifactPath) {
|
|
513
|
-
content = loadArtifactContent(artifactPath
|
|
514
|
-
artifact_type: "ROADMAP",
|
|
515
|
-
milestone_id: milestoneId,
|
|
516
|
-
});
|
|
473
|
+
content = loadArtifactContent(artifactPath);
|
|
517
474
|
}
|
|
518
475
|
|
|
519
476
|
if (!content) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
);
|
|
523
|
-
return false;
|
|
477
|
+
await renderRoadmapFromDb(basePath, milestoneId);
|
|
478
|
+
return true;
|
|
524
479
|
}
|
|
525
480
|
|
|
526
481
|
// Apply checkbox patches for each slice
|
|
@@ -582,18 +537,12 @@ export async function renderPlanCheckboxes(
|
|
|
582
537
|
|
|
583
538
|
let content: string | null = null;
|
|
584
539
|
if (artifactPath) {
|
|
585
|
-
content = loadArtifactContent(artifactPath
|
|
586
|
-
artifact_type: "PLAN",
|
|
587
|
-
milestone_id: milestoneId,
|
|
588
|
-
slice_id: sliceId,
|
|
589
|
-
});
|
|
540
|
+
content = loadArtifactContent(artifactPath);
|
|
590
541
|
}
|
|
591
542
|
|
|
592
543
|
if (!content) {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
);
|
|
596
|
-
return false;
|
|
544
|
+
await renderPlanFromDb(basePath, milestoneId, sliceId);
|
|
545
|
+
return true;
|
|
597
546
|
}
|
|
598
547
|
|
|
599
548
|
// Apply checkbox patches for each task
|
|
@@ -9,7 +9,7 @@ import { existsSync, readdirSync } from "node:fs";
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { spawnSync } from "node:child_process";
|
|
11
11
|
import { loadFile } from "./files.js";
|
|
12
|
-
import { resolveMilestoneFile } from "./paths.js";
|
|
12
|
+
import { resolveGsdPathContract, resolveMilestoneFile } from "./paths.js";
|
|
13
13
|
import { mergeMilestoneToMain } from "./auto-worktree.js";
|
|
14
14
|
import { MergeConflictError } from "./git-service.js";
|
|
15
15
|
import { removeSessionStatus } from "./session-status-io.js";
|
|
@@ -33,12 +33,13 @@ export type MergeOrder = "sequential" | "by-completion";
|
|
|
33
33
|
// ─── Merge Queue ───────────────────────────────────────────────────────────
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* Check whether a milestone is complete by querying
|
|
36
|
+
* Check whether a milestone is complete by querying the canonical project DB.
|
|
37
37
|
* Uses a subprocess to avoid disrupting the global DB singleton.
|
|
38
|
-
* Returns true when milestones.status = 'complete' in
|
|
38
|
+
* Returns true when milestones.status = 'complete' in project gsd.db.
|
|
39
39
|
*/
|
|
40
|
-
export function
|
|
41
|
-
const
|
|
40
|
+
export function isMilestoneCompleteInProjectDb(basePath: string, mid: string): boolean {
|
|
41
|
+
const workRoot = join(basePath, ".gsd", "worktrees", mid);
|
|
42
|
+
const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
|
|
42
43
|
if (!existsSync(dbPath)) return false;
|
|
43
44
|
|
|
44
45
|
try {
|
|
@@ -55,15 +56,15 @@ export function isMilestoneCompleteInWorktreeDb(basePath: string, mid: string):
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
|
-
* Discover milestone IDs with status='complete' in
|
|
59
|
-
*
|
|
59
|
+
* Discover milestone IDs with status='complete' in the canonical DB,
|
|
60
|
+
* using worktree directories only to enumerate active parallel workers.
|
|
60
61
|
*/
|
|
61
62
|
function discoverDbCompletedMilestones(basePath: string): Set<string> {
|
|
62
63
|
const completed = new Set<string>();
|
|
63
64
|
const worktreeDir = join(basePath, ".gsd", "worktrees");
|
|
64
65
|
try {
|
|
65
66
|
for (const entry of readdirSync(worktreeDir)) {
|
|
66
|
-
if (entry.startsWith("M") &&
|
|
67
|
+
if (entry.startsWith("M") && isMilestoneCompleteInProjectDb(basePath, entry)) {
|
|
67
68
|
completed.add(entry);
|
|
68
69
|
}
|
|
69
70
|
}
|
|
@@ -78,9 +79,9 @@ function discoverDbCompletedMilestones(basePath: string): Set<string> {
|
|
|
78
79
|
* Sequential: merge in milestone ID order (M001 before M002).
|
|
79
80
|
* By-completion: merge in the order milestones finished.
|
|
80
81
|
*
|
|
81
|
-
* When basePath is provided, also checks
|
|
82
|
-
* source of truth
|
|
83
|
-
* are included if their
|
|
82
|
+
* When basePath is provided, also checks the canonical project DB as the
|
|
83
|
+
* source of truth. Workers with stale orchestrator state (e.g. "error")
|
|
84
|
+
* are included if their project DB row shows status='complete'.
|
|
84
85
|
* See: https://github.com/gsd-build/gsd-2/issues/2812
|
|
85
86
|
*/
|
|
86
87
|
export function determineMergeOrder(
|
|
@@ -93,7 +94,7 @@ export function determineMergeOrder(
|
|
|
93
94
|
workers.filter(w => w.state === "stopped").map(w => w.milestoneId),
|
|
94
95
|
);
|
|
95
96
|
|
|
96
|
-
// When basePath is available, also check
|
|
97
|
+
// When basePath is available, also check the project DB for milestones
|
|
97
98
|
// whose orchestrator state is stale but are actually complete (#2812)
|
|
98
99
|
const dbCompleted = basePath ? discoverDbCompletedMilestones(basePath) : new Set<string>();
|
|
99
100
|
|
|
@@ -109,7 +110,7 @@ export function determineMergeOrder(
|
|
|
109
110
|
if (w) {
|
|
110
111
|
allMergeable.push(w);
|
|
111
112
|
} else {
|
|
112
|
-
// Milestone discovered from
|
|
113
|
+
// Milestone discovered from project DB but not in workers list
|
|
113
114
|
allMergeable.push({
|
|
114
115
|
milestoneId: mid,
|
|
115
116
|
title: mid,
|
|
@@ -17,6 +17,7 @@ import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
|
|
|
17
17
|
|
|
18
18
|
import { formatDuration, STATUS_GLYPH, STATUS_COLOR } from "../shared/mod.js";
|
|
19
19
|
import { formattedShortcutPair } from "./shortcut-defs.js";
|
|
20
|
+
import { resolveGsdPathContract } from "./paths.js";
|
|
20
21
|
|
|
21
22
|
// ─── Types ────────────────────────────────────────────────────────────────
|
|
22
23
|
|
|
@@ -126,7 +127,8 @@ function discoverWorkers(basePath: string): string[] {
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
function querySliceProgress(basePath: string, mid: string): SliceProgress[] {
|
|
129
|
-
const
|
|
130
|
+
const workRoot = join(basePath, ".gsd", "worktrees", mid);
|
|
131
|
+
const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
|
|
130
132
|
if (!existsSync(dbPath)) return [];
|
|
131
133
|
|
|
132
134
|
try {
|
|
@@ -166,7 +168,8 @@ function extractCostFromNdjson(basePath: string, mid: string): number {
|
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
function queryRecentCompletions(basePath: string, mid: string): string[] {
|
|
169
|
-
const
|
|
171
|
+
const workRoot = join(basePath, ".gsd", "worktrees", mid);
|
|
172
|
+
const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
|
|
170
173
|
if (!existsSync(dbPath)) return [];
|
|
171
174
|
try {
|
|
172
175
|
const sql = `SELECT id, slice_id, one_liner FROM tasks WHERE milestone_id='${mid}' AND status='complete' AND completed_at IS NOT NULL ORDER BY completed_at DESC LIMIT 5`;
|
|
@@ -10,12 +10,13 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { readdirSync, existsSync, realpathSync, Dirent } from "node:fs";
|
|
13
|
-
import { join, dirname, normalize } from "node:path";
|
|
13
|
+
import { join, dirname, normalize, resolve } from "node:path";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import { spawnSync } from "node:child_process";
|
|
16
16
|
import { nativeScanGsdTree, type GsdTreeEntry } from "./native-parser-bridge.js";
|
|
17
17
|
import { DIR_CACHE_MAX } from "./constants.js";
|
|
18
18
|
import { gsdHome } from "./gsd-home.js";
|
|
19
|
+
import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "./worktree-root.js";
|
|
19
20
|
|
|
20
21
|
// ─── Directory Listing Cache ──────────────────────────────────────────────────
|
|
21
22
|
|
|
@@ -286,6 +287,56 @@ const LEGACY_GSD_ROOT_FILES: Record<GSDRootFileKey, string> = {
|
|
|
286
287
|
|
|
287
288
|
const gsdRootCache = new Map<string, string>();
|
|
288
289
|
|
|
290
|
+
export interface GsdPathContract {
|
|
291
|
+
/** Canonical repo/project root where authoritative state lives. */
|
|
292
|
+
projectRoot: string;
|
|
293
|
+
/** Current execution root, which may be an auto-worktree. */
|
|
294
|
+
workRoot: string;
|
|
295
|
+
/** Canonical authoritative .gsd directory. */
|
|
296
|
+
projectGsd: string;
|
|
297
|
+
/** Legacy worktree-local .gsd projection directory, when applicable. */
|
|
298
|
+
worktreeGsd: string | null;
|
|
299
|
+
/** Canonical authoritative SQLite DB path. */
|
|
300
|
+
projectDb: string;
|
|
301
|
+
/** True when workRoot is inside a GSD worktree layout. */
|
|
302
|
+
isWorktree: boolean;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function resolveGsdPathContract(
|
|
306
|
+
workRoot: string,
|
|
307
|
+
originalProjectRoot?: string | null,
|
|
308
|
+
): GsdPathContract {
|
|
309
|
+
const resolvedWorkRoot = resolve(workRoot || process.cwd());
|
|
310
|
+
const isWorktree = isGsdWorktreePath(resolvedWorkRoot);
|
|
311
|
+
if (isWorktree && !originalProjectRoot?.trim()) {
|
|
312
|
+
const externalMatch = /[/\\]\.gsd[/\\]projects[/\\][^/\\]+[/\\]worktrees(?:[/\\]|$)/.exec(resolvedWorkRoot);
|
|
313
|
+
if (externalMatch) {
|
|
314
|
+
const worktreesIdx = externalMatch[0].search(/[/\\]worktrees(?:[/\\]|$)/);
|
|
315
|
+
const projectGsd = resolvedWorkRoot.slice(0, externalMatch.index + worktreesIdx);
|
|
316
|
+
return {
|
|
317
|
+
projectRoot: dirname(dirname(projectGsd)),
|
|
318
|
+
workRoot: resolvedWorkRoot,
|
|
319
|
+
projectGsd,
|
|
320
|
+
worktreeGsd: join(resolvedWorkRoot, ".gsd"),
|
|
321
|
+
projectDb: join(projectGsd, "gsd.db"),
|
|
322
|
+
isWorktree,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const projectRoot = resolve(resolveWorktreeProjectRoot(resolvedWorkRoot, originalProjectRoot));
|
|
327
|
+
const projectGsd = join(projectRoot, ".gsd");
|
|
328
|
+
const worktreeGsd = isWorktree ? join(resolvedWorkRoot, ".gsd") : null;
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
projectRoot,
|
|
332
|
+
workRoot: resolvedWorkRoot,
|
|
333
|
+
projectGsd,
|
|
334
|
+
worktreeGsd,
|
|
335
|
+
projectDb: join(projectGsd, "gsd.db"),
|
|
336
|
+
isWorktree,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
289
340
|
/** Exported for tests only — do not call in production code. */
|
|
290
341
|
export function _clearGsdRootCache(): void {
|
|
291
342
|
gsdRootCache.clear();
|
|
@@ -378,6 +429,9 @@ function isInsideGsdWorktree(p: string): boolean {
|
|
|
378
429
|
}
|
|
379
430
|
|
|
380
431
|
function probeGsdRoot(rawBasePath: string): string {
|
|
432
|
+
const contract = resolveGsdPathContract(rawBasePath);
|
|
433
|
+
if (contract.isWorktree) return contract.projectGsd;
|
|
434
|
+
|
|
381
435
|
// 1. Fast path — check the input path directly
|
|
382
436
|
const local = join(rawBasePath, ".gsd");
|
|
383
437
|
if (existsSync(local)) return local;
|
|
@@ -10,6 +10,12 @@ All relevant context has been preloaded below — start working immediately with
|
|
|
10
10
|
|
|
11
11
|
{{inlinedContext}}
|
|
12
12
|
|
|
13
|
+
## Already Planned? Soft Brake
|
|
14
|
+
|
|
15
|
+
If `{{outputPath}}` already exists on disk with at least one slice line (e.g. `- [ ] **S01:`) AND `gsd_query` reports slice rows for this milestone, a prior `gsd_plan_milestone` call already persisted the plan. Do **not** re-call `gsd_plan_milestone` — its UPSERT would overwrite the existing plan with whatever you reconstruct, which is unsafe when you don't have the original decomposition reasoning. Skip directly to the ready phrase at the end of this prompt.
|
|
16
|
+
|
|
17
|
+
If only the file exists (no DB rows) or only DB rows exist (no file), the prior write was incomplete — proceed with planning as normal so the tool reconciles both.
|
|
18
|
+
|
|
13
19
|
## Your Role in the Pipeline
|
|
14
20
|
|
|
15
21
|
You are the first deep look at this milestone. You have full tool access — explore the codebase, look up docs, investigate technology choices. Your job is to understand the landscape and then strategically decompose the work into demoable slices.
|
|
@@ -13,6 +13,7 @@ import { join } from "node:path";
|
|
|
13
13
|
import { gsdRoot } from "./paths.js";
|
|
14
14
|
import { milestoneIdSort } from "./milestone-ids.js";
|
|
15
15
|
import { loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
|
|
16
|
+
import { isDbAvailable, setMilestoneQueueOrder } from "./gsd-db.js";
|
|
16
17
|
|
|
17
18
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
18
19
|
|
|
@@ -63,9 +64,13 @@ export function loadQueueOrder(basePath: string): string[] | null {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
|
-
* Save a custom queue order
|
|
67
|
+
* Save a custom queue order. The DB sequence is canonical when a DB
|
|
68
|
+
* connection is open; QUEUE-ORDER.json remains a compatibility projection.
|
|
67
69
|
*/
|
|
68
70
|
export function saveQueueOrder(basePath: string, order: string[]): void {
|
|
71
|
+
if (isDbAvailable()) {
|
|
72
|
+
setMilestoneQueueOrder(order);
|
|
73
|
+
}
|
|
69
74
|
const data: QueueOrderFile = {
|
|
70
75
|
order,
|
|
71
76
|
updatedAt: new Date().toISOString(),
|
|
@@ -20,7 +20,7 @@ import { getMilestoneSlices, isDbAvailable } from "./gsd-db.js";
|
|
|
20
20
|
import { buildExistingMilestonesContext } from "./guided-flow-queue.js";
|
|
21
21
|
import { loadPrompt } from "./prompt-loader.js";
|
|
22
22
|
import { isGsdGitignored } from "./gitignore.js";
|
|
23
|
-
import {
|
|
23
|
+
import { currentDirectoryRoot } from "./commands/context.js";
|
|
24
24
|
|
|
25
25
|
// ─── Entry Point ──────────────────────────────────────────────────────────────
|
|
26
26
|
|
|
@@ -34,7 +34,7 @@ export async function handleRethink(
|
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const basePath =
|
|
37
|
+
const basePath = currentDirectoryRoot();
|
|
38
38
|
const root = gsdRoot(basePath);
|
|
39
39
|
if (!existsSync(root)) {
|
|
40
40
|
ctx.ui.notify("No GSD project found. Run /gsd init first.", "warning");
|