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
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { existsSync, mkdirSync } from "node:fs";
|
|
17
17
|
import { join, relative } from "node:path";
|
|
18
18
|
import { loadPrompt } from "./prompt-loader.js";
|
|
19
|
-
import {
|
|
19
|
+
import { currentDirectoryRoot } from "./commands/context.js";
|
|
20
20
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
21
21
|
export const DEFAULT_FOCUS = "tech+arch";
|
|
22
22
|
export const VALID_FOCUS_AREAS = ["tech", "arch", "quality", "concerns", "tech+arch"];
|
|
@@ -67,7 +67,7 @@ export function checkExistingDocuments(paths) {
|
|
|
67
67
|
}
|
|
68
68
|
// ─── Command handler ──────────────────────────────────────────────────────────
|
|
69
69
|
export async function handleScan(args, ctx, pi) {
|
|
70
|
-
const basePath =
|
|
70
|
+
const basePath = currentDirectoryRoot();
|
|
71
71
|
const { focus } = parseScanArgs(args);
|
|
72
72
|
const outputDir = join(basePath, ".gsd", "codebase");
|
|
73
73
|
const outputPaths = buildScanOutputPaths(focus, basePath);
|
|
@@ -13,7 +13,7 @@ import { getLedger, getProjectTotals, aggregateByModel, formatCost, formatTokenC
|
|
|
13
13
|
import { nativeGetCurrentBranch, nativeDetectMainBranch } from "./native-git-bridge.js";
|
|
14
14
|
import { formatDuration } from "../shared/format-utils.js";
|
|
15
15
|
import { parseEvalReviewFrontmatter } from "./eval-review-schema.js";
|
|
16
|
-
import {
|
|
16
|
+
import { currentDirectoryRoot } from "./commands/context.js";
|
|
17
17
|
function git(basePath, args) {
|
|
18
18
|
return execFileSync("git", args, { cwd: basePath, encoding: "utf-8" }).trim();
|
|
19
19
|
}
|
|
@@ -176,7 +176,7 @@ function generatePRContent(basePath, milestoneId, milestoneTitle) {
|
|
|
176
176
|
return { title, body: sections.join("\n") };
|
|
177
177
|
}
|
|
178
178
|
export async function handleShip(args, ctx, _pi) {
|
|
179
|
-
const basePath =
|
|
179
|
+
const basePath = currentDirectoryRoot();
|
|
180
180
|
const dryRun = args.includes("--dry-run");
|
|
181
181
|
const draft = args.includes("--draft");
|
|
182
182
|
const force = args.includes("--force");
|
|
@@ -13,7 +13,7 @@ import { createGitService, runGit } from "./git-service.js";
|
|
|
13
13
|
import { isAutoActive, isAutoPaused } from "./auto.js";
|
|
14
14
|
import { getErrorMessage } from "./error-utils.js";
|
|
15
15
|
import { resolvePlugin } from "./workflow-plugins.js";
|
|
16
|
-
import {
|
|
16
|
+
import { currentDirectoryRoot } from "./commands/context.js";
|
|
17
17
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
18
18
|
/**
|
|
19
19
|
* Generate a URL-friendly slug from text.
|
|
@@ -143,7 +143,7 @@ export async function handleStart(args, ctx, pi) {
|
|
|
143
143
|
// ─── Resume detection ───────────────────────────────────────────────────
|
|
144
144
|
// /gsd start --resume or /gsd start resume → resume in-progress workflow
|
|
145
145
|
if (trimmed === "--resume" || trimmed === "resume") {
|
|
146
|
-
const basePath =
|
|
146
|
+
const basePath = currentDirectoryRoot();
|
|
147
147
|
const inProgress = findInProgressWorkflows(basePath);
|
|
148
148
|
if (inProgress.length === 0) {
|
|
149
149
|
ctx.ui.notify("No in-progress workflows found.", "info");
|
|
@@ -182,7 +182,7 @@ export async function handleStart(args, ctx, pi) {
|
|
|
182
182
|
}
|
|
183
183
|
// Show in-progress workflows when /gsd start is called with no args
|
|
184
184
|
if (!trimmed) {
|
|
185
|
-
const basePath =
|
|
185
|
+
const basePath = currentDirectoryRoot();
|
|
186
186
|
const inProgress = findInProgressWorkflows(basePath);
|
|
187
187
|
if (inProgress.length > 0) {
|
|
188
188
|
const wf = inProgress[0];
|
|
@@ -257,7 +257,7 @@ export async function handleStart(args, ctx, pi) {
|
|
|
257
257
|
// ─── Resolved template ───────────────────────────────────────────────────
|
|
258
258
|
const templateId = match.id;
|
|
259
259
|
const template = match.template;
|
|
260
|
-
const basePath =
|
|
260
|
+
const basePath = currentDirectoryRoot();
|
|
261
261
|
const date = new Date().toISOString().split("T")[0];
|
|
262
262
|
// Load the workflow template content — prefer a project/global plugin
|
|
263
263
|
// override if one exists (same name, .md format).
|
|
@@ -437,7 +437,7 @@ export function dispatchMarkdownPhasePlugin(plugin, description, ctx, pi) {
|
|
|
437
437
|
return;
|
|
438
438
|
}
|
|
439
439
|
const templateId = plugin.name;
|
|
440
|
-
const basePath =
|
|
440
|
+
const basePath = currentDirectoryRoot();
|
|
441
441
|
const date = new Date().toISOString().split("T")[0];
|
|
442
442
|
let workflowContent;
|
|
443
443
|
try {
|
|
@@ -273,22 +273,6 @@ export async function saveRequirementToDb(fields, basePath) {
|
|
|
273
273
|
ORDER BY id
|
|
274
274
|
LIMIT 1`)
|
|
275
275
|
.get({ ':description': fields.description });
|
|
276
|
-
const previousRow = existingRow
|
|
277
|
-
? {
|
|
278
|
-
id: existingRow['id'],
|
|
279
|
-
class: existingRow['class'],
|
|
280
|
-
status: existingRow['status'],
|
|
281
|
-
description: existingRow['description'],
|
|
282
|
-
why: existingRow['why'],
|
|
283
|
-
source: existingRow['source'],
|
|
284
|
-
primary_owner: existingRow['primary_owner'],
|
|
285
|
-
supporting_slices: existingRow['supporting_slices'],
|
|
286
|
-
validation: existingRow['validation'],
|
|
287
|
-
notes: existingRow['notes'],
|
|
288
|
-
full_content: existingRow['full_content'],
|
|
289
|
-
superseded_by: existingRow['superseded_by'] ?? null,
|
|
290
|
-
}
|
|
291
|
-
: null;
|
|
292
276
|
const row = adapter
|
|
293
277
|
.prepare('SELECT MAX(CAST(SUBSTR(id, 2) AS INTEGER)) as max_num FROM requirements')
|
|
294
278
|
.get();
|
|
@@ -313,9 +297,9 @@ export async function saveRequirementToDb(fields, basePath) {
|
|
|
313
297
|
superseded_by: existingRow?.['superseded_by'] ?? null,
|
|
314
298
|
};
|
|
315
299
|
db.upsertRequirement(requirement);
|
|
316
|
-
return { id: nextId
|
|
300
|
+
return { id: nextId };
|
|
317
301
|
});
|
|
318
|
-
const { id
|
|
302
|
+
const { id } = txResult;
|
|
319
303
|
// Fetch all requirements for full file regeneration
|
|
320
304
|
const adapter = db._getAdapter();
|
|
321
305
|
let allRequirements = [];
|
|
@@ -343,19 +327,7 @@ export async function saveRequirementToDb(fields, basePath) {
|
|
|
343
327
|
await saveFile(filePath, md);
|
|
344
328
|
}
|
|
345
329
|
catch (diskErr) {
|
|
346
|
-
|
|
347
|
-
try {
|
|
348
|
-
if (isNew) {
|
|
349
|
-
db.deleteRequirementById(id);
|
|
350
|
-
}
|
|
351
|
-
else if (previousRow) {
|
|
352
|
-
db.upsertRequirement(previousRow);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
catch (rollbackErr) {
|
|
356
|
-
logError('manifest', 'SPLIT BRAIN: disk write failed AND DB rollback failed — DB has orphaned row', { fn: 'saveRequirementToDb', id, error: String(rollbackErr.message) });
|
|
357
|
-
}
|
|
358
|
-
throw diskErr;
|
|
330
|
+
logWarning('projection', 'REQUIREMENTS.md projection write failed; DB requirement remains committed', { fn: 'saveRequirementToDb', id, error: String(diskErr.message) });
|
|
359
331
|
}
|
|
360
332
|
invalidateStateCache();
|
|
361
333
|
clearPathCache();
|
|
@@ -463,14 +435,7 @@ export async function saveDecisionToDb(fields, basePath) {
|
|
|
463
435
|
await saveFile(filePath, md);
|
|
464
436
|
}
|
|
465
437
|
catch (diskErr) {
|
|
466
|
-
|
|
467
|
-
try {
|
|
468
|
-
db.deleteDecisionById(id);
|
|
469
|
-
}
|
|
470
|
-
catch (rollbackErr) {
|
|
471
|
-
logError('manifest', 'SPLIT BRAIN: disk write failed AND DB rollback failed — DB has orphaned row', { fn: 'saveDecisionToDb', id, error: String(rollbackErr.message) });
|
|
472
|
-
}
|
|
473
|
-
throw diskErr;
|
|
438
|
+
logWarning('projection', 'DECISIONS.md projection write failed; DB decision remains committed', { fn: 'saveDecisionToDb', id, error: String(diskErr.message) });
|
|
474
439
|
}
|
|
475
440
|
// #2661: When a decision defers a slice, update the slice status in the DB
|
|
476
441
|
// so the dispatcher skips it. Without this, STATE.md and DECISIONS.md are
|
|
@@ -584,34 +549,7 @@ export function extractDeferredSliceRef(fields) {
|
|
|
584
549
|
export async function updateRequirementInDb(id, updates, basePath) {
|
|
585
550
|
try {
|
|
586
551
|
const db = await import('./gsd-db.js');
|
|
587
|
-
|
|
588
|
-
// If requirement doesn't exist in DB, seed the entire requirements table
|
|
589
|
-
// from REQUIREMENTS.md first (#3346). This handles the standard workflow
|
|
590
|
-
// where requirements are authored in markdown during discussion but never
|
|
591
|
-
// imported into the database — making gsd_requirement_update always fail
|
|
592
|
-
// with "not_found" at milestone completion.
|
|
593
|
-
if (!existing) {
|
|
594
|
-
const reqFilePath = resolveGsdRootFile(basePath, 'REQUIREMENTS');
|
|
595
|
-
try {
|
|
596
|
-
const content = readFileSync(reqFilePath, 'utf-8');
|
|
597
|
-
const { parseRequirementsSections } = await import('./md-importer.js');
|
|
598
|
-
const parsed = parseRequirementsSections(content);
|
|
599
|
-
if (parsed.length > 0) {
|
|
600
|
-
logWarning('manifest', `Seeding ${parsed.length} requirements from REQUIREMENTS.md into DB (first update triggers import)`, { fn: 'updateRequirementInDb' });
|
|
601
|
-
for (const req of parsed) {
|
|
602
|
-
// Only seed if not already in DB (avoid overwriting concurrent inserts)
|
|
603
|
-
if (!db.getRequirementById(req.id)) {
|
|
604
|
-
db.upsertRequirement(req);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
// Re-check after seeding
|
|
608
|
-
existing = db.getRequirementById(id);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
catch {
|
|
612
|
-
// REQUIREMENTS.md missing or unparseable — fall through to skeleton
|
|
613
|
-
}
|
|
614
|
-
}
|
|
552
|
+
const existing = db.getRequirementById(id);
|
|
615
553
|
const base = existing ?? {
|
|
616
554
|
id,
|
|
617
555
|
class: '',
|
|
@@ -662,11 +600,7 @@ export async function updateRequirementInDb(id, updates, basePath) {
|
|
|
662
600
|
await saveFile(filePath, md);
|
|
663
601
|
}
|
|
664
602
|
catch (diskErr) {
|
|
665
|
-
|
|
666
|
-
if (existing) {
|
|
667
|
-
db.upsertRequirement(existing);
|
|
668
|
-
}
|
|
669
|
-
throw diskErr;
|
|
603
|
+
logWarning('projection', 'REQUIREMENTS.md projection write failed; DB requirement update remains committed', { fn: 'updateRequirementInDb', id, error: String(diskErr.message) });
|
|
670
604
|
}
|
|
671
605
|
// Invalidate file-read caches so deriveState() sees the updated markdown.
|
|
672
606
|
// Do NOT clear the artifacts table — we just wrote to it intentionally.
|
|
@@ -701,20 +635,19 @@ export async function saveArtifactToDb(opts, basePath) {
|
|
|
701
635
|
}
|
|
702
636
|
contentToPersist = generateRequirementsMd(activeRequirements);
|
|
703
637
|
}
|
|
704
|
-
// Shrinkage guard: if the file already exists and the new
|
|
705
|
-
// significantly smaller (<50%), preserve the richer file on
|
|
706
|
-
//
|
|
707
|
-
//
|
|
708
|
-
// canonical
|
|
709
|
-
//
|
|
710
|
-
|
|
638
|
+
// Shrinkage guard: if the projection file already exists and the new
|
|
639
|
+
// content is significantly smaller (<50%), preserve the richer file on
|
|
640
|
+
// disk, but keep the DB row authoritative with the caller-provided content.
|
|
641
|
+
// The disk file is a stale projection until the next explicit render.
|
|
642
|
+
// Root canonical artifacts are exempt because their content is rendered
|
|
643
|
+
// from canonical DB state, and cleanup/consolidation is often intentionally
|
|
644
|
+
// much smaller than a malformed accumulated file.
|
|
711
645
|
let skipDiskWrite = false;
|
|
712
646
|
if (!isRootCanonicalArtifact(opts) && existsSync(fullPath)) {
|
|
713
647
|
const existingSize = statSync(fullPath).size;
|
|
714
648
|
const newSize = Buffer.byteLength(contentToPersist, 'utf-8');
|
|
715
649
|
if (existingSize > 0 && newSize < existingSize * 0.5) {
|
|
716
|
-
logWarning('
|
|
717
|
-
dbContent = readFileSync(fullPath, 'utf-8');
|
|
650
|
+
logWarning('projection', `new content (${newSize}B) is <50% of existing projection (${existingSize}B), preserving disk file while DB remains authoritative`, { fn: 'saveArtifactToDb', path: opts.path });
|
|
718
651
|
skipDiskWrite = true;
|
|
719
652
|
}
|
|
720
653
|
}
|
|
@@ -724,7 +657,7 @@ export async function saveArtifactToDb(opts, basePath) {
|
|
|
724
657
|
milestone_id: opts.milestone_id ?? null,
|
|
725
658
|
slice_id: opts.slice_id ?? null,
|
|
726
659
|
task_id: opts.task_id ?? null,
|
|
727
|
-
full_content:
|
|
660
|
+
full_content: contentToPersist,
|
|
728
661
|
});
|
|
729
662
|
// Write the file to disk (only if we're not preserving a richer existing file)
|
|
730
663
|
if (!skipDiskWrite) {
|
|
@@ -732,9 +665,7 @@ export async function saveArtifactToDb(opts, basePath) {
|
|
|
732
665
|
await saveFile(fullPath, contentToPersist);
|
|
733
666
|
}
|
|
734
667
|
catch (diskErr) {
|
|
735
|
-
|
|
736
|
-
db.deleteArtifactByPath(opts.path);
|
|
737
|
-
throw diskErr;
|
|
668
|
+
logWarning('projection', 'artifact projection write failed; DB artifact remains committed', { fn: 'saveArtifactToDb', path: opts.path, error: String(diskErr.message) });
|
|
738
669
|
}
|
|
739
670
|
}
|
|
740
671
|
// Invalidate file-read caches so deriveState() sees the updated markdown.
|
|
@@ -45,25 +45,21 @@ export function getPriorSliceCompletionBlocker(base, _mainBranch, unitType, unit
|
|
|
45
45
|
// completion, which is wrong when the SUMMARY is a failure-path report
|
|
46
46
|
// (verification FAILED, blocker placeholder, etc.). Resolve as follows:
|
|
47
47
|
// 1. When DB is available and status is closed → skip (authoritative).
|
|
48
|
-
// 2. When
|
|
49
|
-
//
|
|
50
|
-
// the guard can still block dependents of an active milestone.
|
|
51
|
-
// 3. Otherwise (SUMMARY without failure markers) → skip. Preserves
|
|
52
|
-
// the #1716 contract where a completed milestone with unchecked
|
|
53
|
-
// remediation slices is still treated as done.
|
|
54
|
-
const summaryPath = resolveMilestoneFile(base, mid, "SUMMARY");
|
|
48
|
+
// 2. When DB is unavailable, legacy SUMMARY.md fallback may skip.
|
|
49
|
+
// DB-backed projects must not treat SUMMARY.md as authoritative.
|
|
55
50
|
if (isDbAvailable()) {
|
|
56
51
|
const milestoneRow = getMilestone(mid);
|
|
57
52
|
if (milestoneRow && isClosedStatus(milestoneRow.status))
|
|
58
53
|
continue;
|
|
59
54
|
}
|
|
60
|
-
|
|
55
|
+
else {
|
|
56
|
+
const summaryPath = resolveMilestoneFile(base, mid, "SUMMARY");
|
|
61
57
|
let summaryContent = null;
|
|
62
58
|
try {
|
|
63
|
-
summaryContent = readFileSync(summaryPath, "utf-8");
|
|
59
|
+
summaryContent = summaryPath ? readFileSync(summaryPath, "utf-8") : null;
|
|
64
60
|
}
|
|
65
61
|
catch { /* ignore */ }
|
|
66
|
-
if (
|
|
62
|
+
if (summaryContent && classifyMilestoneSummaryContent(summaryContent) !== "failure") {
|
|
67
63
|
continue;
|
|
68
64
|
}
|
|
69
65
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { existsSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { isDbAvailable, _getAdapter } from "./gsd-db.js";
|
|
4
|
-
import { resolveMilestoneFile } from "./paths.js";
|
|
4
|
+
import { resolveGsdPathContract, resolveMilestoneFile } from "./paths.js";
|
|
5
5
|
import { deriveState } from "./state.js";
|
|
6
6
|
import { readEvents } from "./workflow-events.js";
|
|
7
7
|
import { renderAllProjections } from "./workflow-projections.js";
|
|
8
8
|
export async function checkEngineHealth(basePath, issues, fixesApplied) {
|
|
9
|
-
const dbPath =
|
|
9
|
+
const dbPath = resolveGsdPathContract(basePath).projectDb;
|
|
10
10
|
if (!isDbAvailable() && existsSync(dbPath)) {
|
|
11
11
|
issues.push({
|
|
12
12
|
severity: "warning",
|
|
@@ -136,7 +136,7 @@ function openRawDb(path) {
|
|
|
136
136
|
const Database = providerModule;
|
|
137
137
|
return new Database(path);
|
|
138
138
|
}
|
|
139
|
-
export const SCHEMA_VERSION =
|
|
139
|
+
export const SCHEMA_VERSION = 23;
|
|
140
140
|
function indexExists(db, name) {
|
|
141
141
|
return !!db.prepare("SELECT 1 as present FROM sqlite_master WHERE type = 'index' AND name = ?").get(name);
|
|
142
142
|
}
|
|
@@ -299,7 +299,8 @@ function initSchema(db, fileBacked) {
|
|
|
299
299
|
verification_uat TEXT NOT NULL DEFAULT '',
|
|
300
300
|
definition_of_done TEXT NOT NULL DEFAULT '[]',
|
|
301
301
|
requirement_coverage TEXT NOT NULL DEFAULT '',
|
|
302
|
-
boundary_map_markdown TEXT NOT NULL DEFAULT ''
|
|
302
|
+
boundary_map_markdown TEXT NOT NULL DEFAULT '',
|
|
303
|
+
sequence INTEGER DEFAULT 0
|
|
303
304
|
)
|
|
304
305
|
`);
|
|
305
306
|
db.exec(`
|
|
@@ -1131,6 +1132,16 @@ function migrateSchema(db) {
|
|
|
1131
1132
|
":applied_at": new Date().toISOString(),
|
|
1132
1133
|
});
|
|
1133
1134
|
}
|
|
1135
|
+
if (currentVersion < 23) {
|
|
1136
|
+
// v23: milestone queue ordering moves into the canonical DB. The
|
|
1137
|
+
// historical QUEUE-ORDER.json file remains a projection, but runtime
|
|
1138
|
+
// derivation must not read it as authoritative state.
|
|
1139
|
+
ensureColumn(db, "milestones", "sequence", "ALTER TABLE milestones ADD COLUMN sequence INTEGER DEFAULT 0");
|
|
1140
|
+
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1141
|
+
":version": 23,
|
|
1142
|
+
":applied_at": new Date().toISOString(),
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1134
1145
|
db.exec("COMMIT");
|
|
1135
1146
|
}
|
|
1136
1147
|
catch (err) {
|
|
@@ -1518,6 +1529,31 @@ export function getActiveRequirements() {
|
|
|
1518
1529
|
superseded_by: null,
|
|
1519
1530
|
}));
|
|
1520
1531
|
}
|
|
1532
|
+
export function getRequirementCounts() {
|
|
1533
|
+
if (!currentDb) {
|
|
1534
|
+
return { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 };
|
|
1535
|
+
}
|
|
1536
|
+
const rows = currentDb
|
|
1537
|
+
.prepare("SELECT lower(status) as status, COUNT(*) as count FROM requirements GROUP BY lower(status)")
|
|
1538
|
+
.all();
|
|
1539
|
+
const counts = { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 };
|
|
1540
|
+
for (const row of rows) {
|
|
1541
|
+
const status = String(row["status"] ?? "");
|
|
1542
|
+
const count = Number(row["count"] ?? 0);
|
|
1543
|
+
counts.total += count;
|
|
1544
|
+
if (status === "active")
|
|
1545
|
+
counts.active += count;
|
|
1546
|
+
else if (status === "validated")
|
|
1547
|
+
counts.validated += count;
|
|
1548
|
+
else if (status === "deferred")
|
|
1549
|
+
counts.deferred += count;
|
|
1550
|
+
else if (status === "out-of-scope" || status === "out_of_scope")
|
|
1551
|
+
counts.outOfScope += count;
|
|
1552
|
+
else if (status === "blocked")
|
|
1553
|
+
counts.blocked += count;
|
|
1554
|
+
}
|
|
1555
|
+
return counts;
|
|
1556
|
+
}
|
|
1521
1557
|
export function getDbOwnerPid() {
|
|
1522
1558
|
return currentPid;
|
|
1523
1559
|
}
|
|
@@ -2164,6 +2200,7 @@ function rowToMilestone(row) {
|
|
|
2164
2200
|
definition_of_done: JSON.parse(row["definition_of_done"] || "[]"),
|
|
2165
2201
|
requirement_coverage: row["requirement_coverage"] ?? "",
|
|
2166
2202
|
boundary_map_markdown: row["boundary_map_markdown"] ?? "",
|
|
2203
|
+
sequence: Number(row["sequence"] ?? 0),
|
|
2167
2204
|
};
|
|
2168
2205
|
}
|
|
2169
2206
|
function rowToArtifact(row) {
|
|
@@ -2180,7 +2217,7 @@ function rowToArtifact(row) {
|
|
|
2180
2217
|
export function getAllMilestones() {
|
|
2181
2218
|
if (!currentDb)
|
|
2182
2219
|
return [];
|
|
2183
|
-
const rows = currentDb.prepare("SELECT * FROM milestones ORDER BY id").all();
|
|
2220
|
+
const rows = currentDb.prepare("SELECT * FROM milestones ORDER BY CASE WHEN sequence > 0 THEN 0 ELSE 1 END, sequence, id").all();
|
|
2184
2221
|
return rows.map(rowToMilestone);
|
|
2185
2222
|
}
|
|
2186
2223
|
export function getMilestone(id) {
|
|
@@ -2191,6 +2228,23 @@ export function getMilestone(id) {
|
|
|
2191
2228
|
return null;
|
|
2192
2229
|
return rowToMilestone(row);
|
|
2193
2230
|
}
|
|
2231
|
+
export function setMilestoneQueueOrder(order) {
|
|
2232
|
+
if (!currentDb)
|
|
2233
|
+
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2234
|
+
currentDb.exec("BEGIN IMMEDIATE");
|
|
2235
|
+
try {
|
|
2236
|
+
currentDb.prepare("UPDATE milestones SET sequence = 0").run();
|
|
2237
|
+
const stmt = currentDb.prepare("UPDATE milestones SET sequence = :sequence WHERE id = :id");
|
|
2238
|
+
order.forEach((id, index) => {
|
|
2239
|
+
stmt.run({ ":id": id, ":sequence": index + 1 });
|
|
2240
|
+
});
|
|
2241
|
+
currentDb.exec("COMMIT");
|
|
2242
|
+
}
|
|
2243
|
+
catch (err) {
|
|
2244
|
+
currentDb.exec("ROLLBACK");
|
|
2245
|
+
throw err;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2194
2248
|
/**
|
|
2195
2249
|
* Update a milestone's status in the database.
|
|
2196
2250
|
* Used by park/unpark to keep the DB in sync with the filesystem marker.
|
|
@@ -2358,6 +2412,8 @@ export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
|
|
|
2358
2412
|
// fall through to the main DB's existing value (not a literal default)
|
|
2359
2413
|
// so reconcile never silently clears state the main tree has recorded.
|
|
2360
2414
|
const hasDecisionSource = wtInfo.some((col) => col["name"] === "source");
|
|
2415
|
+
const wtMilestoneInfo = adapter.prepare("PRAGMA wt.table_info('milestones')").all();
|
|
2416
|
+
const hasMilestoneSequence = wtMilestoneInfo.some((col) => col["name"] === "sequence");
|
|
2361
2417
|
const wtSliceInfo = adapter.prepare("PRAGMA wt.table_info('slices')").all();
|
|
2362
2418
|
const hasIsSketch = wtSliceInfo.some((col) => col["name"] === "is_sketch");
|
|
2363
2419
|
const hasSketchScope = wtSliceInfo.some((col) => col["name"] === "sketch_scope");
|
|
@@ -2415,7 +2471,7 @@ export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
|
|
|
2415
2471
|
id, title, status, depends_on, created_at, completed_at,
|
|
2416
2472
|
vision, success_criteria, key_risks, proof_strategy,
|
|
2417
2473
|
verification_contract, verification_integration, verification_operational, verification_uat,
|
|
2418
|
-
definition_of_done, requirement_coverage, boundary_map_markdown
|
|
2474
|
+
definition_of_done, requirement_coverage, boundary_map_markdown, sequence
|
|
2419
2475
|
)
|
|
2420
2476
|
SELECT w.id, w.title,
|
|
2421
2477
|
CASE
|
|
@@ -2433,7 +2489,8 @@ export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
|
|
|
2433
2489
|
END,
|
|
2434
2490
|
w.vision, w.success_criteria, w.key_risks, w.proof_strategy,
|
|
2435
2491
|
w.verification_contract, w.verification_integration, w.verification_operational, w.verification_uat,
|
|
2436
|
-
w.definition_of_done, w.requirement_coverage, w.boundary_map_markdown
|
|
2492
|
+
w.definition_of_done, w.requirement_coverage, w.boundary_map_markdown,
|
|
2493
|
+
${hasMilestoneSequence ? "COALESCE(w.sequence, 0)" : "COALESCE(m.sequence, 0)"}
|
|
2437
2494
|
FROM wt.milestones w
|
|
2438
2495
|
LEFT JOIN milestones m ON m.id = w.id
|
|
2439
2496
|
`).run());
|
|
@@ -2659,6 +2716,15 @@ export function getAssessment(path) {
|
|
|
2659
2716
|
const row = currentDb.prepare(`SELECT * FROM assessments WHERE path = :path`).get({ ":path": path });
|
|
2660
2717
|
return row ?? null;
|
|
2661
2718
|
}
|
|
2719
|
+
export function getLatestAssessmentByScope(milestoneId, scope) {
|
|
2720
|
+
if (!currentDb)
|
|
2721
|
+
return null;
|
|
2722
|
+
const row = currentDb.prepare(`SELECT * FROM assessments
|
|
2723
|
+
WHERE milestone_id = :mid AND scope = :scope
|
|
2724
|
+
ORDER BY created_at DESC
|
|
2725
|
+
LIMIT 1`).get({ ":mid": milestoneId, ":scope": scope });
|
|
2726
|
+
return row ?? null;
|
|
2727
|
+
}
|
|
2662
2728
|
// ─── Quality Gates ───────────────────────────────────────────────────────
|
|
2663
2729
|
function rowToGate(row) {
|
|
2664
2730
|
return {
|
|
@@ -3027,10 +3093,10 @@ export function restoreManifest(manifest) {
|
|
|
3027
3093
|
const msStmt = db.prepare(`INSERT INTO milestones (id, title, status, depends_on, created_at, completed_at,
|
|
3028
3094
|
vision, success_criteria, key_risks, proof_strategy,
|
|
3029
3095
|
verification_contract, verification_integration, verification_operational, verification_uat,
|
|
3030
|
-
definition_of_done, requirement_coverage, boundary_map_markdown)
|
|
3031
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
3096
|
+
definition_of_done, requirement_coverage, boundary_map_markdown, sequence)
|
|
3097
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
3032
3098
|
for (const m of manifest.milestones) {
|
|
3033
|
-
msStmt.run(m.id, m.title, m.status, JSON.stringify(m.depends_on), m.created_at, m.completed_at, m.vision, JSON.stringify(m.success_criteria), JSON.stringify(m.key_risks), JSON.stringify(m.proof_strategy), m.verification_contract, m.verification_integration, m.verification_operational, m.verification_uat, JSON.stringify(m.definition_of_done), m.requirement_coverage, m.boundary_map_markdown);
|
|
3099
|
+
msStmt.run(m.id, m.title, m.status, JSON.stringify(m.depends_on), m.created_at, m.completed_at, m.vision, JSON.stringify(m.success_criteria), JSON.stringify(m.key_risks), JSON.stringify(m.proof_strategy), m.verification_contract, m.verification_integration, m.verification_operational, m.verification_uat, JSON.stringify(m.definition_of_done), m.requirement_coverage, m.boundary_map_markdown, m.sequence ?? 0);
|
|
3034
3100
|
}
|
|
3035
3101
|
// Restore slices (ADR-011 Phase 1: includes is_sketch + sketch_scope)
|
|
3036
3102
|
const slStmt = db.prepare(`INSERT INTO slices (milestone_id, id, title, status, risk, depends, demo,
|
|
@@ -19,7 +19,7 @@ import { assessInterruptedSession, formatInterruptedSessionRunningMessage, forma
|
|
|
19
19
|
import { listUnitRuntimeRecords, clearUnitRuntimeRecord } from "./unit-runtime.js";
|
|
20
20
|
import { resolveExpectedArtifactPath } from "./auto.js";
|
|
21
21
|
import { gsdHome } from "./gsd-home.js";
|
|
22
|
-
import { gsdRoot, milestonesDir, resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile, relMilestoneFile, relSliceFile, } from "./paths.js";
|
|
22
|
+
import { gsdRoot, milestonesDir, resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile, relMilestoneFile, relSliceFile, clearPathCache, } from "./paths.js";
|
|
23
23
|
import { join } from "node:path";
|
|
24
24
|
import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from "node:fs";
|
|
25
25
|
import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.js";
|
|
@@ -486,12 +486,35 @@ export function maybeHandleReadyPhraseWithoutFiles(event) {
|
|
|
486
486
|
const text = extractAssistantText(lastMsg);
|
|
487
487
|
if (!READY_PHRASE_RE.test(text))
|
|
488
488
|
return false;
|
|
489
|
+
// Bust paths.ts cached dir listings before checking for fresh writes. The
|
|
490
|
+
// LLM's Write tool calls do not invalidate paths.ts caches, so a stale
|
|
491
|
+
// listing taken before the milestone dir or its CONTEXT/ROADMAP files
|
|
492
|
+
// existed would falsely report the artifacts as missing and trigger the
|
|
493
|
+
// 3-strike "ready without files" abort even though the writes succeeded.
|
|
494
|
+
clearPathCache();
|
|
489
495
|
// Gate: artifacts must still be missing — if they exist, the happy path
|
|
490
496
|
// already fired and we have nothing to do.
|
|
491
497
|
const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
|
|
492
498
|
const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
493
499
|
if (contextFile || roadmapFile)
|
|
494
500
|
return false;
|
|
501
|
+
// Diagnostic: when the cached resolver reports both files missing, also probe
|
|
502
|
+
// the canonical paths with uncached existsSync so we can tell whether the
|
|
503
|
+
// recovery is firing on real-missing files or a path-resolution miss
|
|
504
|
+
// (basePath/symlink mismatch, stale cache despite agent-end-recovery flush,
|
|
505
|
+
// legacy descriptor dir not matching, etc.).
|
|
506
|
+
try {
|
|
507
|
+
const mDir = resolveMilestonePath(basePath, milestoneId);
|
|
508
|
+
const canonicalCtx = mDir ? join(mDir, `${milestoneId}-CONTEXT.md`) : null;
|
|
509
|
+
const canonicalRoadmap = mDir ? join(mDir, `${milestoneId}-ROADMAP.md`) : null;
|
|
510
|
+
logWarning("guided", `ready-phrase-reject diagnostic mid=${milestoneId} basePath=${basePath} ` +
|
|
511
|
+
`mDir=${mDir ?? "null"} ` +
|
|
512
|
+
`canonical-ctx=${canonicalCtx ?? "null"} ctx-exists=${canonicalCtx ? existsSync(canonicalCtx) : "n/a"} ` +
|
|
513
|
+
`canonical-roadmap=${canonicalRoadmap ?? "null"} roadmap-exists=${canonicalRoadmap ? existsSync(canonicalRoadmap) : "n/a"}`);
|
|
514
|
+
}
|
|
515
|
+
catch (e) {
|
|
516
|
+
logWarning("guided", `ready-phrase-reject diagnostic failed: ${e.message}`);
|
|
517
|
+
}
|
|
495
518
|
entry.readyRejectCount = (entry.readyRejectCount ?? 0) + 1;
|
|
496
519
|
if (entry.readyRejectCount > MAX_READY_REJECTS) {
|
|
497
520
|
// Give up: clear state and tell the user to re-run /gsd. Avoids an
|
|
@@ -576,14 +599,14 @@ export function maybeHandleEmptyIntentTurn(event, isAuto) {
|
|
|
576
599
|
// path, handled by maybeHandleReadyPhraseWithoutFiles.
|
|
577
600
|
if (READY_PHRASE_RE.test(text))
|
|
578
601
|
return false;
|
|
579
|
-
// Skip if the LLM is clearly handing back to the user.
|
|
580
|
-
//
|
|
581
|
-
//
|
|
582
|
-
//
|
|
583
|
-
//
|
|
602
|
+
// Skip if the LLM is clearly handing back to the user. Discuss flows
|
|
603
|
+
// often pose a question and follow it with a conditional intent on the
|
|
604
|
+
// same line ("Did I capture that correctly? If so, I'll write the
|
|
605
|
+
// requirements."). A line-trailing `?` check misses these because the
|
|
606
|
+
// line ends in `.`. Match any sentence-terminating `?` (followed by
|
|
607
|
+
// whitespace or end-of-text) — false negatives here auto-reply to the
|
|
584
608
|
// user, which is a much worse failure mode than a missed nudge.
|
|
585
|
-
|
|
586
|
-
if (lines.some((l) => l.endsWith("?")))
|
|
609
|
+
if (/\?(?:\s|$)/.test(text))
|
|
587
610
|
return false;
|
|
588
611
|
// Must contain a commit-intent phrase — this is the stall we care about.
|
|
589
612
|
if (!COMMIT_INTENT_RE.test(text))
|
|
@@ -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.
|
|
@@ -60,45 +59,16 @@ function taskSummaryForSlicePlan(description) {
|
|
|
60
59
|
return firstBlock || beforeHeading;
|
|
61
60
|
}
|
|
62
61
|
/**
|
|
63
|
-
* Load artifact content from DB
|
|
64
|
-
*
|
|
65
|
-
*
|
|
62
|
+
* Load artifact content from the DB. Markdown projections are not authoritative
|
|
63
|
+
* during runtime; when the artifact row is missing, callers regenerate from DB
|
|
64
|
+
* rows instead of patching disk fallback content and storing it back.
|
|
66
65
|
*/
|
|
67
|
-
function loadArtifactContent(artifactPath
|
|
68
|
-
// Try DB first
|
|
66
|
+
function loadArtifactContent(artifactPath) {
|
|
69
67
|
const artifact = getArtifact(artifactPath);
|
|
70
68
|
if (artifact && artifact.full_content) {
|
|
71
69
|
return artifact.full_content;
|
|
72
70
|
}
|
|
73
|
-
|
|
74
|
-
if (!absPath) {
|
|
75
|
-
process.stderr.write(`markdown-renderer: artifact not found in DB or on disk: ${artifactPath}\n`);
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
let content;
|
|
79
|
-
try {
|
|
80
|
-
content = readFileSync(absPath, "utf-8");
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
logWarning("renderer", `cannot read file from disk: ${absPath}`);
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
// Store in DB for future use (graceful degradation path)
|
|
87
|
-
try {
|
|
88
|
-
insertArtifact({
|
|
89
|
-
path: artifactPath,
|
|
90
|
-
artifact_type: opts.artifact_type,
|
|
91
|
-
milestone_id: opts.milestone_id,
|
|
92
|
-
slice_id: opts.slice_id ?? null,
|
|
93
|
-
task_id: opts.task_id ?? null,
|
|
94
|
-
full_content: content,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
// Non-fatal: we have the content, DB storage is best-effort
|
|
99
|
-
logWarning("renderer", `failed to store disk fallback in DB: ${artifactPath}`);
|
|
100
|
-
}
|
|
101
|
-
return content;
|
|
71
|
+
return null;
|
|
102
72
|
}
|
|
103
73
|
/**
|
|
104
74
|
* Write rendered content to disk and update the artifacts table.
|
|
@@ -401,17 +371,14 @@ export async function renderRoadmapCheckboxes(basePath, milestoneId) {
|
|
|
401
371
|
}
|
|
402
372
|
const absPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
403
373
|
const artifactPath = absPath ? toArtifactPath(absPath, basePath) : null;
|
|
404
|
-
// Load content from DB
|
|
374
|
+
// Load content from DB; regenerate from DB rows when the artifact is absent.
|
|
405
375
|
let content = null;
|
|
406
376
|
if (artifactPath) {
|
|
407
|
-
content = loadArtifactContent(artifactPath
|
|
408
|
-
artifact_type: "ROADMAP",
|
|
409
|
-
milestone_id: milestoneId,
|
|
410
|
-
});
|
|
377
|
+
content = loadArtifactContent(artifactPath);
|
|
411
378
|
}
|
|
412
379
|
if (!content) {
|
|
413
|
-
|
|
414
|
-
return
|
|
380
|
+
await renderRoadmapFromDb(basePath, milestoneId);
|
|
381
|
+
return true;
|
|
415
382
|
}
|
|
416
383
|
// Apply checkbox patches for each slice
|
|
417
384
|
let updated = content;
|
|
@@ -454,15 +421,11 @@ export async function renderPlanCheckboxes(basePath, milestoneId, sliceId) {
|
|
|
454
421
|
const artifactPath = absPath ? toArtifactPath(absPath, basePath) : null;
|
|
455
422
|
let content = null;
|
|
456
423
|
if (artifactPath) {
|
|
457
|
-
content = loadArtifactContent(artifactPath
|
|
458
|
-
artifact_type: "PLAN",
|
|
459
|
-
milestone_id: milestoneId,
|
|
460
|
-
slice_id: sliceId,
|
|
461
|
-
});
|
|
424
|
+
content = loadArtifactContent(artifactPath);
|
|
462
425
|
}
|
|
463
426
|
if (!content) {
|
|
464
|
-
|
|
465
|
-
return
|
|
427
|
+
await renderPlanFromDb(basePath, milestoneId, sliceId);
|
|
428
|
+
return true;
|
|
466
429
|
}
|
|
467
430
|
// Apply checkbox patches for each task
|
|
468
431
|
let updated = content;
|