gsd-pi 3.0.0-dev.2e8b124f7 → 3.0.0-dev.6c9a50fd0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +2 -3
- package/dist/resources/extensions/gsd/auto/orchestrator.js +2 -2
- package/dist/resources/extensions/gsd/auto/phases.js +12 -4
- package/dist/resources/extensions/gsd/auto-dispatch.js +34 -4
- package/dist/resources/extensions/gsd/auto-recovery.js +1 -0
- package/dist/resources/extensions/gsd/auto.js +27 -11
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +35 -4
- package/dist/resources/extensions/gsd/crash-recovery.js +4 -1
- package/dist/resources/extensions/gsd/db/auto-workers.js +21 -0
- package/dist/resources/extensions/gsd/preferences.js +4 -0
- package/dist/resources/extensions/gsd/repo-identity.js +39 -22
- package/dist/resources/extensions/gsd/session-lock.js +15 -2
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +9 -1
- package/dist/resources/extensions/gsd/tools/complete-slice.js +50 -2
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +66 -40
- package/dist/resources/extensions/gsd/worktree-safety.js +10 -3
- package/dist/resources/extensions/shared/next-action-ui.js +13 -5
- 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 +6 -6
- 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 +6 -6
- 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/src/resources/extensions/gsd/auto/contracts.ts +2 -0
- package/src/resources/extensions/gsd/auto/loop.ts +2 -2
- package/src/resources/extensions/gsd/auto/orchestrator.ts +2 -2
- package/src/resources/extensions/gsd/auto/phases.ts +14 -4
- package/src/resources/extensions/gsd/auto-dispatch.ts +52 -3
- package/src/resources/extensions/gsd/auto-recovery.ts +1 -0
- package/src/resources/extensions/gsd/auto.ts +63 -18
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +25 -4
- package/src/resources/extensions/gsd/crash-recovery.ts +3 -0
- package/src/resources/extensions/gsd/db/auto-workers.ts +25 -0
- package/src/resources/extensions/gsd/preferences.ts +4 -0
- package/src/resources/extensions/gsd/repo-identity.ts +45 -25
- package/src/resources/extensions/gsd/session-lock.ts +15 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +135 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +64 -35
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +17 -15
- package/src/resources/extensions/gsd/tests/auto-workers.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +51 -1
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +111 -1
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +28 -1
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +44 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +10 -0
- package/src/resources/extensions/gsd/tools/complete-slice.ts +51 -2
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +31 -17
- package/src/resources/extensions/gsd/worktree-safety.ts +12 -4
- package/src/resources/extensions/shared/next-action-ui.ts +11 -5
- package/src/resources/extensions/shared/tests/next-action-ui-hasui.test.ts +32 -0
- /package/dist/web/standalone/.next/static/{zCegwxH2e6vLp1vEZLLuZ → 8wipfz6TDZ6YWoaQjgqYD}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{zCegwxH2e6vLp1vEZLLuZ → 8wipfz6TDZ6YWoaQjgqYD}/_ssgManifest.js +0 -0
|
@@ -181,6 +181,31 @@ function renderUatMarkdown(params) {
|
|
|
181
181
|
${params.uatContent}
|
|
182
182
|
`;
|
|
183
183
|
}
|
|
184
|
+
function parseRequirementSection(summaryMd, heading, field) {
|
|
185
|
+
const headingLine = `## ${heading}\n\n`;
|
|
186
|
+
const start = summaryMd.indexOf(headingLine);
|
|
187
|
+
if (start === -1)
|
|
188
|
+
return [];
|
|
189
|
+
const contentStart = start + headingLine.length;
|
|
190
|
+
const nextHeading = summaryMd.indexOf("\n\n## ", contentStart);
|
|
191
|
+
const content = nextHeading === -1
|
|
192
|
+
? summaryMd.slice(contentStart)
|
|
193
|
+
: summaryMd.slice(contentStart, nextHeading);
|
|
194
|
+
return content
|
|
195
|
+
.split("\n")
|
|
196
|
+
.map((line) => line.trim())
|
|
197
|
+
.filter((line) => line.startsWith("- "))
|
|
198
|
+
.map((line) => line.slice(2).trim())
|
|
199
|
+
.map((line) => {
|
|
200
|
+
const pair = line.match(/^(.+?)\s*(?:—|-)\s+(.+)$/);
|
|
201
|
+
const id = pair ? pair[1].trim() : line.trim();
|
|
202
|
+
const detail = pair ? pair[2].trim() : "";
|
|
203
|
+
if (!id || !detail)
|
|
204
|
+
return null;
|
|
205
|
+
return { id, [field]: detail };
|
|
206
|
+
})
|
|
207
|
+
.filter((entry) => entry !== null);
|
|
208
|
+
}
|
|
184
209
|
/**
|
|
185
210
|
* Handle the complete_slice operation end-to-end.
|
|
186
211
|
*
|
|
@@ -216,6 +241,7 @@ export async function handleCompleteSlice(params, basePath) {
|
|
|
216
241
|
// ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
|
|
217
242
|
const completedAt = new Date().toISOString();
|
|
218
243
|
let guardError = null;
|
|
244
|
+
let existingSummaryMd = "";
|
|
219
245
|
transaction(() => {
|
|
220
246
|
// State machine preconditions (inside txn for atomicity).
|
|
221
247
|
// Milestone/slice not existing is OK — insertMilestone/insertSlice below will auto-create.
|
|
@@ -226,6 +252,7 @@ export async function handleCompleteSlice(params, basePath) {
|
|
|
226
252
|
return;
|
|
227
253
|
}
|
|
228
254
|
const slice = getSlice(params.milestoneId, params.sliceId);
|
|
255
|
+
existingSummaryMd = slice?.full_summary_md?.trim() ?? "";
|
|
229
256
|
if (slice && isClosedStatus(slice.status)) {
|
|
230
257
|
if (isStaleWrite("complete-slice")) {
|
|
231
258
|
guardError = "__stale_duplicate__";
|
|
@@ -270,8 +297,29 @@ export async function handleCompleteSlice(params, basePath) {
|
|
|
270
297
|
if (guardError) {
|
|
271
298
|
return { error: guardError };
|
|
272
299
|
}
|
|
300
|
+
const effectiveParams = { ...params };
|
|
301
|
+
if (existingSummaryMd) {
|
|
302
|
+
// Keep these heading names in lock-step with renderSliceSummaryMarkdown's
|
|
303
|
+
// section titles so omitted CompleteSliceParams requirement fields can be
|
|
304
|
+
// backfilled from previously rendered summary markdown.
|
|
305
|
+
if (effectiveParams.requirementsAdvanced === undefined) {
|
|
306
|
+
const parsed = parseRequirementSection(existingSummaryMd, "Requirements Advanced", "how");
|
|
307
|
+
if (parsed.length > 0)
|
|
308
|
+
effectiveParams.requirementsAdvanced = parsed;
|
|
309
|
+
}
|
|
310
|
+
if (effectiveParams.requirementsValidated === undefined) {
|
|
311
|
+
const parsed = parseRequirementSection(existingSummaryMd, "Requirements Validated", "proof");
|
|
312
|
+
if (parsed.length > 0)
|
|
313
|
+
effectiveParams.requirementsValidated = parsed;
|
|
314
|
+
}
|
|
315
|
+
if (effectiveParams.requirementsInvalidated === undefined) {
|
|
316
|
+
const parsed = parseRequirementSection(existingSummaryMd, "Requirements Invalidated or Re-scoped", "what");
|
|
317
|
+
if (parsed.length > 0)
|
|
318
|
+
effectiveParams.requirementsInvalidated = parsed;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
273
321
|
// Render summary markdown
|
|
274
|
-
const summaryMd = renderSliceSummaryMarkdown(
|
|
322
|
+
const summaryMd = renderSliceSummaryMarkdown(effectiveParams);
|
|
275
323
|
// Resolve and write summary to disk
|
|
276
324
|
let summaryPath;
|
|
277
325
|
const sliceDir = resolveSlicePath(basePath, params.milestoneId, params.sliceId);
|
|
@@ -285,7 +333,7 @@ export async function handleCompleteSlice(params, basePath) {
|
|
|
285
333
|
mkdirSync(manualSliceDir, { recursive: true });
|
|
286
334
|
summaryPath = join(manualSliceDir, `${params.sliceId}-SUMMARY.md`);
|
|
287
335
|
}
|
|
288
|
-
const uatMd = renderUatMarkdown(
|
|
336
|
+
const uatMd = renderUatMarkdown(effectiveParams);
|
|
289
337
|
const uatPath = summaryPath.replace(/-SUMMARY\.md$/, "-UAT.md");
|
|
290
338
|
setSliceSummaryMd(params.milestoneId, params.sliceId, summaryMd, uatMd);
|
|
291
339
|
let projectionStale = false;
|
|
@@ -407,46 +407,72 @@ export async function executeSliceComplete(params, basePath = process.cwd()) {
|
|
|
407
407
|
const m = s.match(/^(.+?)\s*(?:—|-)\s+(.+)$/);
|
|
408
408
|
return m ? [m[1].trim(), m[2].trim()] : [s.trim(), ""];
|
|
409
409
|
};
|
|
410
|
-
const
|
|
411
|
-
const coerced =
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
410
|
+
const wrapOptionalArray = (v) => v == null ? undefined : Array.isArray(v) ? v : [v];
|
|
411
|
+
const coerced = Object.fromEntries(Object.entries(params).filter(([, value]) => value !== undefined && value !== null));
|
|
412
|
+
const provides = wrapOptionalArray(params.provides);
|
|
413
|
+
if (provides !== undefined)
|
|
414
|
+
coerced.provides = provides;
|
|
415
|
+
const keyFiles = wrapOptionalArray(params.keyFiles);
|
|
416
|
+
if (keyFiles !== undefined)
|
|
417
|
+
coerced.keyFiles = keyFiles;
|
|
418
|
+
const keyDecisions = wrapOptionalArray(params.keyDecisions);
|
|
419
|
+
if (keyDecisions !== undefined)
|
|
420
|
+
coerced.keyDecisions = keyDecisions;
|
|
421
|
+
const patternsEstablished = wrapOptionalArray(params.patternsEstablished);
|
|
422
|
+
if (patternsEstablished !== undefined)
|
|
423
|
+
coerced.patternsEstablished = patternsEstablished;
|
|
424
|
+
const observabilitySurfaces = wrapOptionalArray(params.observabilitySurfaces);
|
|
425
|
+
if (observabilitySurfaces !== undefined)
|
|
426
|
+
coerced.observabilitySurfaces = observabilitySurfaces;
|
|
427
|
+
const requirementsSurfaced = wrapOptionalArray(params.requirementsSurfaced);
|
|
428
|
+
if (requirementsSurfaced !== undefined)
|
|
429
|
+
coerced.requirementsSurfaced = requirementsSurfaced;
|
|
430
|
+
const drillDownPaths = wrapOptionalArray(params.drillDownPaths);
|
|
431
|
+
if (drillDownPaths !== undefined)
|
|
432
|
+
coerced.drillDownPaths = drillDownPaths;
|
|
433
|
+
const affects = wrapOptionalArray(params.affects);
|
|
434
|
+
if (affects !== undefined)
|
|
435
|
+
coerced.affects = affects;
|
|
436
|
+
const filesModified = wrapOptionalArray(params.filesModified);
|
|
437
|
+
if (filesModified !== undefined)
|
|
438
|
+
coerced.filesModified = filesModified.map((f) => {
|
|
439
|
+
if (typeof f !== "string")
|
|
440
|
+
return f;
|
|
441
|
+
const [path, description] = splitPair(f);
|
|
442
|
+
return { path, description };
|
|
443
|
+
});
|
|
444
|
+
const requires = wrapOptionalArray(params.requires);
|
|
445
|
+
if (requires !== undefined)
|
|
446
|
+
coerced.requires = requires.map((r) => {
|
|
447
|
+
if (typeof r !== "string")
|
|
448
|
+
return r;
|
|
449
|
+
const [slice, provides] = splitPair(r);
|
|
450
|
+
return { slice, provides };
|
|
451
|
+
});
|
|
452
|
+
const requirementsAdvanced = wrapOptionalArray(params.requirementsAdvanced);
|
|
453
|
+
if (requirementsAdvanced !== undefined)
|
|
454
|
+
coerced.requirementsAdvanced = requirementsAdvanced.map((r) => {
|
|
455
|
+
if (typeof r !== "string")
|
|
456
|
+
return r;
|
|
457
|
+
const [id, how] = splitPair(r);
|
|
458
|
+
return { id, how };
|
|
459
|
+
});
|
|
460
|
+
const requirementsValidated = wrapOptionalArray(params.requirementsValidated);
|
|
461
|
+
if (requirementsValidated !== undefined)
|
|
462
|
+
coerced.requirementsValidated = requirementsValidated.map((r) => {
|
|
463
|
+
if (typeof r !== "string")
|
|
464
|
+
return r;
|
|
465
|
+
const [id, proof] = splitPair(r);
|
|
466
|
+
return { id, proof };
|
|
467
|
+
});
|
|
468
|
+
const requirementsInvalidated = wrapOptionalArray(params.requirementsInvalidated);
|
|
469
|
+
if (requirementsInvalidated !== undefined)
|
|
470
|
+
coerced.requirementsInvalidated = requirementsInvalidated.map((r) => {
|
|
471
|
+
if (typeof r !== "string")
|
|
472
|
+
return r;
|
|
473
|
+
const [id, what] = splitPair(r);
|
|
474
|
+
return { id, what };
|
|
475
|
+
});
|
|
450
476
|
const result = await handleCompleteSlice(coerced, basePath);
|
|
451
477
|
if ("error" in result) {
|
|
452
478
|
return {
|
|
@@ -58,9 +58,16 @@ export function createWorktreeSafetyModule(deps = defaultDeps) {
|
|
|
58
58
|
}
|
|
59
59
|
const projectRoot = resolve(input.projectRoot);
|
|
60
60
|
const unitRoot = resolve(input.unitRoot);
|
|
61
|
-
const
|
|
61
|
+
const isolationMode = input.isolationMode ?? "worktree";
|
|
62
|
+
const expectedRoot = isolationMode === "worktree"
|
|
63
|
+
? join(projectRoot, ".gsd", "worktrees", milestoneId)
|
|
64
|
+
: projectRoot;
|
|
62
65
|
if (!samePath(unitRoot, expectedRoot)) {
|
|
63
|
-
return failure("invalid-root",
|
|
66
|
+
return failure("invalid-root", isolationMode === "worktree"
|
|
67
|
+
? `Unit root ${unitRoot} is not the expected worktree root for ${milestoneId}.`
|
|
68
|
+
: `Unit root ${unitRoot} is not the project root while isolation mode is ${isolationMode}.`, isolationMode === "worktree"
|
|
69
|
+
? "Prepare the Unit in its canonical milestone worktree before allowing source writes."
|
|
70
|
+
: "Run the Unit from the project root when worktree isolation is disabled.", { expectedRoot, unitRoot });
|
|
64
71
|
}
|
|
65
72
|
if (!deps.existsSync(unitRoot)) {
|
|
66
73
|
return failure("worktree-missing", `Worktree root ${unitRoot} does not exist.`, "Create or recover the milestone worktree before dispatching the source-writing Unit.", { unitRoot });
|
|
@@ -76,7 +83,7 @@ export function createWorktreeSafetyModule(deps = defaultDeps) {
|
|
|
76
83
|
catch (error) {
|
|
77
84
|
return failure("worktree-git-probe-failed", `Unable to inspect .git marker for worktree root ${unitRoot}.`, "Recover or recreate the milestone worktree before dispatching the source-writing Unit.", { gitMarker, error: errorMessage(error) });
|
|
78
85
|
}
|
|
79
|
-
if (!gitMarkerStat.isFile()) {
|
|
86
|
+
if (isolationMode === "worktree" && !gitMarkerStat.isFile()) {
|
|
80
87
|
return failure("worktree-git-marker-not-file", `Worktree root ${unitRoot} has a .git directory, not a registered worktree .git file.`, "Use a registered GSD worktree instead of a copied or nested repository.", { gitMarker });
|
|
81
88
|
}
|
|
82
89
|
let registered;
|
|
@@ -63,11 +63,9 @@ export async function showNextAction(ctx, opts) {
|
|
|
63
63
|
return f;
|
|
64
64
|
}
|
|
65
65
|
});
|
|
66
|
-
// Headless guard:
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
// reaching a deterministic "not_yet". Lockup #5125 root protection.
|
|
70
|
-
if (!ctx.hasUI) {
|
|
66
|
+
// Headless/non-interactive guard: avoid emitting interactive select requests
|
|
67
|
+
// in contexts where no human can answer (no UI, RPC/headless shims).
|
|
68
|
+
if (!isInteractiveUIContext(ctx)) {
|
|
71
69
|
return "not_yet";
|
|
72
70
|
}
|
|
73
71
|
const result = await ctx.ui.custom((_tui, theme, _kb, done) => {
|
|
@@ -173,3 +171,13 @@ export async function showNextAction(ctx, opts) {
|
|
|
173
171
|
}
|
|
174
172
|
return result;
|
|
175
173
|
}
|
|
174
|
+
function isInteractiveUIContext(ctx) {
|
|
175
|
+
if (!ctx.hasUI)
|
|
176
|
+
return false;
|
|
177
|
+
if (process.env.GSD_HEADLESS === "1")
|
|
178
|
+
return false;
|
|
179
|
+
const uiMode = ctx.ui?.mode;
|
|
180
|
+
if (uiMode === "rpc" || uiMode === "headless")
|
|
181
|
+
return false;
|
|
182
|
+
return true;
|
|
183
|
+
}
|