gsd-pi 2.47.0 → 2.48.0-dev.ced2eca

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 (200) hide show
  1. package/dist/resources/extensions/gsd/auto-dispatch.js +17 -2
  2. package/dist/resources/extensions/gsd/auto-post-unit.js +17 -3
  3. package/dist/resources/extensions/gsd/auto-start.js +8 -1
  4. package/dist/resources/extensions/gsd/auto-worktree.js +5 -2
  5. package/dist/resources/extensions/gsd/commands/handlers/auto.js +43 -3
  6. package/dist/resources/extensions/gsd/forensics.js +292 -1
  7. package/dist/resources/extensions/gsd/git-service.js +11 -10
  8. package/dist/resources/extensions/gsd/guided-flow.js +85 -3
  9. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +223 -56
  10. package/dist/resources/extensions/gsd/prompts/forensics.md +37 -5
  11. package/dist/resources/extensions/gsd/prompts/run-uat.md +4 -4
  12. package/dist/resources/extensions/gsd/session-forensics.js +10 -1
  13. package/dist/resources/extensions/gsd/worktree-command.js +1 -1
  14. package/dist/web/standalone/.next/BUILD_ID +1 -1
  15. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  16. package/dist/web/standalone/.next/build-manifest.json +3 -3
  17. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  18. package/dist/web/standalone/.next/required-server-files.json +3 -3
  19. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  20. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  21. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  22. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  30. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  33. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  40. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  41. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  42. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  43. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  44. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  45. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  46. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  47. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  49. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
  84. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  90. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  104. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  106. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  108. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  110. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/index.html +1 -1
  120. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  121. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  122. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  123. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  125. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  126. package/dist/web/standalone/.next/server/app/page.js +2 -2
  127. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  129. package/dist/web/standalone/.next/server/chunks/229.js +1 -1
  130. package/dist/web/standalone/.next/server/chunks/471.js +3 -3
  131. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/middleware.js +2 -2
  133. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  135. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  136. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  137. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  138. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  139. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  140. package/dist/web/standalone/.next/static/chunks/app/page-6654a8cca61a3d1c.js +1 -0
  141. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  142. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  143. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  144. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  145. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  146. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  147. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  148. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  149. package/dist/web/standalone/server.js +1 -1
  150. package/dist/worktree-cli.js +1 -1
  151. package/package.json +1 -1
  152. package/packages/pi-agent-core/dist/agent-loop.js +3 -2
  153. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  154. package/packages/pi-agent-core/src/agent-loop.ts +3 -2
  155. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +43 -0
  156. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  158. package/packages/pi-coding-agent/dist/core/model-registry.js +26 -3
  159. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  160. package/packages/pi-coding-agent/package.json +1 -1
  161. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +70 -0
  162. package/packages/pi-coding-agent/src/core/model-registry.ts +29 -2
  163. package/packages/pi-tui/dist/components/box.d.ts +1 -0
  164. package/packages/pi-tui/dist/components/box.d.ts.map +1 -1
  165. package/packages/pi-tui/dist/components/box.js +10 -0
  166. package/packages/pi-tui/dist/components/box.js.map +1 -1
  167. package/packages/pi-tui/src/components/box.ts +10 -0
  168. package/pkg/package.json +1 -1
  169. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +8 -4
  170. package/src/resources/extensions/gsd/auto-dispatch.ts +18 -1
  171. package/src/resources/extensions/gsd/auto-post-unit.ts +18 -3
  172. package/src/resources/extensions/gsd/auto-start.ts +7 -1
  173. package/src/resources/extensions/gsd/auto-worktree.ts +4 -2
  174. package/src/resources/extensions/gsd/commands/handlers/auto.ts +46 -3
  175. package/src/resources/extensions/gsd/forensics.ts +329 -2
  176. package/src/resources/extensions/gsd/git-service.ts +12 -11
  177. package/src/resources/extensions/gsd/guided-flow.ts +105 -3
  178. package/src/resources/extensions/gsd/prompts/discuss-headless.md +223 -56
  179. package/src/resources/extensions/gsd/prompts/forensics.md +37 -5
  180. package/src/resources/extensions/gsd/prompts/run-uat.md +4 -4
  181. package/src/resources/extensions/gsd/session-forensics.ts +11 -1
  182. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +2 -2
  183. package/src/resources/extensions/gsd/tests/auto-stash-merge.test.ts +1 -1
  184. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +14 -12
  185. package/src/resources/extensions/gsd/tests/discuss-queued-milestones.test.ts +241 -0
  186. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +1 -1
  187. package/src/resources/extensions/gsd/tests/forensics-error-filter.test.ts +121 -0
  188. package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +162 -0
  189. package/src/resources/extensions/gsd/tests/git-service.test.ts +19 -9
  190. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +2 -2
  191. package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +6 -6
  192. package/src/resources/extensions/gsd/tests/preflight-context-draft-filter.test.ts +115 -0
  193. package/src/resources/extensions/gsd/tests/run-uat.test.ts +68 -0
  194. package/src/resources/extensions/gsd/tests/stale-milestone-id-reservation.test.ts +79 -0
  195. package/src/resources/extensions/gsd/worktree-command.ts +1 -1
  196. package/dist/web/standalone/.next/static/chunks/app/page-12dd5ece0df4badc.js +0 -1
  197. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  198. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  199. /package/dist/web/standalone/.next/static/{VPcLnRF4BL8VoJEilBwlB → PTL5V00OW8q4-092tUQKx}/_buildManifest.js +0 -0
  200. /package/dist/web/standalone/.next/static/{VPcLnRF4BL8VoJEilBwlB → PTL5V00OW8q4-092tUQKx}/_ssgManifest.js +0 -0
@@ -215,10 +215,12 @@ describe('git-service', async () => {
215
215
  oneLiner: "Added JWT-based auth with refresh token rotation",
216
216
  keyFiles: ["src/auth.ts", "src/middleware/jwt.ts"],
217
217
  });
218
- assert.ok(msg.startsWith("feat(S01/T02):"), "message starts with type(scope)");
218
+ assert.ok(msg.startsWith("feat:"), "message starts with type: (no scope)");
219
+ assert.ok(!msg.includes("(S01/T02)"), "no GSD ID in subject line");
219
220
  assert.ok(msg.includes("JWT-based auth"), "message includes one-liner content");
220
221
  assert.ok(msg.includes("- src/auth.ts"), "message body includes key files");
221
222
  assert.ok(msg.includes("- src/middleware/jwt.ts"), "message body includes second key file");
223
+ assert.ok(msg.includes("GSD-Task: S01/T02"), "GSD-Task trailer in body");
222
224
  });
223
225
 
224
226
  {
@@ -226,9 +228,9 @@ describe('git-service', async () => {
226
228
  taskId: "S02/T01",
227
229
  taskTitle: "fix login redirect bug",
228
230
  });
229
- assert.ok(msg.startsWith("fix(S02/T01):"), "infers fix type from title");
231
+ assert.ok(msg.startsWith("fix:"), "infers fix type from title");
230
232
  assert.ok(msg.includes("fix login redirect bug"), "uses task title when no one-liner");
231
- assert.ok(!msg.includes("\n"), "no body when no key files");
233
+ assert.ok(msg.includes("GSD-Task: S02/T01"), "GSD-Task trailer present");
232
234
  }
233
235
 
234
236
  {
@@ -237,7 +239,8 @@ describe('git-service', async () => {
237
239
  taskTitle: "add tests",
238
240
  oneLiner: "Unit tests for auth module with coverage",
239
241
  });
240
- assert.ok(msg.startsWith("test(S01/T03):"), "infers test type");
242
+ assert.ok(msg.startsWith("test:"), "infers test type");
243
+ assert.ok(msg.includes("GSD-Task: S01/T03"), "GSD-Task trailer present");
241
244
  }
242
245
 
243
246
  // ─── RUNTIME_EXCLUSION_PATHS ───────────────────────────────────────────
@@ -478,10 +481,10 @@ describe('git-service', async () => {
478
481
 
479
482
  // Without task context, autoCommit uses generic chore message
480
483
  const msg = svc.autoCommit("task", "T01");
481
- assert.deepStrictEqual(msg, "chore(T01): auto-commit after task", "autoCommit returns generic format without task context");
484
+ assert.deepStrictEqual(msg, "chore: auto-commit after task\n\nGSD-Unit: T01", "autoCommit returns generic format with trailer");
482
485
 
483
486
  const log = run("git log --oneline -1", repo);
484
- assert.ok(log.includes("chore(T01): auto-commit after task"), "generic commit message is in git log");
487
+ assert.ok(log.includes("chore: auto-commit after task"), "generic commit message is in git log");
485
488
 
486
489
  // With task context, autoCommit uses meaningful message
487
490
  createFile(repo, "src/auth.ts", "export function login() {}");
@@ -492,8 +495,9 @@ describe('git-service', async () => {
492
495
  keyFiles: ["src/auth.ts"],
493
496
  });
494
497
  assert.ok(msg2 !== null, "autoCommit with task context returns a message");
495
- assert.ok(msg2!.startsWith("feat(S01/T02):"), "meaningful commit uses feat type and scope");
498
+ assert.ok(msg2!.startsWith("feat:"), "meaningful commit uses feat type without scope");
496
499
  assert.ok(msg2!.includes("JWT-based auth"), "meaningful commit includes one-liner content");
500
+ assert.ok(msg2!.includes("GSD-Task: S01/T02"), "meaningful commit has GSD-Task trailer");
497
501
 
498
502
  rmSync(repo, { recursive: true, force: true });
499
503
  });
@@ -529,7 +533,7 @@ describe('git-service', async () => {
529
533
 
530
534
  // Auto-commit with .gsd/ excluded (simulates pre-switch)
531
535
  const msg = svc.autoCommit("pre-switch", "main", [".gsd/"]);
532
- assert.deepStrictEqual(msg, "chore(main): auto-commit after pre-switch", "pre-switch autoCommit with .gsd/ exclusion commits");
536
+ assert.deepStrictEqual(msg, "chore: auto-commit after pre-switch\n\nGSD-Unit: main", "pre-switch autoCommit with .gsd/ exclusion commits");
533
537
 
534
538
  // Verify .gsd/ file was NOT committed
535
539
  const show = run("git show --stat HEAD", repo);
@@ -1295,7 +1299,12 @@ describe('git-service', async () => {
1295
1299
  issueNumber: 42,
1296
1300
  });
1297
1301
  assert.ok(msg.includes("Resolves #42"), "buildTaskCommitMessage includes Resolves #N trailer when issueNumber is set");
1298
- assert.ok(msg.startsWith("fix(S01/T03):"), "buildTaskCommitMessage infers fix type");
1302
+ assert.ok(msg.startsWith("fix:"), "buildTaskCommitMessage infers fix type");
1303
+ assert.ok(msg.includes("GSD-Task: S01/T03"), "GSD-Task trailer present");
1304
+ // GSD-Task should come before Resolves
1305
+ const taskIdx = msg.indexOf("GSD-Task: S01/T03");
1306
+ const resolvesIdx = msg.indexOf("Resolves #42");
1307
+ assert.ok(taskIdx < resolvesIdx, "GSD-Task trailer before Resolves trailer");
1299
1308
  });
1300
1309
 
1301
1310
  {
@@ -1305,6 +1314,7 @@ describe('git-service', async () => {
1305
1314
  taskTitle: "add dashboard widget",
1306
1315
  });
1307
1316
  assert.ok(!msg.includes("Resolves"), "buildTaskCommitMessage omits Resolves trailer when issueNumber is absent");
1317
+ assert.ok(msg.includes("GSD-Task: S01/T04"), "GSD-Task trailer still present");
1308
1318
  }
1309
1319
 
1310
1320
  // ─── runPreMergeCheck: skips when no package.json ────────────────────────
@@ -94,8 +94,8 @@ test("worktree swap on milestone transition: merge old, create new", () => {
94
94
  assert.equal(process.cwd(), tempDir, "cwd restored to project root after merge");
95
95
  assert.ok(!isInAutoWorktree(tempDir), "no longer in auto-worktree after merge");
96
96
 
97
- // Verify M001 work was merged to main
98
- const mainLog = run("git log --oneline -3", tempDir);
97
+ // Verify M001 work was merged to main (milestone ID is in trailer, not subject)
98
+ const mainLog = run("git log -3", tempDir);
99
99
  assert.ok(mainLog.includes("M001"), "M001 squash commit should be on main");
100
100
 
101
101
  // Phase 3: Create new worktree for M002 (simulates new milestone)
@@ -168,7 +168,7 @@ test("formatMergeResults — empty results", () => {
168
168
 
169
169
  test("formatMergeResults — successful merge", () => {
170
170
  const results: MergeResult[] = [
171
- { milestoneId: "M001", success: true, commitMessage: "feat(M001): Auth", pushed: true },
171
+ { milestoneId: "M001", success: true, commitMessage: "feat: Auth\n\nGSD-Milestone: M001\nBranch: milestone/M001", pushed: true },
172
172
  ];
173
173
  const output = formatMergeResults(results);
174
174
  assert.ok(output.includes("M001"));
@@ -178,7 +178,7 @@ test("formatMergeResults — successful merge", () => {
178
178
 
179
179
  test("formatMergeResults — successful merge without push", () => {
180
180
  const results: MergeResult[] = [
181
- { milestoneId: "M001", success: true, commitMessage: "feat(M001): Auth", pushed: false },
181
+ { milestoneId: "M001", success: true, commitMessage: "feat: Auth\n\nGSD-Milestone: M001\nBranch: milestone/M001", pushed: false },
182
182
  ];
183
183
  const output = formatMergeResults(results);
184
184
  assert.ok(output.includes("merged successfully"));
@@ -213,7 +213,7 @@ test("formatMergeResults — generic failure without conflict files", () => {
213
213
 
214
214
  test("formatMergeResults — mixed results", () => {
215
215
  const results: MergeResult[] = [
216
- { milestoneId: "M001", success: true, commitMessage: "feat(M001): OK", pushed: false },
216
+ { milestoneId: "M001", success: true, commitMessage: "feat: OK\n\nGSD-Milestone: M001\nBranch: milestone/M001", pushed: false },
217
217
  { milestoneId: "M002", success: false, error: "conflict", conflictFiles: ["a.ts"] },
218
218
  ];
219
219
  const output = formatMergeResults(results);
@@ -281,9 +281,9 @@ test("mergeCompletedMilestone — clean merge, session status cleaned up", async
281
281
  // Verify file merged to main
282
282
  assert.ok(existsSync(join(repo, "auth.ts")), "auth.ts should be on main");
283
283
 
284
- // Verify commit on main
285
- const log = run("git log --oneline main", repo);
286
- assert.ok(log.includes("M010"), "commit message should reference M010");
284
+ // Verify commit on main (M010 is now in the body as a GSD-Milestone trailer)
285
+ const log = run("git log -1 --format=%B main", repo);
286
+ assert.ok(log.includes("GSD-Milestone: M010"), "commit message should reference M010 in trailer");
287
287
 
288
288
  // Verify session status cleaned up
289
289
  const statusAfter = readSessionStatus(repo, "M010");
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Regression test for #2473: Pre-flight CONTEXT-DRAFT warning should skip
3
+ * completed and parked milestones.
4
+ *
5
+ * The pre-flight loop in auto-start.ts warns about CONTEXT-DRAFT.md files
6
+ * so the user knows which milestones will pause for discussion. But completed
7
+ * milestones with leftover CONTEXT-DRAFT.md files are not actionable — the
8
+ * warning is noise.
9
+ *
10
+ * This test exercises the filtering logic directly: given a set of milestones
11
+ * with CONTEXT-DRAFT files, only active/pending ones should produce warnings.
12
+ */
13
+ import { describe, test, beforeEach, afterEach } from "node:test";
14
+ import assert from "node:assert/strict";
15
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ import { tmpdir } from "node:os";
18
+
19
+ import {
20
+ openDatabase,
21
+ closeDatabase,
22
+ isDbAvailable,
23
+ insertMilestone,
24
+ getMilestone,
25
+ } from "../gsd-db.ts";
26
+ import { resolveMilestoneFile } from "../paths.ts";
27
+
28
+ describe("pre-flight CONTEXT-DRAFT filter (#2473)", () => {
29
+ let tmpBase: string;
30
+ let gsd: string;
31
+
32
+ beforeEach(() => {
33
+ tmpBase = mkdtempSync(join(tmpdir(), "gsd-preflight-draft-"));
34
+ gsd = join(tmpBase, ".gsd");
35
+
36
+ // Create milestone directories with CONTEXT-DRAFT files
37
+ for (const id of ["M001", "M002", "M003"]) {
38
+ const msDir = join(gsd, "milestones", id);
39
+ mkdirSync(msDir, { recursive: true });
40
+ writeFileSync(join(msDir, `${id}-CONTEXT-DRAFT.md`), `# ${id}: Draft\n`);
41
+ }
42
+
43
+ // Open DB and insert milestones with different statuses
44
+ const dbPath = join(gsd, "gsd.db");
45
+ openDatabase(dbPath);
46
+ insertMilestone({ id: "M001", title: "Complete milestone", status: "complete" });
47
+ insertMilestone({ id: "M002", title: "Active milestone", status: "active" });
48
+ insertMilestone({ id: "M003", title: "Parked milestone", status: "parked" });
49
+ });
50
+
51
+ afterEach(() => {
52
+ closeDatabase();
53
+ rmSync(tmpBase, { recursive: true, force: true });
54
+ });
55
+
56
+ test("completed milestone is skipped — no warning emitted", () => {
57
+ assert.ok(isDbAvailable(), "DB should be available");
58
+ const ms = getMilestone("M001");
59
+ assert.equal(ms?.status, "complete");
60
+ });
61
+
62
+ test("parked milestone is skipped — no warning emitted", () => {
63
+ const ms = getMilestone("M003");
64
+ assert.equal(ms?.status, "parked");
65
+ });
66
+
67
+ test("active milestone with CONTEXT-DRAFT produces warning", () => {
68
+ const ms = getMilestone("M002");
69
+ assert.equal(ms?.status, "active");
70
+
71
+ const draft = resolveMilestoneFile(tmpBase, "M002", "CONTEXT-DRAFT");
72
+ assert.ok(draft, "CONTEXT-DRAFT file should be found for active milestone");
73
+ });
74
+
75
+ test("full pre-flight filter produces warnings only for active milestones", () => {
76
+ const milestoneIds = ["M001", "M002", "M003"];
77
+ const issues: string[] = [];
78
+
79
+ for (const id of milestoneIds) {
80
+ // Replicate the fixed pre-flight logic from auto-start.ts
81
+ if (isDbAvailable()) {
82
+ const ms = getMilestone(id);
83
+ if (ms?.status === "complete" || ms?.status === "parked") continue;
84
+ }
85
+ const draft = resolveMilestoneFile(tmpBase, id, "CONTEXT-DRAFT");
86
+ if (draft) {
87
+ issues.push(`${id}: has CONTEXT-DRAFT.md (will pause for discussion)`);
88
+ }
89
+ }
90
+
91
+ assert.equal(issues.length, 1, "only one warning should be emitted");
92
+ assert.match(issues[0], /M002/, "warning should be for the active milestone only");
93
+ });
94
+
95
+ test("when DB is unavailable, all milestones with CONTEXT-DRAFT produce warnings (safe fallback)", () => {
96
+ closeDatabase();
97
+ assert.ok(!isDbAvailable(), "DB should be unavailable after close");
98
+
99
+ const milestoneIds = ["M001", "M002", "M003"];
100
+ const issues: string[] = [];
101
+
102
+ for (const id of milestoneIds) {
103
+ if (isDbAvailable()) {
104
+ const ms = getMilestone(id);
105
+ if (ms?.status === "complete" || ms?.status === "parked") continue;
106
+ }
107
+ const draft = resolveMilestoneFile(tmpBase, id, "CONTEXT-DRAFT");
108
+ if (draft) {
109
+ issues.push(`${id}: has CONTEXT-DRAFT.md (will pause for discussion)`);
110
+ }
111
+ }
112
+
113
+ assert.equal(issues.length, 3, "all milestones should warn when DB is unavailable");
114
+ });
115
+ });
@@ -343,6 +343,74 @@ test('(m) non-artifact UAT skip', async () => {
343
343
  }
344
344
  });
345
345
 
346
+ test('(o) verdict gate: PARTIAL is acceptable for mixed/human-experience/live-runtime UAT types', () => {
347
+ // This test verifies the contract that extractUatType correctly identifies
348
+ // the modes where PARTIAL should not block progression.
349
+ // The verdict gate in auto-dispatch.ts uses this to build acceptableVerdicts.
350
+ const mixedType = extractUatType(makeUatContent('mixed'));
351
+ const humanExpType = extractUatType(makeUatContent('human-experience'));
352
+ const liveRuntimeType = extractUatType(makeUatContent('live-runtime'));
353
+ const artifactType = extractUatType(makeUatContent('artifact-driven'));
354
+ const browserType = extractUatType(makeUatContent('browser-executable'));
355
+ const runtimeExecType = extractUatType(makeUatContent('runtime-executable'));
356
+
357
+ // These modes should allow PARTIAL (non-fully-automatable)
358
+ const partialAcceptableModes = ['mixed', 'human-experience', 'live-runtime'];
359
+ assert.ok(
360
+ partialAcceptableModes.includes(mixedType!),
361
+ `mixed → "${mixedType}" is in partialAcceptableModes`,
362
+ );
363
+ assert.ok(
364
+ partialAcceptableModes.includes(humanExpType!),
365
+ `human-experience → "${humanExpType}" is in partialAcceptableModes`,
366
+ );
367
+ assert.ok(
368
+ partialAcceptableModes.includes(liveRuntimeType!),
369
+ `live-runtime → "${liveRuntimeType}" is in partialAcceptableModes`,
370
+ );
371
+
372
+ // These modes should NOT allow PARTIAL (fully automatable)
373
+ assert.ok(
374
+ !partialAcceptableModes.includes(artifactType!),
375
+ `artifact-driven → "${artifactType}" is NOT in partialAcceptableModes`,
376
+ );
377
+ assert.ok(
378
+ !partialAcceptableModes.includes(browserType!),
379
+ `browser-executable → "${browserType}" is NOT in partialAcceptableModes`,
380
+ );
381
+ assert.ok(
382
+ !partialAcceptableModes.includes(runtimeExecType!),
383
+ `runtime-executable → "${runtimeExecType}" is NOT in partialAcceptableModes`,
384
+ );
385
+ });
386
+
387
+ test('(p) run-uat prompt allows PASS when human-only checks remain as NEEDS-HUMAN', () => {
388
+ const promptResult = loadPromptFromWorktree('run-uat', {
389
+ workingDirectory: '/tmp/test-project',
390
+ milestoneId: 'M001',
391
+ sliceId: 'S01',
392
+ uatPath: '.gsd/milestones/M001/slices/S01/S01-UAT.md',
393
+ uatResultPath: '.gsd/milestones/M001/slices/S01/S01-UAT-RESULT.md',
394
+ uatType: 'mixed',
395
+ inlinedContext: '<!-- no context -->',
396
+ });
397
+
398
+ // PASS verdict should be usable when automatable checks pass (even with NEEDS-HUMAN remaining)
399
+ assert.ok(
400
+ /PASS.*automatable checks passed/i.test(promptResult),
401
+ 'prompt defines PASS as valid when all automatable checks passed',
402
+ );
403
+ assert.ok(
404
+ /PARTIAL.*automatable checks.*(skipped|inconclusive)/i.test(promptResult),
405
+ 'prompt reserves PARTIAL for when automatable checks themselves are inconclusive',
406
+ );
407
+ // human-experience mode should NOT force PARTIAL when automatable checks pass
408
+ assert.ok(
409
+ !promptResult.includes('use an overall verdict of `PARTIAL`'),
410
+ 'prompt does not force PARTIAL verdict for human-experience mode',
411
+ );
412
+ });
413
+
346
414
  test('(n) stale replay guard', async () => {
347
415
  const base = createFixtureBase();
348
416
  try {
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Regression test for #2488: Stale milestone ID reservations inflate next ID
3
+ * after cancelled /gsd sessions.
4
+ *
5
+ * The module-level `reservedMilestoneIds` Set persists across /gsd invocations
6
+ * within the same Node process. Without clearReservedMilestoneIds() at session
7
+ * start, each cancelled session permanently bumps the counter by 1.
8
+ */
9
+ import { describe, test, beforeEach } from "node:test";
10
+ import assert from "node:assert/strict";
11
+
12
+ import {
13
+ nextMilestoneId,
14
+ reserveMilestoneId,
15
+ getReservedMilestoneIds,
16
+ clearReservedMilestoneIds,
17
+ } from "../milestone-ids.ts";
18
+
19
+ describe("stale milestone ID reservation cleanup (#2488)", () => {
20
+ beforeEach(() => {
21
+ clearReservedMilestoneIds();
22
+ });
23
+
24
+ test("without cleanup, cancelled sessions inflate the next ID", () => {
25
+ const diskIds = ["M001", "M002", "M003"];
26
+
27
+ // Session 1: user starts /gsd, ID is previewed and reserved, then cancelled
28
+ const allIds1 = [...new Set([...diskIds, ...getReservedMilestoneIds()])];
29
+ const preview1 = nextMilestoneId(allIds1);
30
+ reserveMilestoneId(preview1);
31
+ assert.equal(preview1, "M004");
32
+
33
+ // Session 2: user starts /gsd again — stale reservation still in Set
34
+ // WITHOUT clearing, the next ID skips M004 (reserved) and goes to M005
35
+ const allIds2 = [...new Set([...diskIds, ...getReservedMilestoneIds()])];
36
+ const preview2 = nextMilestoneId(allIds2);
37
+ assert.equal(preview2, "M005", "without cleanup, ID inflates to M005");
38
+ });
39
+
40
+ test("with cleanup at session start, next ID is correct", () => {
41
+ const diskIds = ["M001", "M002", "M003"];
42
+
43
+ // Session 1: user starts /gsd, ID is previewed and reserved, then cancelled
44
+ const allIds1 = [...new Set([...diskIds, ...getReservedMilestoneIds()])];
45
+ const preview1 = nextMilestoneId(allIds1);
46
+ reserveMilestoneId(preview1);
47
+ assert.equal(preview1, "M004");
48
+
49
+ // Session 2: clear stale reservations first (the fix)
50
+ clearReservedMilestoneIds();
51
+
52
+ // Now the next ID correctly returns M004 again
53
+ const allIds2 = [...new Set([...diskIds, ...getReservedMilestoneIds()])];
54
+ const preview2 = nextMilestoneId(allIds2);
55
+ assert.equal(preview2, "M004", "after cleanup, ID is correctly M004");
56
+ });
57
+
58
+ test("multiple cancelled sessions compound the inflation without cleanup", () => {
59
+ const diskIds = ["M001", "M002", "M003"];
60
+
61
+ // 3 cancelled sessions without cleanup
62
+ for (let i = 0; i < 3; i++) {
63
+ const allIds = [...new Set([...diskIds, ...getReservedMilestoneIds()])];
64
+ const preview = nextMilestoneId(allIds);
65
+ reserveMilestoneId(preview);
66
+ }
67
+
68
+ // Without cleanup, we're now at M007 instead of M004
69
+ const allIds = [...new Set([...diskIds, ...getReservedMilestoneIds()])];
70
+ const next = nextMilestoneId(allIds);
71
+ assert.equal(next, "M007", "3 cancelled sessions inflate ID by 3");
72
+
73
+ // With cleanup, we're back to M004
74
+ clearReservedMilestoneIds();
75
+ const allIdsClean = [...new Set([...diskIds, ...getReservedMilestoneIds()])];
76
+ const nextClean = nextMilestoneId(allIdsClean);
77
+ assert.equal(nextClean, "M004", "cleanup restores correct next ID");
78
+ });
79
+ });
@@ -661,7 +661,7 @@ async function handleMerge(
661
661
  // --- Deterministic merge path (preferred) ---
662
662
  // Try a direct squash-merge first. Only fall back to LLM on conflict.
663
663
  const commitType = inferCommitType(name);
664
- const commitMessage = `${commitType}(${name}): merge worktree ${name}`;
664
+ const commitMessage = `${commitType}: merge worktree ${name}\n\nGSD-Worktree: ${name}`;
665
665
 
666
666
  // Reconcile worktree DB into main DB before squash merge
667
667
  const wtDbPath = join(worktreePath(basePath, name), ".gsd", "gsd.db");
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[8974],{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)},15726:(e,t,n)=>{Promise.resolve().then(n.bind(n,66919))},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(6079),n.e(4986),n.e(485),n.e(4024)]).then(n.bind(n,4024)).then(e=>e.GSDAppShell),{loadableGenerated:{webpack:()=>[4024]},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=15726)),_N_E=e.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[7358],{2852:(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))},19393:()=>{}},e=>{var s=s=>e(e.s=s);e.O(0,[8441,3794],()=>(s(83861),s(2852))),_N_E=e.O()}]);
@@ -1 +0,0 @@
1
- (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[9337],{52560:(e,s,_)=>{Promise.resolve().then(_.t.bind(_,27123,23))}},e=>{e.O(0,[8441,3794,7358],()=>e(e.s=52560)),_N_E=e.O()}]);