@opengsd/gsd-pi 1.9.0-dev.5c1a3ff2 → 1.9.0-dev.97ef11e3
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 +10 -10
- 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 +10 -10
- 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/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 → DXD9fZPnqtjZSlT5xdC3v}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{6YWV7VAPg-jqjfPswrKZb → DXD9fZPnqtjZSlT5xdC3v}/_ssgManifest.js +0 -0
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// distinguish gsd-pi's own writes (expected) from external edits made by gsd-core
|
|
6
6
|
// (drift to import). gsd-core is oblivious to this file and ignores it.
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
|
-
import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync, mkdirSync } from "node:fs";
|
|
9
|
-
import { dirname, join, isAbsolute, normalize } from "node:path";
|
|
8
|
+
import { existsSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync, mkdirSync } from "node:fs";
|
|
9
|
+
import { dirname, join, isAbsolute, normalize, relative, resolve } from "node:path";
|
|
10
10
|
/** Current marker schema version. Bump on breaking format changes + migrate. */
|
|
11
11
|
export const COMPAT_MARKER_SCHEMA = 2;
|
|
12
12
|
/** Marker returned when no marker exists yet (fresh project, first gsd-pi run). */
|
|
@@ -32,6 +32,35 @@ export function normalizeForHash(content) {
|
|
|
32
32
|
export function computeProjectionSha(content) {
|
|
33
33
|
return createHash("sha256").update(normalizeForHash(content)).digest("hex").slice(0, 16);
|
|
34
34
|
}
|
|
35
|
+
function normalizeRealPath(p) {
|
|
36
|
+
try {
|
|
37
|
+
return realpathSync.native(p);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return resolve(p);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function rootRelativeKey(rootPath, absPath) {
|
|
44
|
+
const root = normalizeRealPath(rootPath);
|
|
45
|
+
const abs = normalizeRealPath(absPath);
|
|
46
|
+
const rel = relative(root, abs);
|
|
47
|
+
if (!rel || rel === ".." || rel.startsWith(`..${sepForRelative(rel)}`) || isAbsolute(rel)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return rel.replace(/\\/g, "/");
|
|
51
|
+
}
|
|
52
|
+
function sepForRelative(rel) {
|
|
53
|
+
return rel.includes("\\") ? "\\" : "/";
|
|
54
|
+
}
|
|
55
|
+
export function deriveCompatProjectionKey(absPath, roots) {
|
|
56
|
+
for (const root of roots) {
|
|
57
|
+
const key = rootRelativeKey(root, absPath);
|
|
58
|
+
if (key)
|
|
59
|
+
return key;
|
|
60
|
+
}
|
|
61
|
+
const fallbackRoot = roots[roots.length - 1] ?? dirname(absPath);
|
|
62
|
+
return relative(fallbackRoot, absPath).replace(/\\/g, "/");
|
|
63
|
+
}
|
|
35
64
|
/**
|
|
36
65
|
* Read & validate the marker. A missing marker → EMPTY_MARKER (treat every
|
|
37
66
|
* projection as external on next reconcile). A malformed marker is quarantined
|
|
@@ -39,7 +68,9 @@ export function computeProjectionSha(content) {
|
|
|
39
68
|
* EMPTY_MARKER. A schema-mismatch returns EMPTY_MARKER (forward-compat: refuse
|
|
40
69
|
* to act on a future format we don't understand).
|
|
41
70
|
*/
|
|
42
|
-
export function readCompatMarker(basePath) {
|
|
71
|
+
export function readCompatMarker(basePath, options = {}) {
|
|
72
|
+
const healInvalidKeys = options.healInvalidKeys ?? true;
|
|
73
|
+
const quarantineInvalid = options.quarantineInvalid ?? true;
|
|
43
74
|
const path = compatMarkerPath(basePath);
|
|
44
75
|
if (!existsSync(path))
|
|
45
76
|
return emptyMarker();
|
|
@@ -55,11 +86,16 @@ export function readCompatMarker(basePath) {
|
|
|
55
86
|
parsed = JSON.parse(raw);
|
|
56
87
|
}
|
|
57
88
|
catch {
|
|
58
|
-
|
|
89
|
+
if (quarantineInvalid)
|
|
90
|
+
quarantine(basePath, raw);
|
|
59
91
|
return emptyMarker();
|
|
60
92
|
}
|
|
93
|
+
if (healInvalidKeys && healMarkerProjectionKeys(basePath, parsed) && isValidMarker(parsed)) {
|
|
94
|
+
writeCompatMarker(basePath, parsed);
|
|
95
|
+
}
|
|
61
96
|
if (!isValidMarker(parsed)) {
|
|
62
|
-
|
|
97
|
+
if (quarantineInvalid)
|
|
98
|
+
quarantine(basePath, raw);
|
|
63
99
|
return emptyMarker();
|
|
64
100
|
}
|
|
65
101
|
// Promote older markers by defaulting absent fields. Schema 1 → 2 only adds
|
|
@@ -196,6 +232,45 @@ function isValidProjectionMap(x) {
|
|
|
196
232
|
}
|
|
197
233
|
return true;
|
|
198
234
|
}
|
|
235
|
+
function healMarkerProjectionKeys(basePath, marker) {
|
|
236
|
+
if (typeof marker !== "object" || marker === null)
|
|
237
|
+
return false;
|
|
238
|
+
const m = marker;
|
|
239
|
+
let changed = false;
|
|
240
|
+
if (typeof m.projections === "object" && m.projections !== null) {
|
|
241
|
+
changed = healProjectionMapKeys(basePath, ".gsd", m.projections) || changed;
|
|
242
|
+
}
|
|
243
|
+
const planning = m.planning;
|
|
244
|
+
if (typeof planning === "object" && planning !== null) {
|
|
245
|
+
const p = planning;
|
|
246
|
+
if (typeof p.projections === "object" && p.projections !== null) {
|
|
247
|
+
changed = healProjectionMapKeys(basePath, ".planning", p.projections) || changed;
|
|
248
|
+
}
|
|
249
|
+
if (typeof p.passthrough === "object" && p.passthrough !== null) {
|
|
250
|
+
changed = healProjectionMapKeys(basePath, ".planning", p.passthrough) || changed;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return changed;
|
|
254
|
+
}
|
|
255
|
+
function healProjectionMapKeys(basePath, rootName, map) {
|
|
256
|
+
let changed = false;
|
|
257
|
+
const root = join(basePath, rootName);
|
|
258
|
+
for (const key of Object.keys(map)) {
|
|
259
|
+
if (isSafeProjectionKey(key))
|
|
260
|
+
continue;
|
|
261
|
+
if (key.includes("\0") || isAbsolute(key) || /^[A-Za-z]:/.test(key))
|
|
262
|
+
continue;
|
|
263
|
+
const healedKey = rootRelativeKey(root, resolve(root, key));
|
|
264
|
+
if (!healedKey || !isSafeProjectionKey(healedKey))
|
|
265
|
+
continue;
|
|
266
|
+
if (!map[healedKey]) {
|
|
267
|
+
map[healedKey] = map[key];
|
|
268
|
+
}
|
|
269
|
+
delete map[key];
|
|
270
|
+
changed = true;
|
|
271
|
+
}
|
|
272
|
+
return changed;
|
|
273
|
+
}
|
|
199
274
|
function isValidPlanningMarker(x) {
|
|
200
275
|
if (typeof x !== "object" || x === null)
|
|
201
276
|
return false;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// writer layer.
|
|
11
11
|
import { createRequire } from "node:module";
|
|
12
12
|
import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
|
|
13
|
-
import { dirname, join } from "node:path";
|
|
13
|
+
import { dirname, join, resolve } from "node:path";
|
|
14
14
|
import { GSDError, GSD_STALE_STATE } from "../errors.js";
|
|
15
15
|
import { logError, logWarning } from "../workflow-logger.js";
|
|
16
16
|
import { createDbAdapter } from "../db-adapter.js";
|
|
@@ -31,7 +31,10 @@ function getGsdRequire() {
|
|
|
31
31
|
if (_gsdRequire !== undefined)
|
|
32
32
|
return _gsdRequire;
|
|
33
33
|
try {
|
|
34
|
-
|
|
34
|
+
// Next.js may emit this module into a CommonJS chunk. Avoid ESM-only module
|
|
35
|
+
// metadata syntax here; it is a hard parse error there.
|
|
36
|
+
const packageRoot = process.env.GSD_WEB_PACKAGE_ROOT || process.env.GSD_PKG_ROOT || process.cwd();
|
|
37
|
+
_gsdRequire = createRequire(resolve(packageRoot, "package.json"));
|
|
35
38
|
}
|
|
36
39
|
catch {
|
|
37
40
|
_gsdRequire = null;
|
|
@@ -58,6 +58,23 @@ function reportCheckboxDbStatusDivergence(issues, basePath, filePath, scope, uni
|
|
|
58
58
|
fixable: false,
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
|
+
function bareDuplicateMilestoneId(milestoneId) {
|
|
62
|
+
const match = milestoneId.match(/^(M\d{3})-[a-z0-9]{6}$/i);
|
|
63
|
+
return match?.[1]?.toUpperCase() ?? null;
|
|
64
|
+
}
|
|
65
|
+
function checkboxDbStatusMilestoneIds(basePath, milestoneIds) {
|
|
66
|
+
if (!hasFlatPhaseLayout(basePath))
|
|
67
|
+
return milestoneIds;
|
|
68
|
+
// Flat-phase directories are keyed by phase number. A restored
|
|
69
|
+
// M003-xxxxxx row alongside M003 is a stale duplicate for the same
|
|
70
|
+
// phases/03-* projection, so checking both can compare DB status against two
|
|
71
|
+
// competing PLAN files and report a persistent false divergence.
|
|
72
|
+
const allIds = new Set(milestoneIds.map((id) => id.toUpperCase()));
|
|
73
|
+
return milestoneIds.filter((milestoneId) => {
|
|
74
|
+
const bareId = bareDuplicateMilestoneId(milestoneId);
|
|
75
|
+
return !bareId || !allIds.has(bareId);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
61
78
|
function checkProjectionCheckboxDbStatus(basePath, milestoneIds, issues) {
|
|
62
79
|
for (const milestoneId of milestoneIds) {
|
|
63
80
|
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
@@ -322,7 +339,9 @@ function staleMilestonesArtifactRowFixable(basePath, row, artifactRows) {
|
|
|
322
339
|
}
|
|
323
340
|
function stalePhasesArtifactRowFixable(basePath, row, artifactRows) {
|
|
324
341
|
const issuePath = artifactPathRelativeToGsd(row.path);
|
|
325
|
-
return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) &&
|
|
342
|
+
return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) &&
|
|
343
|
+
(hasPresentFlatPhaseReplacement(basePath, row, artifactRows) ||
|
|
344
|
+
hasPresentMilestonesReplacement(basePath, row, artifactRows));
|
|
326
345
|
}
|
|
327
346
|
function staleArtifactRowFixable(basePath, row, artifactRows) {
|
|
328
347
|
return staleMilestonesArtifactRowFixable(basePath, row, artifactRows) ||
|
|
@@ -658,7 +677,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied, options)
|
|
|
658
677
|
// block doctor.
|
|
659
678
|
try {
|
|
660
679
|
if (isDbAvailable()) {
|
|
661
|
-
checkProjectionCheckboxDbStatus(basePath, getAllMilestones().map((milestone) => milestone.id), issues);
|
|
680
|
+
checkProjectionCheckboxDbStatus(basePath, checkboxDbStatusMilestoneIds(basePath, getAllMilestones().map((milestone) => milestone.id)), issues);
|
|
662
681
|
}
|
|
663
682
|
}
|
|
664
683
|
catch {
|
|
@@ -38,8 +38,12 @@ const NETWORK_RE = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|web
|
|
|
38
38
|
// transient errors so auto-mode can retry with reduced budget or fall back to a larger-context model.
|
|
39
39
|
// See: https://github.com/open-gsd/gsd-pi/issues/4528
|
|
40
40
|
const SERVER_RE = /internal(?: server)?[ _-]?error|server[ _-]?error|500|502|503|overloaded|server_error|api_error|service.?unavailable|context (?:window|length) exceed|context window exceed/i;
|
|
41
|
+
// Provider adapters throw this when an upstream stop reason reaches Pi before
|
|
42
|
+
// the adapter knows how to map it. Keep the match scoped so plain "unknown"
|
|
43
|
+
// runtime errors still pause for inspection instead of being retried forever.
|
|
44
|
+
const PROVIDER_STOP_REASON_RE = /unhandled stop reason:\s*(?:abort|unknown)\b/i;
|
|
41
45
|
// ECONNRESET/ECONNREFUSED are in NETWORK_RE (same-model retry first).
|
|
42
|
-
const CONNECTION_RE = /terminated|connection.?(?:refused|error)|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
|
|
46
|
+
const CONNECTION_RE = /terminated|\babort\b|connection.?(?:refused|error)|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
|
|
43
47
|
// Catch-all for V8 JSON.parse errors: all modern variants end with "in JSON at position \d+".
|
|
44
48
|
// This eliminates the need to enumerate every error message variant individually.
|
|
45
49
|
const STREAM_RE = /in JSON at position \d+|Unexpected end of JSON|SyntaxError.*JSON/i;
|
|
@@ -72,7 +76,7 @@ const UNSUPPORTED_MODEL_SCOPE_RE = /\b(?:account|plan|tier|subscription)\b/i;
|
|
|
72
76
|
* 3. Network (ECONNRESET, ETIMEDOUT, socket hang up, fetch failed, dns)
|
|
73
77
|
* 4. Stream truncation (malformed JSON from mid-stream cut)
|
|
74
78
|
* 5. Server (500/502/503, overloaded, server_error)
|
|
75
|
-
* 6. Connection (terminated, ECONNREFUSED, EPIPE, other side closed)
|
|
79
|
+
* 6. Connection (terminated, abort/unknown stop reason, ECONNREFUSED, EPIPE, other side closed)
|
|
76
80
|
* 7. Model error (400 invalid argument / payload rejected for this model)
|
|
77
81
|
* 8. Unknown
|
|
78
82
|
*/
|
|
@@ -124,6 +128,12 @@ export function classifyError(errorMsg, retryAfterMs) {
|
|
|
124
128
|
const delayMs = resetMatch ? Number(resetMatch[1]) * 1000 : 60_000;
|
|
125
129
|
return { kind: "rate-limit", retryAfterMs: delayMs };
|
|
126
130
|
}
|
|
131
|
+
// Provider stop-reason adapter gaps are usually request/stream failures from
|
|
132
|
+
// the current model. Treat the scoped adapter message as fallback-eligible,
|
|
133
|
+
// but keep generic unknown errors on the indefinite pause path.
|
|
134
|
+
if (PROVIDER_STOP_REASON_RE.test(errorMsg)) {
|
|
135
|
+
return { kind: "connection", retryAfterMs: retryAfterMs ?? 15_000 };
|
|
136
|
+
}
|
|
127
137
|
// 3. Network errors — same-model retry candidate
|
|
128
138
|
if (NETWORK_RE.test(errorMsg)) {
|
|
129
139
|
// Exclude if also matches permanent signals (already handled above for
|
|
@@ -130,6 +130,17 @@ function isInsideFlatProjectionBackup(backupDir, src) {
|
|
|
130
130
|
const phaseBackupDir = join(backupDir, "__phases");
|
|
131
131
|
return src === phaseBackupDir || src.startsWith(`${phaseBackupDir}/`) || src.startsWith(`${phaseBackupDir}\\`);
|
|
132
132
|
}
|
|
133
|
+
function removeFlatProjectionBackup(backupDir) {
|
|
134
|
+
const phaseBackupDir = join(backupDir, "__phases");
|
|
135
|
+
if (!existsSync(phaseBackupDir))
|
|
136
|
+
return;
|
|
137
|
+
try {
|
|
138
|
+
removePathWithRetries(phaseBackupDir);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
logWarning("migration", `flat-phase migration succeeded but could not remove temporary ${LAYOUT_SEGMENTS.level1}/ backup: ${err.message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
133
144
|
function restoreFlatProjectionFromBackup(basePath, backupDir) {
|
|
134
145
|
const phaseBackupDir = join(backupDir, "__phases");
|
|
135
146
|
if (!existsSync(phaseBackupDir))
|
|
@@ -433,4 +444,5 @@ export async function migrateToFlatPhase(basePath) {
|
|
|
433
444
|
catch (err) {
|
|
434
445
|
logWarning("migration", `flat-phase migration succeeded but could not remove ${LEGACY_MIGRATING_SEGMENT}: ${err.message}`);
|
|
435
446
|
}
|
|
447
|
+
removeFlatProjectionBackup(backupDir);
|
|
436
448
|
}
|
|
@@ -38,6 +38,7 @@ export * from "./db/queries.js";
|
|
|
38
38
|
export * from "./db/writers/cascades.js";
|
|
39
39
|
import { TERMINAL_STATUS_SQL } from "./db/sql-constants.js";
|
|
40
40
|
import { applyStatusTransition } from "./db/writers/status.js";
|
|
41
|
+
import { LAYOUT_SEGMENTS, derivePhaseSlug, milestoneIdToPhaseNum, milestoneIdUniqueSuffix, phaseDirName, } from "./layout-policy.js";
|
|
41
42
|
export function insertDecision(d) {
|
|
42
43
|
if (!getDbOrNull())
|
|
43
44
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
@@ -169,6 +170,44 @@ export function insertArtifact(a) {
|
|
|
169
170
|
":content_hash": contentHash,
|
|
170
171
|
});
|
|
171
172
|
}
|
|
173
|
+
function canonicalPhaseDirNameForDb(milestoneId, title) {
|
|
174
|
+
const phaseNum = milestoneIdToPhaseNum(milestoneId);
|
|
175
|
+
const slug = derivePhaseSlug(title || milestoneId);
|
|
176
|
+
const suffix = milestoneIdUniqueSuffix(milestoneId);
|
|
177
|
+
return phaseDirName(phaseNum, suffix ? `${suffix}-${slug}` : slug);
|
|
178
|
+
}
|
|
179
|
+
function reconcileMilestonePhaseArtifactPaths(milestoneId, title) {
|
|
180
|
+
const db = getDbOrNull();
|
|
181
|
+
const phaseNumPrefix = String(milestoneIdToPhaseNum(milestoneId)).padStart(2, "0");
|
|
182
|
+
const canonicalDir = canonicalPhaseDirNameForDb(milestoneId, title);
|
|
183
|
+
const staleRows = db.prepare(`SELECT path
|
|
184
|
+
FROM artifacts
|
|
185
|
+
WHERE milestone_id = :milestone_id
|
|
186
|
+
AND path LIKE :phase_prefix`).all({
|
|
187
|
+
":milestone_id": milestoneId,
|
|
188
|
+
":phase_prefix": `${LAYOUT_SEGMENTS.level1}/${phaseNumPrefix}-%/%`,
|
|
189
|
+
});
|
|
190
|
+
const updatePath = db.prepare("UPDATE artifacts SET path = :new_path WHERE path = :old_path");
|
|
191
|
+
const deletePath = db.prepare("DELETE FROM artifacts WHERE path = :path");
|
|
192
|
+
const existingPath = db.prepare("SELECT 1 AS present FROM artifacts WHERE path = :path");
|
|
193
|
+
for (const row of staleRows) {
|
|
194
|
+
const parts = row.path.split("/");
|
|
195
|
+
if (parts.length < 3)
|
|
196
|
+
continue;
|
|
197
|
+
if (parts[0] !== LAYOUT_SEGMENTS.level1)
|
|
198
|
+
continue;
|
|
199
|
+
if (parts[1] === canonicalDir)
|
|
200
|
+
continue;
|
|
201
|
+
if (!parts[1]?.startsWith(`${phaseNumPrefix}-`))
|
|
202
|
+
continue;
|
|
203
|
+
const newPath = [LAYOUT_SEGMENTS.level1, canonicalDir, ...parts.slice(2)].join("/");
|
|
204
|
+
if (existingPath.get({ ":path": newPath })) {
|
|
205
|
+
deletePath.run({ ":path": row.path });
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
updatePath.run({ ":new_path": newPath, ":old_path": row.path });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
172
211
|
export function insertMilestone(m) {
|
|
173
212
|
if (!getDbOrNull())
|
|
174
213
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
@@ -207,37 +246,42 @@ export function insertMilestone(m) {
|
|
|
207
246
|
export function upsertMilestonePlanning(milestoneId, planning) {
|
|
208
247
|
if (!getDbOrNull())
|
|
209
248
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
249
|
+
transaction(() => {
|
|
250
|
+
getDbOrNull().prepare(`UPDATE milestones SET
|
|
251
|
+
title = COALESCE(NULLIF(:title, ''), title),
|
|
252
|
+
status = COALESCE(NULLIF(:status, ''), status),
|
|
253
|
+
depends_on = COALESCE(:depends_on, depends_on),
|
|
254
|
+
vision = COALESCE(:vision, vision),
|
|
255
|
+
success_criteria = COALESCE(:success_criteria, success_criteria),
|
|
256
|
+
key_risks = COALESCE(:key_risks, key_risks),
|
|
257
|
+
proof_strategy = COALESCE(:proof_strategy, proof_strategy),
|
|
258
|
+
verification_contract = COALESCE(:verification_contract, verification_contract),
|
|
259
|
+
verification_integration = COALESCE(:verification_integration, verification_integration),
|
|
260
|
+
verification_operational = COALESCE(:verification_operational, verification_operational),
|
|
261
|
+
verification_uat = COALESCE(:verification_uat, verification_uat),
|
|
262
|
+
definition_of_done = COALESCE(:definition_of_done, definition_of_done),
|
|
263
|
+
requirement_coverage = COALESCE(:requirement_coverage, requirement_coverage),
|
|
264
|
+
boundary_map_markdown = COALESCE(:boundary_map_markdown, boundary_map_markdown)
|
|
265
|
+
WHERE id = :id`).run({
|
|
266
|
+
":id": milestoneId,
|
|
267
|
+
":title": planning.title ?? "",
|
|
268
|
+
":status": planning.status ?? "",
|
|
269
|
+
":depends_on": planning.depends_on ? JSON.stringify(planning.depends_on) : null,
|
|
270
|
+
":vision": planning.vision ?? null,
|
|
271
|
+
":success_criteria": planning.successCriteria ? JSON.stringify(planning.successCriteria) : null,
|
|
272
|
+
":key_risks": planning.keyRisks ? JSON.stringify(planning.keyRisks) : null,
|
|
273
|
+
":proof_strategy": planning.proofStrategy ? JSON.stringify(planning.proofStrategy) : null,
|
|
274
|
+
":verification_contract": planning.verificationContract ?? null,
|
|
275
|
+
":verification_integration": planning.verificationIntegration ?? null,
|
|
276
|
+
":verification_operational": planning.verificationOperational ?? null,
|
|
277
|
+
":verification_uat": planning.verificationUat ?? null,
|
|
278
|
+
":definition_of_done": planning.definitionOfDone ? JSON.stringify(planning.definitionOfDone) : null,
|
|
279
|
+
":requirement_coverage": planning.requirementCoverage ?? null,
|
|
280
|
+
":boundary_map_markdown": planning.boundaryMapMarkdown ?? null,
|
|
281
|
+
});
|
|
282
|
+
const finalTitle = planning.title?.trim();
|
|
283
|
+
if (finalTitle)
|
|
284
|
+
reconcileMilestonePhaseArtifactPaths(milestoneId, finalTitle);
|
|
241
285
|
});
|
|
242
286
|
}
|
|
243
287
|
export function insertSlice(s) {
|
|
@@ -922,7 +922,7 @@ export async function buildDiscussSlicePrompt(mid, sid, sTitle, base, options) {
|
|
|
922
922
|
? `\n\n## Re-discuss Mode\n\nThis slice already has an existing context file (\`${sliceContextPath}\`) from a prior discussion. The user has chosen to re-discuss it. Read the existing context file, interview for any updates, changes, or new decisions, and rewrite the file with merged findings. Do NOT skip the interview — the user explicitly asked to revisit this slice.\n`
|
|
923
923
|
: "";
|
|
924
924
|
const inlinedTemplates = inlineTemplate("slice-context", "Slice Context");
|
|
925
|
-
return loadPrompt("guided-discuss-slice", {
|
|
925
|
+
return prependLanguageDirective(base, loadPrompt("guided-discuss-slice", {
|
|
926
926
|
milestoneId: mid,
|
|
927
927
|
sliceId: sid,
|
|
928
928
|
sliceTitle: sTitle,
|
|
@@ -933,7 +933,7 @@ export async function buildDiscussSlicePrompt(mid, sid, sTitle, base, options) {
|
|
|
933
933
|
inlinedTemplates,
|
|
934
934
|
structuredQuestionsAvailable: options?.structuredQuestionsAvailable ?? "false",
|
|
935
935
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}/${sid}): slice context from discuss`),
|
|
936
|
-
});
|
|
936
|
+
}));
|
|
937
937
|
}
|
|
938
938
|
/**
|
|
939
939
|
* /gsd discuss — show a picker of non-done slices and run a slice interview.
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
import { readFileSync, existsSync, mkdirSync, statSync, unlinkSync } from "node:fs";
|
|
12
12
|
import { logWarning } from "./workflow-logger.js";
|
|
13
13
|
import { isClosedStatus } from "./status-guards.js";
|
|
14
|
-
import { dirname, join
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
15
|
import { getAllMilestones, getMilestone, getMilestoneScopedArtifacts, getSliceScopedArtifacts, getMilestoneSlices, getSliceTasks, getTasksBySliceIds, getTask, getSlice, insertArtifact, deleteArtifactByPath, getGateResults, isDbAvailable, } from "./gsd-db.js";
|
|
16
16
|
import { resolveDir, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, targetMilestoneFile, targetSliceFile, targetTaskFile, gsdProjectionRoot, gsdRoot, buildTaskFileName, } from "./paths.js";
|
|
17
17
|
import { saveFile, clearParseCache, registerCacheClearCallback } from "./files.js";
|
|
18
18
|
import { parseRoadmap, parsePlan } from "./parsers-legacy.js";
|
|
19
19
|
import { invalidateStateCache } from "./state.js";
|
|
20
20
|
import { clearPathCache, milestonesDir, legacyMilestonesDir, isLegacyMilestonesLayout, resolveMilestonePath, relSliceFile, canonicalPhaseDirName } from "./paths.js";
|
|
21
|
-
import { readCompatMarker, writeCompatMarker, computeProjectionSha } from "./compat/compat-marker.js";
|
|
21
|
+
import { readCompatMarker, writeCompatMarker, computeProjectionSha, deriveCompatProjectionKey } from "./compat/compat-marker.js";
|
|
22
22
|
// ─── Compat marker invalidation ───────────────────────────────────────────
|
|
23
23
|
// Every successful projection write pushes its (basePath, projectionPath,
|
|
24
24
|
// entities) here; invalidateCaches() drains it and refreshes .gsd/.compat.json
|
|
@@ -71,13 +71,7 @@ function flushProjectionWritesToMarker() {
|
|
|
71
71
|
*/
|
|
72
72
|
function toArtifactPath(absPath, basePath) {
|
|
73
73
|
const projectionRoot = gsdProjectionRoot(basePath);
|
|
74
|
-
|
|
75
|
-
const root = projectionRel && !projectionRel.startsWith("..") && !projectionRel.startsWith("/")
|
|
76
|
-
? projectionRoot
|
|
77
|
-
: gsdRoot(basePath);
|
|
78
|
-
const rel = relative(root, absPath);
|
|
79
|
-
// Normalize to forward slashes for consistent DB keys
|
|
80
|
-
return rel.replace(/\\/g, "/");
|
|
74
|
+
return deriveCompatProjectionKey(absPath, [projectionRoot, gsdRoot(basePath)]);
|
|
81
75
|
}
|
|
82
76
|
/**
|
|
83
77
|
* Invalidate all caches after a disk write.
|
|
@@ -122,6 +122,14 @@ function cachedReaddir(dirPath) {
|
|
|
122
122
|
dirListCache.set(dirPath, entries);
|
|
123
123
|
return entries;
|
|
124
124
|
}
|
|
125
|
+
function isExistingFile(path) {
|
|
126
|
+
try {
|
|
127
|
+
return statSync(path).isFile();
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
125
133
|
/**
|
|
126
134
|
* Clear the volatile directory listing caches.
|
|
127
135
|
* Call after milestone transitions, file creation in planning directories,
|
|
@@ -257,7 +265,7 @@ export function resolveFile(dir, idPrefix, suffix) {
|
|
|
257
265
|
return null;
|
|
258
266
|
const target = `${idPrefix}-${suffix}.md`.toUpperCase();
|
|
259
267
|
try {
|
|
260
|
-
const entries =
|
|
268
|
+
const entries = cachedReaddirWithTypes(dir).filter(e => e.isFile()).map(e => e.name);
|
|
261
269
|
// Direct match: ID-SUFFIX.md
|
|
262
270
|
const direct = entries.find(e => e.toUpperCase() === target);
|
|
263
271
|
if (direct)
|
|
@@ -286,7 +294,9 @@ export function resolveTaskFiles(tasksDir, suffix) {
|
|
|
286
294
|
if (!existsSync(tasksDir))
|
|
287
295
|
return [];
|
|
288
296
|
try {
|
|
289
|
-
return
|
|
297
|
+
return cachedReaddirWithTypes(tasksDir)
|
|
298
|
+
.filter(e => e.isFile())
|
|
299
|
+
.map(e => e.name)
|
|
290
300
|
.filter(f => taskIdFromTaskFileName(f, suffix) !== null)
|
|
291
301
|
.sort();
|
|
292
302
|
}
|
|
@@ -826,7 +836,7 @@ export function resolveMilestoneFile(basePath, milestoneId, suffix) {
|
|
|
826
836
|
const prefix = `${String(phaseNum).padStart(2, "0")}`;
|
|
827
837
|
const flatName = `${prefix}-${suffix}.md`;
|
|
828
838
|
const flatPath = join(mDir, flatName);
|
|
829
|
-
if (
|
|
839
|
+
if (isExistingFile(flatPath))
|
|
830
840
|
return flatPath;
|
|
831
841
|
// Legacy fallback: M001-SUFFIX.md
|
|
832
842
|
const file = resolveFile(mDir, milestoneId, suffix);
|
|
@@ -859,12 +869,12 @@ export function resolveSliceFile(basePath, milestoneId, sliceId, suffix) {
|
|
|
859
869
|
const planNum = sliceIdToPlanNum(sliceId);
|
|
860
870
|
const flatName = planFileName(phaseNum, planNum, suffix);
|
|
861
871
|
const flatPath = join(phaseDir, flatName);
|
|
862
|
-
if (
|
|
872
|
+
if (isExistingFile(flatPath))
|
|
863
873
|
return flatPath;
|
|
864
874
|
// Also check plan-number-only format MM-SUFFIX.md (written by buildSliceFileName)
|
|
865
875
|
const planOnlyName = `${String(planNum).padStart(2, "0")}-${suffix}.md`;
|
|
866
876
|
const planOnlyPath = join(phaseDir, planOnlyName);
|
|
867
|
-
if (
|
|
877
|
+
if (isExistingFile(planOnlyPath))
|
|
868
878
|
return planOnlyPath;
|
|
869
879
|
// Try prefix match for the phase+plan number (handles suffix variations)
|
|
870
880
|
const planPrefix = `${String(phaseNum).padStart(2, "0")}-${String(planNum).padStart(2, "0")}-`;
|
|
@@ -910,10 +920,10 @@ export function resolveTaskFile(basePath, milestoneId, sliceId, taskId, suffix)
|
|
|
910
920
|
const isLegacy = phaseDir.startsWith(legacyBase + "/") || phaseDir.startsWith(legacyBase + "\\");
|
|
911
921
|
if (suffix !== "PLAN" && !isLegacy) {
|
|
912
922
|
const flatPath = join(phaseDir, buildFlatTaskFileName(sliceId, taskId, suffix));
|
|
913
|
-
if (
|
|
923
|
+
if (isExistingFile(flatPath))
|
|
914
924
|
return flatPath;
|
|
915
925
|
const legacyFlatPath = join(phaseDir, buildTaskFileName(taskId, suffix));
|
|
916
|
-
return
|
|
926
|
+
return isExistingFile(legacyFlatPath) ? legacyFlatPath : null;
|
|
917
927
|
}
|
|
918
928
|
const tDir = resolveTasksDir(basePath, milestoneId, sliceId);
|
|
919
929
|
if (tDir) {
|
|
@@ -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.
|