@opengsd/gsd-pi 1.9.0-dev.5c1a3ff2 → 1.9.0-dev.d2070876
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/headless.js +2 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +24 -4
- package/dist/resources/extensions/gsd/auto/run-unit.js +19 -1
- package/dist/resources/extensions/gsd/auto/unit-phase.js +37 -5
- package/dist/resources/extensions/gsd/auto-dispatch.js +2 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +12 -4
- package/dist/resources/extensions/gsd/auto-runtime-state.js +5 -5
- package/dist/resources/extensions/gsd/auto-start.js +29 -9
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +40 -0
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +7 -3
- package/dist/resources/extensions/gsd/auto.js +15 -5
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +2 -2
- package/dist/resources/extensions/gsd/commands-verdict.js +10 -9
- package/dist/resources/extensions/gsd/compat/compat-marker.js +80 -5
- package/dist/resources/extensions/gsd/db/engine.js +5 -2
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +21 -2
- package/dist/resources/extensions/gsd/error-classifier.js +12 -2
- package/dist/resources/extensions/gsd/exec-sandbox.js +1 -0
- package/dist/resources/extensions/gsd/flat-phase-migration.js +12 -0
- package/dist/resources/extensions/gsd/gsd-db.js +75 -31
- package/dist/resources/extensions/gsd/guided-flow.js +2 -2
- package/dist/resources/extensions/gsd/markdown-renderer.js +3 -9
- package/dist/resources/extensions/gsd/paths.js +17 -7
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +5 -5
- package/dist/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.js +98 -2
- package/dist/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.js +4 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +5 -2
- package/dist/resources/extensions/gsd/stop-notice.js +9 -0
- package/dist/resources/extensions/gsd/worktree-state-projection.js +20 -18
- package/dist/resources/extensions/shared/interview-ui.js +43 -6
- 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 +11 -11
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
- package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +2 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/README.md +9 -3
- package/packages/gsd-cloud/dist/background-cli.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/background-cli.test.js +248 -0
- package/packages/gsd-cloud/dist/background-cli.test.js.map +1 -0
- package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cli.js +121 -15
- package/packages/gsd-cloud/dist/cli.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.js +7 -1
- package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts +1 -3
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.js +9 -7
- package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
- package/packages/gsd-cloud/dist/runtime-process.d.ts +27 -0
- package/packages/gsd-cloud/dist/runtime-process.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.js +272 -0
- package/packages/gsd-cloud/dist/runtime-process.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js +97 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js.map +1 -0
- package/packages/gsd-cloud/package.json +2 -2
- package/packages/mcp-server/dist/cli-runner.d.ts +3 -0
- package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
- package/packages/mcp-server/dist/cli-runner.js +119 -33
- package/packages/mcp-server/dist/cli-runner.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +17 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +17 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +103 -0
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +54 -0
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop.ts +48 -4
- package/src/resources/extensions/gsd/auto/run-unit.ts +20 -1
- package/src/resources/extensions/gsd/auto/unit-phase.ts +41 -5
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +14 -4
- package/src/resources/extensions/gsd/auto-runtime-state.ts +12 -7
- package/src/resources/extensions/gsd/auto-start.ts +31 -8
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +56 -0
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +10 -3
- package/src/resources/extensions/gsd/auto.ts +20 -7
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +2 -2
- package/src/resources/extensions/gsd/commands-verdict.ts +16 -9
- package/src/resources/extensions/gsd/compat/compat-marker.ts +99 -5
- package/src/resources/extensions/gsd/db/engine.ts +5 -2
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +27 -2
- package/src/resources/extensions/gsd/error-classifier.ts +13 -2
- package/src/resources/extensions/gsd/exec-sandbox.ts +1 -0
- package/src/resources/extensions/gsd/flat-phase-migration.ts +14 -0
- package/src/resources/extensions/gsd/gsd-db.ts +85 -33
- package/src/resources/extensions/gsd/guided-flow.ts +2 -2
- package/src/resources/extensions/gsd/markdown-renderer.ts +3 -9
- package/src/resources/extensions/gsd/paths.ts +17 -7
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +5 -5
- package/src/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.ts +132 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.ts +4 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +4 -2
- package/src/resources/extensions/gsd/stop-notice.ts +11 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +264 -0
- package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +74 -1
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/compat-marker.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/db-engine-cjs-compat.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/external-markdown-edit.test.ts +38 -1
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +8 -9
- package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +20 -1
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +121 -1
- package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/stop-notice.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +60 -0
- package/src/resources/extensions/gsd/worktree-state-projection.ts +21 -24
- package/src/resources/extensions/shared/interview-ui.ts +47 -8
- package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +29 -0
- /package/dist/web/standalone/.next/static/{6YWV7VAPg-jqjfPswrKZb → sggX-h41OjePo-FjdArP5}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{6YWV7VAPg-jqjfPswrKZb → sggX-h41OjePo-FjdArP5}/_ssgManifest.js +0 -0
|
@@ -67,6 +67,13 @@ export type { SliceRow, TaskRow } from "./db-task-slice-rows.js";
|
|
|
67
67
|
|
|
68
68
|
import { TERMINAL_STATUS_SQL } from "./db/sql-constants.js";
|
|
69
69
|
import { applyStatusTransition } from "./db/writers/status.js";
|
|
70
|
+
import {
|
|
71
|
+
LAYOUT_SEGMENTS,
|
|
72
|
+
derivePhaseSlug,
|
|
73
|
+
milestoneIdToPhaseNum,
|
|
74
|
+
milestoneIdUniqueSuffix,
|
|
75
|
+
phaseDirName,
|
|
76
|
+
} from "./layout-policy.js";
|
|
70
77
|
|
|
71
78
|
export function insertDecision(d: Omit<Decision, "seq">): void {
|
|
72
79
|
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
@@ -206,6 +213,47 @@ export function insertArtifact(a: {
|
|
|
206
213
|
});
|
|
207
214
|
}
|
|
208
215
|
|
|
216
|
+
function canonicalPhaseDirNameForDb(milestoneId: string, title: string): string {
|
|
217
|
+
const phaseNum = milestoneIdToPhaseNum(milestoneId);
|
|
218
|
+
const slug = derivePhaseSlug(title || milestoneId);
|
|
219
|
+
const suffix = milestoneIdUniqueSuffix(milestoneId);
|
|
220
|
+
return phaseDirName(phaseNum, suffix ? `${suffix}-${slug}` : slug);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function reconcileMilestonePhaseArtifactPaths(milestoneId: string, title: string): void {
|
|
224
|
+
const db = getDbOrNull()!;
|
|
225
|
+
const phaseNumPrefix = String(milestoneIdToPhaseNum(milestoneId)).padStart(2, "0");
|
|
226
|
+
const canonicalDir = canonicalPhaseDirNameForDb(milestoneId, title);
|
|
227
|
+
const staleRows = db.prepare(
|
|
228
|
+
`SELECT path
|
|
229
|
+
FROM artifacts
|
|
230
|
+
WHERE milestone_id = :milestone_id
|
|
231
|
+
AND path LIKE :phase_prefix`,
|
|
232
|
+
).all({
|
|
233
|
+
":milestone_id": milestoneId,
|
|
234
|
+
":phase_prefix": `${LAYOUT_SEGMENTS.level1}/${phaseNumPrefix}-%/%`,
|
|
235
|
+
}) as Array<{ path: string }>;
|
|
236
|
+
|
|
237
|
+
const updatePath = db.prepare("UPDATE artifacts SET path = :new_path WHERE path = :old_path");
|
|
238
|
+
const deletePath = db.prepare("DELETE FROM artifacts WHERE path = :path");
|
|
239
|
+
const existingPath = db.prepare("SELECT 1 AS present FROM artifacts WHERE path = :path");
|
|
240
|
+
|
|
241
|
+
for (const row of staleRows) {
|
|
242
|
+
const parts = row.path.split("/");
|
|
243
|
+
if (parts.length < 3) continue;
|
|
244
|
+
if (parts[0] !== LAYOUT_SEGMENTS.level1) continue;
|
|
245
|
+
if (parts[1] === canonicalDir) continue;
|
|
246
|
+
if (!parts[1]?.startsWith(`${phaseNumPrefix}-`)) continue;
|
|
247
|
+
|
|
248
|
+
const newPath = [LAYOUT_SEGMENTS.level1, canonicalDir, ...parts.slice(2)].join("/");
|
|
249
|
+
if (existingPath.get({ ":path": newPath })) {
|
|
250
|
+
deletePath.run({ ":path": row.path });
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
updatePath.run({ ":new_path": newPath, ":old_path": row.path });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
209
257
|
export interface MilestonePlanningRecord {
|
|
210
258
|
vision: string;
|
|
211
259
|
successCriteria: string[];
|
|
@@ -287,39 +335,43 @@ export function insertMilestone(m: {
|
|
|
287
335
|
|
|
288
336
|
export function upsertMilestonePlanning(milestoneId: string, planning: Partial<MilestonePlanningRecord> & { title?: string; status?: string; depends_on?: string[] }): void {
|
|
289
337
|
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
338
|
+
transaction(() => {
|
|
339
|
+
getDbOrNull()!.prepare(
|
|
340
|
+
`UPDATE milestones SET
|
|
341
|
+
title = COALESCE(NULLIF(:title, ''), title),
|
|
342
|
+
status = COALESCE(NULLIF(:status, ''), status),
|
|
343
|
+
depends_on = COALESCE(:depends_on, depends_on),
|
|
344
|
+
vision = COALESCE(:vision, vision),
|
|
345
|
+
success_criteria = COALESCE(:success_criteria, success_criteria),
|
|
346
|
+
key_risks = COALESCE(:key_risks, key_risks),
|
|
347
|
+
proof_strategy = COALESCE(:proof_strategy, proof_strategy),
|
|
348
|
+
verification_contract = COALESCE(:verification_contract, verification_contract),
|
|
349
|
+
verification_integration = COALESCE(:verification_integration, verification_integration),
|
|
350
|
+
verification_operational = COALESCE(:verification_operational, verification_operational),
|
|
351
|
+
verification_uat = COALESCE(:verification_uat, verification_uat),
|
|
352
|
+
definition_of_done = COALESCE(:definition_of_done, definition_of_done),
|
|
353
|
+
requirement_coverage = COALESCE(:requirement_coverage, requirement_coverage),
|
|
354
|
+
boundary_map_markdown = COALESCE(:boundary_map_markdown, boundary_map_markdown)
|
|
355
|
+
WHERE id = :id`,
|
|
356
|
+
).run({
|
|
357
|
+
":id": milestoneId,
|
|
358
|
+
":title": planning.title ?? "",
|
|
359
|
+
":status": planning.status ?? "",
|
|
360
|
+
":depends_on": planning.depends_on ? JSON.stringify(planning.depends_on) : null,
|
|
361
|
+
":vision": planning.vision ?? null,
|
|
362
|
+
":success_criteria": planning.successCriteria ? JSON.stringify(planning.successCriteria) : null,
|
|
363
|
+
":key_risks": planning.keyRisks ? JSON.stringify(planning.keyRisks) : null,
|
|
364
|
+
":proof_strategy": planning.proofStrategy ? JSON.stringify(planning.proofStrategy) : null,
|
|
365
|
+
":verification_contract": planning.verificationContract ?? null,
|
|
366
|
+
":verification_integration": planning.verificationIntegration ?? null,
|
|
367
|
+
":verification_operational": planning.verificationOperational ?? null,
|
|
368
|
+
":verification_uat": planning.verificationUat ?? null,
|
|
369
|
+
":definition_of_done": planning.definitionOfDone ? JSON.stringify(planning.definitionOfDone) : null,
|
|
370
|
+
":requirement_coverage": planning.requirementCoverage ?? null,
|
|
371
|
+
":boundary_map_markdown": planning.boundaryMapMarkdown ?? null,
|
|
372
|
+
});
|
|
373
|
+
const finalTitle = planning.title?.trim();
|
|
374
|
+
if (finalTitle) reconcileMilestonePhaseArtifactPaths(milestoneId, finalTitle);
|
|
323
375
|
});
|
|
324
376
|
}
|
|
325
377
|
|
|
@@ -1282,7 +1282,7 @@ export async function buildDiscussSlicePrompt(
|
|
|
1282
1282
|
: "";
|
|
1283
1283
|
|
|
1284
1284
|
const inlinedTemplates = inlineTemplate("slice-context", "Slice Context");
|
|
1285
|
-
return loadPrompt("guided-discuss-slice", {
|
|
1285
|
+
return prependLanguageDirective(base, loadPrompt("guided-discuss-slice", {
|
|
1286
1286
|
milestoneId: mid,
|
|
1287
1287
|
sliceId: sid,
|
|
1288
1288
|
sliceTitle: sTitle,
|
|
@@ -1293,7 +1293,7 @@ export async function buildDiscussSlicePrompt(
|
|
|
1293
1293
|
inlinedTemplates,
|
|
1294
1294
|
structuredQuestionsAvailable: options?.structuredQuestionsAvailable ?? "false",
|
|
1295
1295
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}/${sid}): slice context from discuss`),
|
|
1296
|
-
});
|
|
1296
|
+
}));
|
|
1297
1297
|
}
|
|
1298
1298
|
|
|
1299
1299
|
/**
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { readFileSync, existsSync, mkdirSync, statSync, unlinkSync } from "node:fs";
|
|
13
13
|
import { logWarning } from "./workflow-logger.js";
|
|
14
14
|
import { isClosedStatus } from "./status-guards.js";
|
|
15
|
-
import { dirname, join
|
|
15
|
+
import { dirname, join } from "node:path";
|
|
16
16
|
import {
|
|
17
17
|
getAllMilestones,
|
|
18
18
|
getMilestone,
|
|
@@ -50,7 +50,7 @@ import { saveFile, clearParseCache, registerCacheClearCallback } from "./files.j
|
|
|
50
50
|
import { parseRoadmap, parsePlan } from "./parsers-legacy.js";
|
|
51
51
|
import { invalidateStateCache } from "./state.js";
|
|
52
52
|
import { clearPathCache, milestonesDir, legacyMilestonesDir, isLegacyMilestonesLayout, resolveMilestonePath, relSliceFile, canonicalPhaseDirName } from "./paths.js";
|
|
53
|
-
import { readCompatMarker, writeCompatMarker, computeProjectionSha } from "./compat/compat-marker.js";
|
|
53
|
+
import { readCompatMarker, writeCompatMarker, computeProjectionSha, deriveCompatProjectionKey } from "./compat/compat-marker.js";
|
|
54
54
|
import type { RiskLevel } from "./types.js";
|
|
55
55
|
import {
|
|
56
56
|
phaseDirName,
|
|
@@ -109,13 +109,7 @@ function flushProjectionWritesToMarker(): void {
|
|
|
109
109
|
*/
|
|
110
110
|
function toArtifactPath(absPath: string, basePath: string): string {
|
|
111
111
|
const projectionRoot = gsdProjectionRoot(basePath);
|
|
112
|
-
|
|
113
|
-
const root = projectionRel && !projectionRel.startsWith("..") && !projectionRel.startsWith("/")
|
|
114
|
-
? projectionRoot
|
|
115
|
-
: gsdRoot(basePath);
|
|
116
|
-
const rel = relative(root, absPath);
|
|
117
|
-
// Normalize to forward slashes for consistent DB keys
|
|
118
|
-
return rel.replace(/\\/g, "/");
|
|
112
|
+
return deriveCompatProjectionKey(absPath, [projectionRoot, gsdRoot(basePath)]);
|
|
119
113
|
}
|
|
120
114
|
|
|
121
115
|
/**
|
|
@@ -136,6 +136,14 @@ function cachedReaddir(dirPath: string): string[] {
|
|
|
136
136
|
return entries;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
function isExistingFile(path: string): boolean {
|
|
140
|
+
try {
|
|
141
|
+
return statSync(path).isFile();
|
|
142
|
+
} catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
139
147
|
/**
|
|
140
148
|
* Clear the volatile directory listing caches.
|
|
141
149
|
* Call after milestone transitions, file creation in planning directories,
|
|
@@ -284,7 +292,7 @@ export function resolveFile(dir: string, idPrefix: string, suffix: string): stri
|
|
|
284
292
|
if (!existsSync(dir)) return null;
|
|
285
293
|
const target = `${idPrefix}-${suffix}.md`.toUpperCase();
|
|
286
294
|
try {
|
|
287
|
-
const entries =
|
|
295
|
+
const entries = cachedReaddirWithTypes(dir).filter(e => e.isFile()).map(e => e.name);
|
|
288
296
|
// Direct match: ID-SUFFIX.md
|
|
289
297
|
const direct = entries.find(e => e.toUpperCase() === target);
|
|
290
298
|
if (direct) return direct;
|
|
@@ -311,7 +319,9 @@ export function resolveFile(dir: string, idPrefix: string, suffix: string): stri
|
|
|
311
319
|
export function resolveTaskFiles(tasksDir: string, suffix: string): string[] {
|
|
312
320
|
if (!existsSync(tasksDir)) return [];
|
|
313
321
|
try {
|
|
314
|
-
return
|
|
322
|
+
return cachedReaddirWithTypes(tasksDir)
|
|
323
|
+
.filter(e => e.isFile())
|
|
324
|
+
.map(e => e.name)
|
|
315
325
|
.filter(f => taskIdFromTaskFileName(f, suffix) !== null)
|
|
316
326
|
.sort();
|
|
317
327
|
} catch {
|
|
@@ -860,7 +870,7 @@ export function resolveMilestoneFile(
|
|
|
860
870
|
const prefix = `${String(phaseNum).padStart(2, "0")}`;
|
|
861
871
|
const flatName = `${prefix}-${suffix}.md`;
|
|
862
872
|
const flatPath = join(mDir, flatName);
|
|
863
|
-
if (
|
|
873
|
+
if (isExistingFile(flatPath)) return flatPath;
|
|
864
874
|
// Legacy fallback: M001-SUFFIX.md
|
|
865
875
|
const file = resolveFile(mDir, milestoneId, suffix);
|
|
866
876
|
return file ? join(mDir, file) : null;
|
|
@@ -895,11 +905,11 @@ export function resolveSliceFile(
|
|
|
895
905
|
const planNum = sliceIdToPlanNum(sliceId);
|
|
896
906
|
const flatName = planFileName(phaseNum, planNum, suffix);
|
|
897
907
|
const flatPath = join(phaseDir, flatName);
|
|
898
|
-
if (
|
|
908
|
+
if (isExistingFile(flatPath)) return flatPath;
|
|
899
909
|
// Also check plan-number-only format MM-SUFFIX.md (written by buildSliceFileName)
|
|
900
910
|
const planOnlyName = `${String(planNum).padStart(2, "0")}-${suffix}.md`;
|
|
901
911
|
const planOnlyPath = join(phaseDir, planOnlyName);
|
|
902
|
-
if (
|
|
912
|
+
if (isExistingFile(planOnlyPath)) return planOnlyPath;
|
|
903
913
|
// Try prefix match for the phase+plan number (handles suffix variations)
|
|
904
914
|
const planPrefix = `${String(phaseNum).padStart(2, "0")}-${String(planNum).padStart(2, "0")}-`;
|
|
905
915
|
try {
|
|
@@ -949,9 +959,9 @@ export function resolveTaskFile(
|
|
|
949
959
|
|
|
950
960
|
if (suffix !== "PLAN" && !isLegacy) {
|
|
951
961
|
const flatPath = join(phaseDir, buildFlatTaskFileName(sliceId, taskId, suffix));
|
|
952
|
-
if (
|
|
962
|
+
if (isExistingFile(flatPath)) return flatPath;
|
|
953
963
|
const legacyFlatPath = join(phaseDir, buildTaskFileName(taskId, suffix));
|
|
954
|
-
return
|
|
964
|
+
return isExistingFile(legacyFlatPath) ? legacyFlatPath : null;
|
|
955
965
|
}
|
|
956
966
|
|
|
957
967
|
const tDir = resolveTasksDir(basePath, milestoneId, sliceId);
|
|
@@ -24,18 +24,18 @@ Roadmap, slice summaries, assessments, requirements, decisions, and project cont
|
|
|
24
24
|
|
|
25
25
|
### Step 1 - Dispatch Parallel Reviewers
|
|
26
26
|
|
|
27
|
-
The Inlined Context above already preloads the evidence reviewers need — the roadmap, per-slice SUMMARY/ASSESSMENT excerpts, requirements, and verification classes. **Embed the relevant preloaded evidence directly into each reviewer's task prompt** so reviewers work from it instead of re-reading the same artifacts from disk. Each reviewer should read a full file only when its excerpt is missing, truncated, or internally inconsistent — never as a routine first step. This avoids three reviewers independently re-surveying artifacts the orchestrator already holds.
|
|
27
|
+
The Inlined Context above already preloads the evidence reviewers need — the roadmap, per-slice SUMMARY/ASSESSMENT excerpts, requirements, and verification classes. **Embed the relevant preloaded evidence directly into each reviewer's task prompt** so reviewers work from it instead of re-reading the same artifacts from disk. Each reviewer should read a full file only when its excerpt is missing, truncated, or internally inconsistent — never as a routine first step. When a reviewer needs a full artifact, use only explicit file paths from the On-demand Validation Artifacts / On-demand Milestone Context blocks or discover files with `find .gsd -type f`; never pass a phase, slice, `tasks/`, or `slices/` directory to `read` or `readFileSync`. This avoids three reviewers independently re-surveying artifacts the orchestrator already holds.
|
|
28
28
|
|
|
29
29
|
Call `subagent` with `tasks: [...]` containing ALL THREE reviewers simultaneously:
|
|
30
30
|
|
|
31
31
|
**Reviewer A - Requirements Coverage**
|
|
32
|
-
Prompt: "Review milestone {{milestoneId}} requirements coverage. Working directory: {{workingDirectory}}. Use the preloaded requirements and slice SUMMARY evidence embedded in this task — do not re-read them from disk. For each requirement, mark COVERED, PARTIAL, or MISSING against that evidence. Read a full SUMMARY
|
|
32
|
+
Prompt: "Review milestone {{milestoneId}} requirements coverage. Working directory: {{workingDirectory}}. Use the preloaded requirements and slice SUMMARY evidence embedded in this task — do not re-read them from disk. For each requirement, mark COVERED, PARTIAL, or MISSING against that evidence. Read a full SUMMARY only from an explicit file path in the On-demand Validation Artifacts list, or discover candidates with `find .gsd -type f -name '*-SUMMARY.md'`; never read a directory path. Output table: Requirement | Status | Evidence. End with one-line verdict: PASS if all covered, NEEDS-ATTENTION if partials exist, FAIL if any missing."
|
|
33
33
|
|
|
34
34
|
**Reviewer B - Cross-Slice Integration**
|
|
35
|
-
Prompt: "Review milestone {{milestoneId}} cross-slice integration. Working directory: {{workingDirectory}}. Use the preloaded roadmap and slice SUMMARY evidence embedded in this task — do not re-read them from disk. Find the boundary map (produces/consumes contracts) in the roadmap. For each boundary, confirm producer SUMMARY produced the artifact and consumer SUMMARY consumed it. Read `{{roadmapPath}}` or a full SUMMARY only if the preloaded evidence is missing, truncated, or inconsistent. Output table: Boundary | Producer Summary | Consumer Summary | Status. End with one-line verdict: PASS if all boundaries honored, NEEDS-ATTENTION if any gaps."
|
|
35
|
+
Prompt: "Review milestone {{milestoneId}} cross-slice integration. Working directory: {{workingDirectory}}. Use the preloaded roadmap and slice SUMMARY evidence embedded in this task — do not re-read them from disk. Find the boundary map (produces/consumes contracts) in the roadmap. For each boundary, confirm producer SUMMARY produced the artifact and consumer SUMMARY consumed it. Read `{{roadmapPath}}` or a full SUMMARY from the On-demand Validation Artifacts list only if the preloaded evidence is missing, truncated, or inconsistent; never read a directory path. Output table: Boundary | Producer Summary | Consumer Summary | Status. End with one-line verdict: PASS if all boundaries honored, NEEDS-ATTENTION if any gaps."
|
|
36
36
|
|
|
37
37
|
**Reviewer C - Assessment & Acceptance Criteria**
|
|
38
|
-
Prompt: "Review milestone {{milestoneId}} assessment evidence and acceptance criteria. Working directory: {{workingDirectory}}. Use the preloaded milestone context, slice SUMMARY, and ASSESSMENT evidence embedded in this task — do not re-read them from disk. Read
|
|
38
|
+
Prompt: "Review milestone {{milestoneId}} assessment evidence and acceptance criteria. Working directory: {{workingDirectory}}. Use the preloaded milestone context, slice SUMMARY, and ASSESSMENT evidence embedded in this task — do not re-read them from disk. Read the milestone context from the On-demand Milestone Context file path, and read full SUMMARY or ASSESSMENT files only from explicit paths in the On-demand Validation Artifacts list, if the preloaded excerpt is missing, truncated, or inconsistent. UAT files are specs, not evidence. Verify each criterion maps to passing evidence. Then review the inlined `Verification Classes (from planning)` table. For every planned row in that table, output a `Verification Classes` table with columns `Class | Planned Check | Evidence | Verdict`. Preserve every planned non-empty class row; do not summarize, rename, combine, or omit planned classes. The first cell of each row must be exactly `Contract`, `Integration`, `Operational`, or `UAT` when that class is present in planning. If a planned class lacks evidence, still include its canonical row and mark the verdict NEEDS-ATTENTION or FAIL. If a planned browser/UAT class has no ASSESSMENT with browser/runtime actions and assertions, return NEEDS-ATTENTION. If no verification classes were planned, say that explicitly. Output sections `Acceptance Criteria` with checklist `[ ] Criterion | Evidence`, and `Verification Classes` with the table. End with one-line verdict: PASS if all criteria and classes are covered by evidence, NEEDS-ATTENTION if gaps exist."
|
|
39
39
|
|
|
40
40
|
### Step 2 - Synthesize Findings
|
|
41
41
|
|
|
@@ -85,6 +85,6 @@ If verdict is `needs-remediation`:
|
|
|
85
85
|
|
|
86
86
|
**You MUST call `gsd_validate_milestone` before finishing. Do not manually write `{{validationPath}}`.**
|
|
87
87
|
|
|
88
|
-
**File system safety:** When scanning milestone directories, use `
|
|
88
|
+
**File system safety:** When scanning milestone directories, use `find .gsd -type f` first and read only files. In flat-phase projects, `.gsd/phases/<NN>-<slug>/` is a directory, not an artifact. Never pass a directory path such as a phase directory, `tasks/`, or `slices/` directly to `read`, `readFile`, or `readFileSync`.
|
|
89
89
|
|
|
90
90
|
When done, say: "Milestone {{milestoneId}} validation complete — verdict: <verdict>." Say this exactly once — if you already said it in a prior message, do not repeat it.
|
|
@@ -9,13 +9,15 @@
|
|
|
9
9
|
// refreshes the marker entry, closing the loop.
|
|
10
10
|
|
|
11
11
|
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
-
import { join } from "node:path";
|
|
12
|
+
import { basename, join } from "node:path";
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
computeProjectionSha,
|
|
16
16
|
readCompatMarker,
|
|
17
17
|
writeCompatMarker,
|
|
18
|
+
type ProjectionEntry,
|
|
18
19
|
} from "../../compat/compat-marker.js";
|
|
20
|
+
import { _getAdapter } from "../../gsd-db.js";
|
|
19
21
|
import { logWarning } from "../../workflow-logger.js";
|
|
20
22
|
import type { GSDState } from "../../types.js";
|
|
21
23
|
import type { DriftContext, DriftHandler, DriftRecord } from "../types.js";
|
|
@@ -25,6 +27,102 @@ type ExternalMarkdownEditDrift = Extract<
|
|
|
25
27
|
{ kind: "external-markdown-edit" }
|
|
26
28
|
>;
|
|
27
29
|
|
|
30
|
+
type ArtifactContentRow = {
|
|
31
|
+
full_content: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type ProjectionScope = {
|
|
35
|
+
milestoneId: string;
|
|
36
|
+
sliceId: string | null;
|
|
37
|
+
taskId: string | null;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function deepestProjectionScope(entities: readonly string[]): ProjectionScope | null {
|
|
41
|
+
let best: string[] | null = null;
|
|
42
|
+
for (const entity of entities) {
|
|
43
|
+
const parts = entity.split("/").filter(Boolean);
|
|
44
|
+
if (parts.length === 0) continue;
|
|
45
|
+
if (!best || parts.length > best.length) best = parts;
|
|
46
|
+
}
|
|
47
|
+
if (!best) return null;
|
|
48
|
+
return {
|
|
49
|
+
milestoneId: best[0]!,
|
|
50
|
+
sliceId: best[1] ?? null,
|
|
51
|
+
taskId: best[2] ?? null,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function artifactTypeFromProjectionPath(projectionPath: string): string | null {
|
|
56
|
+
const name = basename(projectionPath);
|
|
57
|
+
const match = name.match(/^(?:[A-Za-z]\d+|\d+(?:-\d+)?)-(.+)\.md$/);
|
|
58
|
+
return match?.[1]?.toUpperCase() ?? null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function dbProjectionMatches(
|
|
62
|
+
projectionPath: string,
|
|
63
|
+
entry: ProjectionEntry,
|
|
64
|
+
actualSha: string,
|
|
65
|
+
): boolean {
|
|
66
|
+
const adapter = _getAdapter();
|
|
67
|
+
if (!adapter) return false;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const rows: ArtifactContentRow[] = adapter
|
|
71
|
+
.prepare("SELECT full_content FROM artifacts WHERE path = :path")
|
|
72
|
+
.all({ ":path": projectionPath }) as ArtifactContentRow[];
|
|
73
|
+
|
|
74
|
+
const scope = deepestProjectionScope(entry.entities);
|
|
75
|
+
const artifactType = artifactTypeFromProjectionPath(projectionPath);
|
|
76
|
+
if (scope?.taskId) {
|
|
77
|
+
rows.push(
|
|
78
|
+
...(adapter
|
|
79
|
+
.prepare(
|
|
80
|
+
`SELECT full_content
|
|
81
|
+
FROM artifacts
|
|
82
|
+
WHERE milestone_id = :mid AND slice_id = :sid AND task_id = :tid
|
|
83
|
+
AND (:type IS NULL OR artifact_type = :type)`,
|
|
84
|
+
)
|
|
85
|
+
.all({
|
|
86
|
+
":mid": scope.milestoneId,
|
|
87
|
+
":sid": scope.sliceId,
|
|
88
|
+
":tid": scope.taskId,
|
|
89
|
+
":type": artifactType,
|
|
90
|
+
}) as ArtifactContentRow[]),
|
|
91
|
+
);
|
|
92
|
+
} else if (scope?.sliceId) {
|
|
93
|
+
rows.push(
|
|
94
|
+
...(adapter
|
|
95
|
+
.prepare(
|
|
96
|
+
`SELECT full_content
|
|
97
|
+
FROM artifacts
|
|
98
|
+
WHERE milestone_id = :mid AND slice_id = :sid AND task_id IS NULL
|
|
99
|
+
AND (:type IS NULL OR artifact_type = :type)`,
|
|
100
|
+
)
|
|
101
|
+
.all({
|
|
102
|
+
":mid": scope.milestoneId,
|
|
103
|
+
":sid": scope.sliceId,
|
|
104
|
+
":type": artifactType,
|
|
105
|
+
}) as ArtifactContentRow[]),
|
|
106
|
+
);
|
|
107
|
+
} else if (scope) {
|
|
108
|
+
rows.push(
|
|
109
|
+
...(adapter
|
|
110
|
+
.prepare(
|
|
111
|
+
`SELECT full_content
|
|
112
|
+
FROM artifacts
|
|
113
|
+
WHERE milestone_id = :mid AND slice_id IS NULL AND task_id IS NULL
|
|
114
|
+
AND (:type IS NULL OR artifact_type = :type)`,
|
|
115
|
+
)
|
|
116
|
+
.all({ ":mid": scope.milestoneId, ":type": artifactType }) as ArtifactContentRow[]),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return rows.some((row) => computeProjectionSha(row.full_content) === actualSha);
|
|
121
|
+
} catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
28
126
|
/**
|
|
29
127
|
* Detect sha drift between the marker baseline and current file contents.
|
|
30
128
|
*
|
|
@@ -38,16 +136,43 @@ function detectExternalMarkdownEdit(
|
|
|
38
136
|
_state: GSDState,
|
|
39
137
|
ctx: DriftContext,
|
|
40
138
|
): ExternalMarkdownEditDrift[] {
|
|
41
|
-
const marker = readCompatMarker(ctx.basePath
|
|
139
|
+
const marker = readCompatMarker(ctx.basePath, {
|
|
140
|
+
healInvalidKeys: !ctx.dryRun,
|
|
141
|
+
quarantineInvalid: !ctx.dryRun,
|
|
142
|
+
});
|
|
42
143
|
const entries = Object.entries(marker.projections);
|
|
43
144
|
if (entries.length === 0) return [];
|
|
44
145
|
|
|
45
146
|
const records: ExternalMarkdownEditDrift[] = [];
|
|
147
|
+
let markerChanged = false;
|
|
46
148
|
for (const [projectionPath, entry] of entries) {
|
|
47
149
|
const abs = join(ctx.basePath, ".gsd", projectionPath);
|
|
48
150
|
if (!existsSync(abs)) continue;
|
|
49
151
|
const actual = computeProjectionSha(readFileSync(abs, "utf-8"));
|
|
50
152
|
if (actual === entry.sha) continue;
|
|
153
|
+
if (dbProjectionMatches(projectionPath, entry, actual)) {
|
|
154
|
+
if (!ctx.dryRun) {
|
|
155
|
+
marker.projections[projectionPath] = {
|
|
156
|
+
sha: actual,
|
|
157
|
+
entities: entry.entities,
|
|
158
|
+
};
|
|
159
|
+
markerChanged = true;
|
|
160
|
+
logWarning(
|
|
161
|
+
"reconcile",
|
|
162
|
+
`external-markdown-edit: refreshed stale compat marker for ${projectionPath} (expectedSha=${entry.sha}, actualSha=${actual})`,
|
|
163
|
+
);
|
|
164
|
+
} else {
|
|
165
|
+
logWarning(
|
|
166
|
+
"reconcile",
|
|
167
|
+
`external-markdown-edit: stale compat marker for ${projectionPath} (expectedSha=${entry.sha}, actualSha=${actual})`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
logWarning(
|
|
173
|
+
"reconcile",
|
|
174
|
+
`external-markdown-edit drift for ${projectionPath} (expectedSha=${entry.sha}, actualSha=${actual})`,
|
|
175
|
+
);
|
|
51
176
|
records.push({
|
|
52
177
|
kind: "external-markdown-edit",
|
|
53
178
|
projectionPath,
|
|
@@ -56,6 +181,11 @@ function detectExternalMarkdownEdit(
|
|
|
56
181
|
entities: entry.entities,
|
|
57
182
|
});
|
|
58
183
|
}
|
|
184
|
+
if (markerChanged) {
|
|
185
|
+
marker.lastWriter = "gsd-pi";
|
|
186
|
+
marker.lastProjectedAt = new Date().toISOString();
|
|
187
|
+
writeCompatMarker(ctx.basePath, marker);
|
|
188
|
+
}
|
|
59
189
|
return records;
|
|
60
190
|
}
|
|
61
191
|
|
|
@@ -82,7 +82,10 @@ async function detectExternalPlanningEdit(
|
|
|
82
82
|
_state: GSDState,
|
|
83
83
|
ctx: DriftContext,
|
|
84
84
|
): Promise<ExternalPlanningEditDrift[]> {
|
|
85
|
-
const marker = readCompatMarker(ctx.basePath
|
|
85
|
+
const marker = readCompatMarker(ctx.basePath, {
|
|
86
|
+
healInvalidKeys: !ctx.dryRun,
|
|
87
|
+
quarantineInvalid: !ctx.dryRun,
|
|
88
|
+
});
|
|
86
89
|
if (!marker.planning?.active) {
|
|
87
90
|
// Not yet activated. Activation (layout parse + DB import) is owned by
|
|
88
91
|
// capturePlanningCompatIfNeeded, called from reconcileBeforeDispatch when
|
|
@@ -16,7 +16,7 @@ import { renderRoadmapFromDb } from "../../markdown-renderer.js";
|
|
|
16
16
|
import { findMilestoneIds } from "../../milestone-ids.js";
|
|
17
17
|
import { parseRoadmap } from "../../parsers-legacy.js";
|
|
18
18
|
import { resolveMilestoneFile } from "../../paths.js";
|
|
19
|
-
import { isClosedStatus } from "../../status-guards.js";
|
|
19
|
+
import { isClosedStatus, isSkippedForDispatch } from "../../status-guards.js";
|
|
20
20
|
import type { GSDState } from "../../types.js";
|
|
21
21
|
import type { DriftContext, DriftHandler, DriftRecord } from "../types.js";
|
|
22
22
|
|
|
@@ -94,7 +94,9 @@ export function detectRoadmapDivergenceDrift(
|
|
|
94
94
|
for (const milestoneId of findMilestoneIds(ctx.basePath)) {
|
|
95
95
|
// Skip milestones that don't yet have a DB row — that's the
|
|
96
96
|
// unregistered-milestone drift handler's responsibility.
|
|
97
|
-
|
|
97
|
+
const milestone = getMilestone(milestoneId);
|
|
98
|
+
if (!milestone) continue;
|
|
99
|
+
if (isSkippedForDispatch(milestone.status)) continue;
|
|
98
100
|
if (milestoneHasDivergence(ctx.basePath, milestoneId)) {
|
|
99
101
|
drifts.push({ kind: "roadmap-divergence", milestoneId });
|
|
100
102
|
}
|
|
@@ -29,6 +29,14 @@ export function formatStopNoticePrefix(reason?: string | null): string {
|
|
|
29
29
|
return displayReason ? `${prefix} — ${displayReason}` : prefix;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export function formatVerdictRecordedNotice(message: string): string {
|
|
33
|
+
return `Verdict recorded: ${message}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function formatVerdictRejectedNotice(message: string): string {
|
|
37
|
+
return `Verdict rejected: ${message}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
// ─── Classification (headless host side) ────────────────────────────────
|
|
33
41
|
// The canonical lowercase prefixes the headless event loop recognizes in
|
|
34
42
|
// notify messages. Emitters above and ad-hoc emitters elsewhere must start
|
|
@@ -42,6 +50,8 @@ export const TERMINAL_NOTICE_PREFIXES = [
|
|
|
42
50
|
"auto-mode complete",
|
|
43
51
|
"no active milestone",
|
|
44
52
|
"auto-mode idle",
|
|
53
|
+
"verdict recorded",
|
|
54
|
+
"verdict rejected",
|
|
45
55
|
] as const;
|
|
46
56
|
|
|
47
57
|
/** Manual-resolution notices emitted before auto-mode can formally pause/stop. */
|
|
@@ -85,6 +95,7 @@ export function isInteractiveMenuUnavailableNotice(message: string): boolean {
|
|
|
85
95
|
export function isBlockedNoticeMessage(message: string): boolean {
|
|
86
96
|
return (
|
|
87
97
|
message.includes("blocked:") ||
|
|
98
|
+
message.startsWith("verdict rejected") ||
|
|
88
99
|
(isPauseNotice(message) && !isNonBlockingPauseNotice(message)) ||
|
|
89
100
|
isManualResolutionNotice(message) ||
|
|
90
101
|
isInteractiveMenuUnavailableNotice(message)
|