gsd-pi 2.79.0-dev.ece5fd8ba → 2.80.0

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 (193) hide show
  1. package/dist/loader.js +0 -0
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/contracts.js +1 -0
  4. package/dist/resources/extensions/gsd/auto/orchestrator.js +146 -0
  5. package/dist/resources/extensions/gsd/auto/phases.js +55 -6
  6. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  7. package/dist/resources/extensions/gsd/auto-recovery.js +45 -52
  8. package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -0
  9. package/dist/resources/extensions/gsd/auto.js +159 -2
  10. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
  11. package/dist/resources/extensions/gsd/gsd-db.js +34 -1
  12. package/dist/resources/extensions/gsd/post-execution-checks.js +25 -6
  13. package/dist/resources/extensions/shared/interview-ui.js +15 -4
  14. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  15. package/dist/web/standalone/.next/BUILD_ID +1 -1
  16. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  17. package/dist/web/standalone/.next/build-manifest.json +3 -3
  18. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  19. package/dist/web/standalone/.next/required-server-files.json +3 -3
  20. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  21. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  31. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +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 +3 -3
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  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 +3 -3
  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/api/boot/route.js +1 -1
  41. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  42. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  43. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  44. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  45. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  46. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  47. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  49. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  59. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  79. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  89. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  95. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  111. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  115. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/index.html +1 -1
  125. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  126. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  127. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  128. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  129. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  130. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app/page.js +2 -2
  132. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  134. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  135. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  136. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/middleware.js +2 -2
  138. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  140. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  141. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  142. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  143. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-f2a7482d42a5614b.js → page-2f24283c162b6ab3.js} +1 -1
  144. package/dist/web/standalone/.next/static/chunks/app/{layout-a16c7a7ecdf0c2cf.js → layout-9ecfd95f343793f0.js} +1 -1
  145. package/dist/web/standalone/.next/static/chunks/app/page-ff639266d978f2a0.js +1 -0
  146. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +1 -0
  147. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +1 -0
  148. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  149. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  150. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  151. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  152. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  153. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  154. package/dist/web/standalone/server.js +1 -1
  155. package/package.json +1 -1
  156. package/packages/daemon/package.json +2 -2
  157. package/packages/mcp-server/package.json +2 -2
  158. package/packages/native/package.json +1 -1
  159. package/packages/pi-agent-core/package.json +1 -1
  160. package/packages/pi-ai/package.json +1 -1
  161. package/packages/pi-coding-agent/package.json +1 -1
  162. package/packages/pi-tui/package.json +1 -1
  163. package/packages/rpc-client/package.json +1 -1
  164. package/pkg/package.json +1 -1
  165. package/src/resources/extensions/gsd/auto/contracts.ts +87 -0
  166. package/src/resources/extensions/gsd/auto/loop-deps.ts +10 -3
  167. package/src/resources/extensions/gsd/auto/orchestrator.ts +161 -0
  168. package/src/resources/extensions/gsd/auto/phases.ts +82 -8
  169. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  170. package/src/resources/extensions/gsd/auto-recovery.ts +42 -50
  171. package/src/resources/extensions/gsd/auto-runtime-state.ts +7 -0
  172. package/src/resources/extensions/gsd/auto.ts +167 -1
  173. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +14 -1
  174. package/src/resources/extensions/gsd/gsd-db.ts +35 -1
  175. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  176. package/src/resources/extensions/gsd/post-execution-checks.ts +31 -6
  177. package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +32 -0
  178. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +353 -0
  179. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +39 -0
  180. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +3 -0
  181. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +95 -0
  182. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +79 -0
  183. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +134 -0
  184. package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +2 -0
  185. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +27 -0
  186. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +46 -0
  187. package/src/resources/extensions/shared/interview-ui.ts +18 -5
  188. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +41 -0
  189. package/dist/web/standalone/.next/static/chunks/app/page-fab3ebb85b006001.js +0 -1
  190. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +0 -1
  191. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +0 -1
  192. /package/dist/web/standalone/.next/static/{TzEVJ1Lh8vbez4n4Q9TqQ → V-3Ehy4B24f9FCGiLPWIM}/_buildManifest.js +0 -0
  193. /package/dist/web/standalone/.next/static/{TzEVJ1Lh8vbez4n4Q9TqQ → V-3Ehy4B24f9FCGiLPWIM}/_ssgManifest.js +0 -0
@@ -399,6 +399,140 @@ test("runDispatch pauses when complete-milestone summary exists on disk but the
399
399
  assert.equal(stopCalls, 0, "mismatch pause should not hard-stop the loop");
400
400
  });
401
401
 
402
+ test("runDispatch clears stuck state after Level 1 artifact recovery", async (t) => {
403
+ const capture = createEventCapture();
404
+ let invalidateCalls = 0;
405
+ let stopCalls = 0;
406
+ const base = join(tmpdir(), `gsd-stuck-plan-${randomUUID()}`);
407
+ t.after(() => {
408
+ closeDatabase();
409
+ rmSync(base, { recursive: true, force: true });
410
+ });
411
+
412
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
413
+ const tasksDir = join(sliceDir, "tasks");
414
+ mkdirSync(tasksDir, { recursive: true });
415
+ openDatabase(join(base, ".gsd", "gsd.db"));
416
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
417
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "pending" });
418
+ insertTask({ id: "T01", milestoneId: "M001", sliceId: "S01", title: "First task", status: "pending" });
419
+ writeFileSync(join(sliceDir, "S01-PLAN.md"), "# S01\n\n## Tasks\n\n- [ ] **T01: First task** `est:1h`\n");
420
+ writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\n");
421
+
422
+ const deps = makeMockDeps(capture, {
423
+ invalidateAllCaches: () => { invalidateCalls++; },
424
+ stopAuto: async () => { stopCalls++; },
425
+ resolveDispatch: async () => ({
426
+ action: "dispatch" as const,
427
+ unitType: "plan-slice",
428
+ unitId: "M001/S01",
429
+ prompt: "plan the slice",
430
+ matchedRule: "planning → plan-slice",
431
+ }),
432
+ });
433
+ const ic = makeIC(deps, {
434
+ s: {
435
+ ...makeSession(),
436
+ basePath: base,
437
+ originalBasePath: base,
438
+ } as any,
439
+ });
440
+ const preData: PreDispatchData = {
441
+ state: {
442
+ phase: "planning",
443
+ activeMilestone: { id: "M001", title: "Test", status: "active" },
444
+ activeSlice: { id: "S01", title: "Slice" },
445
+ registry: [{ id: "M001", status: "active" }],
446
+ blockers: [],
447
+ } as any,
448
+ mid: "M001",
449
+ midTitle: "Test Milestone",
450
+ };
451
+ const loopState: LoopState = {
452
+ recentUnits: [
453
+ { key: "plan-slice/M001/S01" },
454
+ { key: "plan-slice/M001/S01" },
455
+ ],
456
+ stuckRecoveryAttempts: 0,
457
+ consecutiveFinalizeTimeouts: 0,
458
+ };
459
+
460
+ const result = await runDispatch(ic, preData, loopState);
461
+
462
+ assert.equal(result.action, "continue");
463
+ assert.equal(invalidateCalls, 1, "Level 1 artifact recovery should invalidate caches");
464
+ assert.equal(stopCalls, 0, "Level 1 artifact recovery should not hard-stop");
465
+ assert.deepEqual(loopState.recentUnits, [], "Level 1 artifact recovery should clear the stuck window");
466
+ assert.equal(loopState.stuckRecoveryAttempts, 0, "Level 1 artifact recovery should reset the recovery counter");
467
+ });
468
+
469
+ test("runDispatch escapes Level 2 stuck stop when artifact verifies after cache invalidation", async (t) => {
470
+ const capture = createEventCapture();
471
+ let invalidateCalls = 0;
472
+ let stopCalls = 0;
473
+ const base = join(tmpdir(), `gsd-stuck-plan-l2-${randomUUID()}`);
474
+ t.after(() => {
475
+ closeDatabase();
476
+ rmSync(base, { recursive: true, force: true });
477
+ });
478
+
479
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
480
+ const tasksDir = join(sliceDir, "tasks");
481
+ mkdirSync(tasksDir, { recursive: true });
482
+ openDatabase(join(base, ".gsd", "gsd.db"));
483
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
484
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "pending" });
485
+ insertTask({ id: "T01", milestoneId: "M001", sliceId: "S01", title: "First task", status: "pending" });
486
+ writeFileSync(join(sliceDir, "S01-PLAN.md"), "# S01\n\n## Tasks\n\n- [ ] **T01: First task** `est:1h`\n");
487
+ writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan\n");
488
+
489
+ const deps = makeMockDeps(capture, {
490
+ invalidateAllCaches: () => { invalidateCalls++; },
491
+ stopAuto: async () => { stopCalls++; },
492
+ resolveDispatch: async () => ({
493
+ action: "dispatch" as const,
494
+ unitType: "plan-slice",
495
+ unitId: "M001/S01",
496
+ prompt: "plan the slice",
497
+ matchedRule: "planning → plan-slice",
498
+ }),
499
+ });
500
+ const ic = makeIC(deps, {
501
+ s: {
502
+ ...makeSession(),
503
+ basePath: base,
504
+ originalBasePath: base,
505
+ } as any,
506
+ });
507
+ const preData: PreDispatchData = {
508
+ state: {
509
+ phase: "planning",
510
+ activeMilestone: { id: "M001", title: "Test", status: "active" },
511
+ activeSlice: { id: "S01", title: "Slice" },
512
+ registry: [{ id: "M001", status: "active" }],
513
+ blockers: [],
514
+ } as any,
515
+ mid: "M001",
516
+ midTitle: "Test Milestone",
517
+ };
518
+ const loopState: LoopState = {
519
+ recentUnits: [
520
+ { key: "plan-slice/M001/S01" },
521
+ { key: "plan-slice/M001/S01" },
522
+ ],
523
+ stuckRecoveryAttempts: 1,
524
+ consecutiveFinalizeTimeouts: 0,
525
+ };
526
+
527
+ const result = await runDispatch(ic, preData, loopState);
528
+
529
+ assert.equal(result.action, "continue");
530
+ assert.equal(invalidateCalls, 1, "Level 2 escape should invalidate caches before rechecking artifacts");
531
+ assert.equal(stopCalls, 0, "verified artifacts should escape Level 2 hard stop");
532
+ assert.deepEqual(loopState.recentUnits, [], "Level 2 artifact escape should clear the stuck window");
533
+ assert.equal(loopState.stuckRecoveryAttempts, 0, "Level 2 artifact escape should reset the recovery counter");
534
+ });
535
+
402
536
  test("runUnitPhase emits unit-start and unit-end with causedBy reference", async () => {
403
537
  const capture = createEventCapture();
404
538
 
@@ -64,6 +64,7 @@ test("readPausedSessionMetadata round-trips a real PausedSessionMetadata payload
64
64
  activeRunDir: null,
65
65
  autoStartTime: Date.now(),
66
66
  milestoneLock: null,
67
+ pauseReason: "Blocked: waiting for UAT",
67
68
  };
68
69
  setRuntimeKv("global", "", PAUSED_SESSION_KV_KEY, meta);
69
70
 
@@ -73,6 +74,7 @@ test("readPausedSessionMetadata round-trips a real PausedSessionMetadata payload
73
74
  assert.equal(loaded!.unitType, "plan-slice");
74
75
  assert.equal(loaded!.unitId, "M001/S01");
75
76
  assert.equal(loaded!.sessionFile, "/tmp/session.jsonl");
77
+ assert.equal(loaded!.pauseReason, "Blocked: waiting for UAT");
76
78
  });
77
79
 
78
80
  test("readPausedSessionMetadata auto-deletes stale pseudo-milestone pause rows", (t) => {
@@ -1,3 +1,5 @@
1
+ // GSD Extension — Plan-slice tool integration tests.
2
+
1
3
  import test from 'node:test';
2
4
  import assert from 'node:assert/strict';
3
5
  import { mkdtempSync, mkdirSync, rmSync, readFileSync, existsSync, writeFileSync } from 'node:fs';
@@ -8,6 +10,7 @@ import { openDatabase, closeDatabase, insertMilestone, insertSlice, getSlice, ge
8
10
  import { handlePlanSlice } from '../tools/plan-slice.ts';
9
11
  import { parsePlan } from '../parsers-legacy.ts';
10
12
  import { parseTaskPlanFile } from '../files.ts';
13
+ import { deriveState, invalidateStateCache } from '../state.ts';
11
14
 
12
15
  function makeTmpBase(): string {
13
16
  const base = mkdtempSync(join(tmpdir(), 'gsd-plan-slice-'));
@@ -98,6 +101,30 @@ test('handlePlanSlice writes slice/task planning state and renders plan artifact
98
101
  }
99
102
  });
100
103
 
104
+ test('handlePlanSlice advances DB-derived state out of planning immediately', async () => {
105
+ const base = makeTmpBase();
106
+ openDatabase(join(base, '.gsd', 'gsd.db'));
107
+
108
+ try {
109
+ seedParentSlice();
110
+
111
+ invalidateStateCache();
112
+ const before = await deriveState(base);
113
+ assert.equal(before.phase, 'planning');
114
+ assert.equal(before.progress?.tasks?.total, 0);
115
+
116
+ const result = await handlePlanSlice(validParams(), base);
117
+ assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
118
+
119
+ invalidateStateCache();
120
+ const after = await deriveState(base);
121
+ assert.notEqual(after.phase, 'planning');
122
+ assert.equal(after.progress?.tasks?.total, 2);
123
+ } finally {
124
+ cleanup(base);
125
+ }
126
+ });
127
+
101
128
  test('handlePlanSlice leaves omitted enrichment fields empty instead of rendering placeholders', async () => {
102
129
  const base = makeTmpBase();
103
130
  openDatabase(join(base, '.gsd', 'gsd.db'));
@@ -304,6 +304,30 @@ describe("resolveImportPath", () => {
304
304
  assert.ok(!result.exists);
305
305
  assert.equal(result.resolvedPath, null);
306
306
  });
307
+
308
+ test("resolves dotted TS module stem like .server via extension probing", (t) => {
309
+ const dir = mkdtempSync(join(tmpdir(), "post-exec-test-server-dot-"));
310
+ t.after(() => rmSync(dir, { recursive: true, force: true }));
311
+ mkdirSync(join(dir, "src"), { recursive: true });
312
+ writeFileSync(join(dir, "src", "route.server.ts"), "export {};\n");
313
+ writeFileSync(join(dir, "src", "main.ts"), "");
314
+
315
+ const result = resolveImportPath("./route.server", "src/main.ts", dir);
316
+ assert.ok(result.exists);
317
+ assert.ok(result.resolvedPath?.endsWith("route.server.ts"));
318
+ });
319
+
320
+ test("missing unknown explicit extension does not match code-extension shadow", (t) => {
321
+ const dir = mkdtempSync(join(tmpdir(), "post-exec-test-unknown-shadow-"));
322
+ t.after(() => rmSync(dir, { recursive: true, force: true }));
323
+ mkdirSync(join(dir, "src"), { recursive: true });
324
+ writeFileSync(join(dir, "src", "video.mp4.ts"), "export {};\n");
325
+ writeFileSync(join(dir, "src", "main.ts"), "");
326
+
327
+ const result = resolveImportPath("./video.mp4", "src/main.ts", dir);
328
+ assert.ok(!result.exists);
329
+ assert.equal(result.resolvedPath, null);
330
+ });
307
331
  });
308
332
 
309
333
  // ─── Import Resolution Check Tests ───────────────────────────────────────────
@@ -334,6 +358,28 @@ describe("checkImportResolution", () => {
334
358
  }
335
359
  });
336
360
 
361
+ test("ignores generated React Router +types imports", () => {
362
+ tempDir = join(tmpdir(), `post-exec-test-${Date.now()}`);
363
+ mkdirSync(tempDir, { recursive: true });
364
+ mkdirSync(join(tempDir, "app", "routes"), { recursive: true });
365
+ writeFileSync(
366
+ join(tempDir, "app", "routes", "root.tsx"),
367
+ "import type { Route } from './+types/root';\nexport default function Root() { return null; }"
368
+ );
369
+
370
+ try {
371
+ const task = createTask({
372
+ id: "T01",
373
+ key_files: ["app/routes/root.tsx"],
374
+ });
375
+
376
+ const results = checkImportResolution(task, [], tempDir);
377
+ assert.deepEqual(results, []);
378
+ } finally {
379
+ rmSync(tempDir, { recursive: true, force: true });
380
+ }
381
+ });
382
+
337
383
  test("fails when import doesn't resolve", () => {
338
384
  tempDir = join(tmpdir(), `post-exec-test-${Date.now()}`);
339
385
  mkdirSync(tempDir, { recursive: true });
@@ -1,3 +1,4 @@
1
+ // GSD2 — Shared interview round UI widget
1
2
  /**
2
3
  * Shared interview round UI widget.
3
4
  *
@@ -224,12 +225,24 @@ export async function showInterviewRound(
224
225
  let showingExitConfirm = false;
225
226
  let exitCursor = 0; // 0 = keep going (default), 1 = end interview
226
227
  let cachedLines: string[] | undefined;
228
+ let completed = false;
229
+ let removeAbortListener: (() => void) | undefined;
230
+
231
+ function finish(result: RoundResult) {
232
+ if (completed) return;
233
+ completed = true;
234
+ removeAbortListener?.();
235
+ done(result);
236
+ }
227
237
 
228
238
  // External cancellation (e.g. remote channel won the race)
229
239
  if (opts.signal) {
230
- const onAbort = () => done({ endInterview: false, answers: {} });
240
+ const onAbort = () => finish({ endInterview: false, answers: {} });
231
241
  if (opts.signal.aborted) { onAbort(); }
232
- else { opts.signal.addEventListener("abort", onAbort, { once: true }); }
242
+ else {
243
+ opts.signal.addEventListener("abort", onAbort, { once: true });
244
+ removeAbortListener = () => opts.signal?.removeEventListener("abort", onAbort);
245
+ }
233
246
  }
234
247
 
235
248
  // Editor is created once; editorTheme comes from the design system
@@ -312,7 +325,7 @@ export async function showInterviewRound(
312
325
 
313
326
  function submit() {
314
327
  saveEditorToState();
315
- done(buildResult());
328
+ finish(buildResult());
316
329
  }
317
330
 
318
331
  function goNextOrSubmit() {
@@ -355,10 +368,10 @@ export async function showInterviewRound(
355
368
  if (matchesKey(data, Key.up) || matchesKey(data, Key.left)) { exitCursor = 0; refresh(); return; }
356
369
  if (matchesKey(data, Key.down) || matchesKey(data, Key.right)) { exitCursor = 1; refresh(); return; }
357
370
  if (data === "1") { showingExitConfirm = false; refresh(); return; }
358
- if (data === "2") { done({ endInterview: false, answers: {} }); return; }
371
+ if (data === "2") { finish({ endInterview: false, answers: {} }); return; }
359
372
  if (matchesKey(data, Key.enter) || matchesKey(data, Key.space)) {
360
373
  if (exitCursor === 0) { showingExitConfirm = false; refresh(); }
361
- else { done({ endInterview: false, answers: {} }); }
374
+ else { finish({ endInterview: false, answers: {} }); }
362
375
  return;
363
376
  }
364
377
  if (matchesKey(data, Key.escape)) { showingExitConfirm = false; refresh(); return; }
@@ -139,4 +139,45 @@ describe("interview-ui notes loop regression (#3502)", () => {
139
139
  assert.ok(answer, "answer for q1 should exist");
140
140
  assert.equal(answer.selected, "Web App");
141
141
  });
142
+
143
+ it("ignores abort signals after a submitted answer", async () => {
144
+ const controller = new AbortController();
145
+ const doneCalls: RoundResult[] = [];
146
+ let widget: { handleInput(input: string): void } | undefined;
147
+
148
+ const resultPromise = showInterviewRound(questions, { signal: controller.signal }, {
149
+ ui: {
150
+ custom: (factory: any) => new Promise<RoundResult>((resolve) => {
151
+ const mockTui = { requestRender: () => {} };
152
+ const mockTheme = {
153
+ fg: (_c: string, t: string) => t,
154
+ bold: (t: string) => t,
155
+ dim: (t: string) => t,
156
+ italic: (t: string) => t,
157
+ strikethrough: (t: string) => t,
158
+ accent: (t: string) => t,
159
+ success: (t: string) => t,
160
+ warning: (t: string) => t,
161
+ error: (t: string) => t,
162
+ info: (t: string) => t,
163
+ muted: (t: string) => t,
164
+ dimmed: (t: string) => t,
165
+ };
166
+ widget = factory(mockTui, mockTheme, {}, (result: RoundResult) => {
167
+ doneCalls.push(result);
168
+ resolve(result);
169
+ });
170
+ }),
171
+ },
172
+ } as any);
173
+
174
+ assert.ok(widget, "widget should be created synchronously");
175
+ widget.handleInput(ENTER);
176
+ widget.handleInput(ENTER);
177
+ controller.abort();
178
+
179
+ const result = await resultPromise;
180
+ assert.equal(doneCalls.length, 1, "abort after submit must not emit a second empty result");
181
+ assert.deepEqual(result.answers.q1, { selected: "Web App", notes: "" });
182
+ });
142
183
  });
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[8974],{2600:(e,t,n)=>{Promise.resolve().then(n.bind(n,66919))},5214:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"workAsyncStorage",{enumerable:!0,get:function(){return r.workAsyncStorageInstance}});let r=n(17828)},17828:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"workAsyncStorageInstance",{enumerable:!0,get:function(){return r}});let r=(0,n(64054).createAsyncLocalStorage)()},21957:(e,t,n)=>{"use strict";function r({moduleIds:e}){return null}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"PreloadChunks",{enumerable:!0,get:function(){return r}}),n(95155),n(47650),n(5214),n(2451),n(53887)},37206:(e,t,n)=>{"use strict";n.d(t,{default:()=>u.a});var r=n(75707),u=n.n(r)},41112:(e,t,n)=>{"use strict";function r({reason:e,children:t}){return t}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"BailoutToCSR",{enumerable:!0,get:function(){return r}}),n(1980)},64054:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n={bindSnapshot:function(){return s},createAsyncLocalStorage:function(){return a},createSnapshot:function(){return i}};for(var r in n)Object.defineProperty(t,r,{enumerable:!0,get:n[r]});let u=Object.defineProperty(Error("Invariant: AsyncLocalStorage accessed in runtime where it is not available"),"__NEXT_ERROR_CODE",{value:"E504",enumerable:!1,configurable:!0});class l{disable(){throw u}getStore(){}run(){throw u}exit(){throw u}enterWith(){throw u}static bind(e){return e}}let o="u">typeof globalThis&&globalThis.AsyncLocalStorage;function a(){return o?new o:new l}function s(e){return o?o.bind(e):l.bind(e)}function i(){return o?o.snapshot():function(e,...t){return e(...t)}}},66919:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>l});var r=n(95155);let u=(0,n(37206).default)(()=>Promise.all([n.e(1838),n.e(4986),n.e(2059),n.e(8336)]).then(n.bind(n,78336)).then(e=>e.GSDAppShell),{loadableGenerated:{webpack:()=>[78336]},ssr:!1,loading:()=>(0,r.jsx)("div",{className:"flex h-screen items-center justify-center bg-background text-sm text-muted-foreground",children:"Loading workspace…"})});function l(){return(0,r.jsx)(u,{})}},68635:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return s}});let r=n(95155),u=n(12115),l=n(41112);function o(e){return{default:e&&"default"in e?e.default:e}}n(21957);let a={loader:()=>Promise.resolve(o(()=>null)),loading:null,ssr:!0},s=function(e){let t={...a,...e},n=(0,u.lazy)(()=>t.loader().then(o)),s=t.loading;function i(e){let o=s?(0,r.jsx)(s,{isLoading:!0,pastDelay:!0,error:null}):null,a=!t.ssr||!!t.loading,i=a?u.Suspense:u.Fragment,c=t.ssr?(0,r.jsxs)(r.Fragment,{children:[null,(0,r.jsx)(n,{...e})]}):(0,r.jsx)(l.BailoutToCSR,{reason:"next/dynamic",children:(0,r.jsx)(n,{...e})});return(0,r.jsx)(i,{...a?{fallback:o}:{},children:c})}return i.displayName="LoadableComponent",i}},75707:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return u}});let r=n(73623)._(n(68635));function u(e,t){let n={};"function"==typeof e&&(n.loader=e);let u={...n,...t};return(0,r.default)({...u,modules:u.loadableGenerated?.modules})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}},e=>{e.O(0,[8441,3794,7358],()=>e(e.s=2600)),_N_E=e.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[7358],{19393:()=>{},55548:(e,s,n)=>{Promise.resolve().then(n.t.bind(n,27123,23)),Promise.resolve().then(n.t.bind(n,61304,23)),Promise.resolve().then(n.t.bind(n,78616,23)),Promise.resolve().then(n.t.bind(n,64777,23)),Promise.resolve().then(n.t.bind(n,57121,23)),Promise.resolve().then(n.t.bind(n,74581,23)),Promise.resolve().then(n.t.bind(n,90484,23)),Promise.resolve().then(n.bind(n,86869))}},e=>{var s=s=>e(e.s=s);e.O(0,[8441,3794],()=>(s(83861),s(55548))),_N_E=e.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[9337],{43946:(e,s,_)=>{Promise.resolve().then(_.t.bind(_,27123,23))}},e=>{e.O(0,[8441,3794,7358],()=>e(e.s=43946)),_N_E=e.O()}]);