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.
Files changed (87) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/gsd/auto/loop.js +2 -3
  3. package/dist/resources/extensions/gsd/auto/orchestrator.js +2 -2
  4. package/dist/resources/extensions/gsd/auto/phases.js +12 -4
  5. package/dist/resources/extensions/gsd/auto-dispatch.js +34 -4
  6. package/dist/resources/extensions/gsd/auto-recovery.js +1 -0
  7. package/dist/resources/extensions/gsd/auto.js +27 -11
  8. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +35 -4
  9. package/dist/resources/extensions/gsd/crash-recovery.js +4 -1
  10. package/dist/resources/extensions/gsd/db/auto-workers.js +21 -0
  11. package/dist/resources/extensions/gsd/preferences.js +4 -0
  12. package/dist/resources/extensions/gsd/repo-identity.js +39 -22
  13. package/dist/resources/extensions/gsd/session-lock.js +15 -2
  14. package/dist/resources/extensions/gsd/tools/complete-milestone.js +9 -1
  15. package/dist/resources/extensions/gsd/tools/complete-slice.js +50 -2
  16. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +66 -40
  17. package/dist/resources/extensions/gsd/worktree-safety.js +10 -3
  18. package/dist/resources/extensions/shared/next-action-ui.js +13 -5
  19. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  20. package/dist/web/standalone/.next/BUILD_ID +1 -1
  21. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  22. package/dist/web/standalone/.next/build-manifest.json +2 -2
  23. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  24. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/index.html +1 -1
  41. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  48. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  49. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  50. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  51. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  52. package/package.json +1 -1
  53. package/src/resources/extensions/gsd/auto/contracts.ts +2 -0
  54. package/src/resources/extensions/gsd/auto/loop.ts +2 -2
  55. package/src/resources/extensions/gsd/auto/orchestrator.ts +2 -2
  56. package/src/resources/extensions/gsd/auto/phases.ts +14 -4
  57. package/src/resources/extensions/gsd/auto-dispatch.ts +52 -3
  58. package/src/resources/extensions/gsd/auto-recovery.ts +1 -0
  59. package/src/resources/extensions/gsd/auto.ts +63 -18
  60. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +25 -4
  61. package/src/resources/extensions/gsd/crash-recovery.ts +3 -0
  62. package/src/resources/extensions/gsd/db/auto-workers.ts +25 -0
  63. package/src/resources/extensions/gsd/preferences.ts +4 -0
  64. package/src/resources/extensions/gsd/repo-identity.ts +45 -25
  65. package/src/resources/extensions/gsd/session-lock.ts +15 -2
  66. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +135 -0
  67. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +64 -35
  68. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +17 -15
  69. package/src/resources/extensions/gsd/tests/auto-workers.test.ts +13 -0
  70. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +51 -1
  71. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +55 -0
  72. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +111 -1
  73. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +15 -0
  74. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +38 -0
  75. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +28 -1
  76. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +35 -0
  77. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +38 -0
  78. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +120 -0
  79. package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +44 -0
  80. package/src/resources/extensions/gsd/tools/complete-milestone.ts +10 -0
  81. package/src/resources/extensions/gsd/tools/complete-slice.ts +51 -2
  82. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +31 -17
  83. package/src/resources/extensions/gsd/worktree-safety.ts +12 -4
  84. package/src/resources/extensions/shared/next-action-ui.ts +11 -5
  85. package/src/resources/extensions/shared/tests/next-action-ui-hasui.test.ts +32 -0
  86. /package/dist/web/standalone/.next/static/{zCegwxH2e6vLp1vEZLLuZ → 8wipfz6TDZ6YWoaQjgqYD}/_buildManifest.js +0 -0
  87. /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(params);
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(params);
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 wrapArray = (v) => v == null ? [] : Array.isArray(v) ? v : [v];
411
- const coerced = { ...params };
412
- coerced.provides = wrapArray(params.provides);
413
- coerced.keyFiles = wrapArray(params.keyFiles);
414
- coerced.keyDecisions = wrapArray(params.keyDecisions);
415
- coerced.patternsEstablished = wrapArray(params.patternsEstablished);
416
- coerced.observabilitySurfaces = wrapArray(params.observabilitySurfaces);
417
- coerced.requirementsSurfaced = wrapArray(params.requirementsSurfaced);
418
- coerced.drillDownPaths = wrapArray(params.drillDownPaths);
419
- coerced.affects = wrapArray(params.affects);
420
- coerced.filesModified = wrapArray(params.filesModified).map((f) => {
421
- if (typeof f !== "string")
422
- return f;
423
- const [path, description] = splitPair(f);
424
- return { path, description };
425
- });
426
- coerced.requires = wrapArray(params.requires).map((r) => {
427
- if (typeof r !== "string")
428
- return r;
429
- const [slice, provides] = splitPair(r);
430
- return { slice, provides };
431
- });
432
- coerced.requirementsAdvanced = wrapArray(params.requirementsAdvanced).map((r) => {
433
- if (typeof r !== "string")
434
- return r;
435
- const [id, how] = splitPair(r);
436
- return { id, how };
437
- });
438
- coerced.requirementsValidated = wrapArray(params.requirementsValidated).map((r) => {
439
- if (typeof r !== "string")
440
- return r;
441
- const [id, proof] = splitPair(r);
442
- return { id, proof };
443
- });
444
- coerced.requirementsInvalidated = wrapArray(params.requirementsInvalidated).map((r) => {
445
- if (typeof r !== "string")
446
- return r;
447
- const [id, what] = splitPair(r);
448
- return { id, what };
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 expectedRoot = join(projectRoot, ".gsd", "worktrees", milestoneId);
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", `Unit root ${unitRoot} is not the expected worktree root for ${milestoneId}.`, "Prepare the Unit in its canonical milestone worktree before allowing source writes.", { expectedRoot, unitRoot });
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: when no UI is bound (noOpUIContext), ctx.ui.custom() resolves
67
- // to undefined immediately, and ctx.ui.select() does the same. Skip both and
68
- // return the safe default so callers don't await two no-op promises before
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
+ }