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
@@ -8,7 +8,7 @@
8
8
  * data structure that is inspectable, testable per-rule, and extensible
9
9
  * without modifying orchestration code.
10
10
  */
11
- import { loadFile, loadActiveOverrides } from "./files.js";
11
+ import { loadFile, extractUatType, loadActiveOverrides } from "./files.js";
12
12
  import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js";
13
13
  import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveTaskFile, relSliceFile, buildMilestoneFileName, } from "./paths.js";
14
14
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
@@ -112,7 +112,22 @@ export const DISPATCH_RULES = [
112
112
  continue;
113
113
  const verdictMatch = content.match(/verdict:\s*([\w-]+)/i);
114
114
  const verdict = verdictMatch?.[1]?.toLowerCase();
115
- if (verdict && verdict !== "pass" && verdict !== "passed") {
115
+ // Determine acceptable verdicts based on UAT type.
116
+ // mixed / human-experience / live-runtime modes may legitimately
117
+ // produce PARTIAL when all automatable checks pass but human-only
118
+ // checks remain — this should not block progression.
119
+ const acceptableVerdicts = ["pass", "passed"];
120
+ const uatFile = resolveSliceFile(basePath, mid, sliceId, "UAT");
121
+ if (uatFile) {
122
+ const uatContent = await loadFile(uatFile);
123
+ if (uatContent) {
124
+ const uatType = extractUatType(uatContent);
125
+ if (uatType === "mixed" || uatType === "human-experience" || uatType === "live-runtime") {
126
+ acceptableVerdicts.push("partial");
127
+ }
128
+ }
129
+ }
130
+ if (verdict && !acceptableVerdicts.includes(verdict)) {
116
131
  return {
117
132
  action: "stop",
118
133
  reason: `UAT verdict for ${sliceId} is "${verdict}" — blocking progression until resolved.\nReview the UAT result and update the verdict to PASS, or re-run /gsd auto after fixing.`,
@@ -26,6 +26,15 @@ import { consumeSignal } from "./session-status-io.js";
26
26
  import { checkPostUnitHooks, isRetryPending, consumeRetryTrigger, persistHookState, resolveHookArtifactPath, } from "./post-unit-hooks.js";
27
27
  import { hasPendingCaptures, loadPendingCaptures } from "./captures.js";
28
28
  import { debugLog } from "./debug-logger.js";
29
+ /** Unit types that only touch `.gsd/` internal state files (no code changes).
30
+ * Auto-commit is skipped for these — their state files are picked up by the
31
+ * next actual task commit via `smartStage()`. */
32
+ const LIFECYCLE_ONLY_UNITS = new Set([
33
+ "research-milestone", "discuss-milestone", "plan-milestone",
34
+ "validate-milestone", "research-slice", "plan-slice",
35
+ "replan-slice", "complete-slice", "run-uat",
36
+ "reassess-roadmap", "rewrite-docs",
37
+ ]);
29
38
  import { existsSync, unlinkSync } from "node:fs";
30
39
  import { join } from "node:path";
31
40
  import { _resetHasChangesCache } from "./native-git-bridge.js";
@@ -210,9 +219,14 @@ export async function postUnitPreVerification(pctx, opts) {
210
219
  // code files only in the working tree where they are destroyed by
211
220
  // `git worktree remove --force` during teardown.
212
221
  _resetHasChangesCache();
213
- const commitMsg = autoCommitCurrentBranch(s.basePath, s.currentUnit.type, s.currentUnit.id, taskContext);
214
- if (commitMsg) {
215
- ctx.ui.notify(`Committed: ${commitMsg.split("\n")[0]}`, "info");
222
+ // Skip auto-commit for lifecycle-only units (#2553) they only touch
223
+ // `.gsd/` internal state files. Those files are picked up by the next
224
+ // actual task commit via smartStage().
225
+ if (!LIFECYCLE_ONLY_UNITS.has(s.currentUnit.type)) {
226
+ const commitMsg = autoCommitCurrentBranch(s.basePath, s.currentUnit.type, s.currentUnit.id, taskContext);
227
+ if (commitMsg) {
228
+ ctx.ui.notify(`Committed: ${commitMsg.split("\n")[0]}`, "info");
229
+ }
216
230
  }
217
231
  }
218
232
  catch (e) {
@@ -30,7 +30,7 @@ import { initRoutingHistory } from "./routing-history.js";
30
30
  import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
31
31
  import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
32
32
  import { snapshotSkills } from "./skill-discovery.js";
33
- import { isDbAvailable } from "./gsd-db.js";
33
+ import { isDbAvailable, getMilestone } from "./gsd-db.js";
34
34
  import { hideFooter } from "./auto-dashboard.js";
35
35
  import { debugLog, enableDebug, isDebugEnabled, getDebugLogPath, } from "./debug-logger.js";
36
36
  import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync, } from "node:fs";
@@ -503,6 +503,13 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
503
503
  if (milestoneIds.length > 1) {
504
504
  const issues = [];
505
505
  for (const id of milestoneIds) {
506
+ // Skip completed/parked milestones — a leftover CONTEXT-DRAFT.md
507
+ // on a finished milestone is harmless residue, not an actionable warning.
508
+ if (isDbAvailable()) {
509
+ const ms = getMilestone(id);
510
+ if (ms?.status === "complete" || ms?.status === "parked")
511
+ continue;
512
+ }
506
513
  const draft = resolveMilestoneFile(base, id, "CONTEXT-DRAFT");
507
514
  if (draft)
508
515
  issues.push(`${id}: has CONTEXT-DRAFT.md (will pause for discussion)`);
@@ -910,13 +910,16 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
910
910
  milestoneTitle = titleMatch[1].trim();
911
911
  }
912
912
  milestoneTitle = milestoneTitle || milestoneId;
913
- const subject = `feat(${milestoneId}): ${milestoneTitle}`;
913
+ const subject = `feat: ${milestoneTitle}`;
914
914
  let body = "";
915
915
  if (completedSlices.length > 0) {
916
916
  const sliceLines = completedSlices
917
917
  .map((s) => `- ${s.id}: ${s.title}`)
918
918
  .join("\n");
919
- body = `\n\nCompleted slices:\n${sliceLines}\n\nBranch: ${milestoneBranch}`;
919
+ body = `\n\nCompleted slices:\n${sliceLines}\n\nGSD-Milestone: ${milestoneId}\nBranch: ${milestoneBranch}`;
920
+ }
921
+ else {
922
+ body = `\n\nGSD-Milestone: ${milestoneId}\nBranch: ${milestoneBranch}`;
920
923
  }
921
924
  const commitMessage = subject + body;
922
925
  // 6b. Reconcile worktree HEAD with milestone branch ref (#1846).
@@ -1,7 +1,27 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
1
3
  import { enableDebug } from "../../debug-logger.js";
2
4
  import { isAutoActive, isAutoPaused, pauseAuto, startAuto, stopAuto, stopAutoRemote } from "../../auto.js";
3
5
  import { handleRate } from "../../commands-rate.js";
4
6
  import { guardRemoteSession, projectRoot } from "../context.js";
7
+ /**
8
+ * Parse --yolo flag and optional file path from the auto command string.
9
+ * Supports: `/gsd auto --yolo path/to/file.md` or `/gsd auto -y path/to/file.md`
10
+ */
11
+ function parseYoloFlag(trimmed) {
12
+ const yoloRe = /(?:--yolo|-y)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\S+)/;
13
+ const match = trimmed.match(yoloRe);
14
+ if (!match)
15
+ return { yoloSeedFile: null, rest: trimmed };
16
+ // Strip quotes if present
17
+ let filePath = match[1];
18
+ if ((filePath.startsWith('"') && filePath.endsWith('"')) ||
19
+ (filePath.startsWith("'") && filePath.endsWith("'"))) {
20
+ filePath = filePath.slice(1, -1);
21
+ }
22
+ const rest = trimmed.replace(match[0], "").replace(/\s+/g, " ").trim();
23
+ return { yoloSeedFile: filePath, rest };
24
+ }
5
25
  export async function handleAutoCommand(trimmed, ctx, pi) {
6
26
  if (trimmed === "next" || trimmed.startsWith("next ")) {
7
27
  if (trimmed.includes("--dry-run")) {
@@ -19,13 +39,33 @@ export async function handleAutoCommand(trimmed, ctx, pi) {
19
39
  return true;
20
40
  }
21
41
  if (trimmed === "auto" || trimmed.startsWith("auto ")) {
22
- const verboseMode = trimmed.includes("--verbose");
23
- const debugMode = trimmed.includes("--debug");
42
+ const { yoloSeedFile, rest } = parseYoloFlag(trimmed);
43
+ const verboseMode = rest.includes("--verbose");
44
+ const debugMode = rest.includes("--debug");
24
45
  if (debugMode)
25
46
  enableDebug(projectRoot());
26
47
  if (!(await guardRemoteSession(ctx, pi)))
27
48
  return true;
28
- await startAuto(ctx, pi, projectRoot(), verboseMode);
49
+ if (yoloSeedFile) {
50
+ const resolved = resolve(projectRoot(), yoloSeedFile);
51
+ if (!existsSync(resolved)) {
52
+ ctx.ui.notify(`Yolo seed file not found: ${resolved}`, "error");
53
+ return true;
54
+ }
55
+ const seedContent = readFileSync(resolved, "utf-8").trim();
56
+ if (!seedContent) {
57
+ ctx.ui.notify(`Yolo seed file is empty: ${resolved}`, "error");
58
+ return true;
59
+ }
60
+ // Headless path: bootstrap project, dispatch non-interactive discuss,
61
+ // then auto-mode starts automatically via checkAutoStartAfterDiscuss
62
+ // when the LLM says "Milestone X ready."
63
+ const { showHeadlessMilestoneCreation } = await import("../../guided-flow.js");
64
+ await showHeadlessMilestoneCreation(ctx, pi, projectRoot(), seedContent);
65
+ }
66
+ else {
67
+ await startAuto(ctx, pi, projectRoot(), verboseMode);
68
+ }
29
69
  return true;
30
70
  }
31
71
  if (trimmed === "stop") {
@@ -197,7 +197,11 @@ export async function buildForensicReport(basePath) {
197
197
  // Extensions run from ~/.gsd/agent/extensions/gsd/ at runtime, so path-traversal
198
198
  // from import.meta.url would resolve to ~/package.json (wrong on every system).
199
199
  const gsdVersion = process.env.GSD_VERSION || "unknown";
200
- // 9. Run anomaly detectors
200
+ // 9. Scan journal for flow timeline and structured events
201
+ const journalSummary = scanJournalForForensics(basePath);
202
+ // 10. Gather activity log directory metadata
203
+ const activityLogMeta = gatherActivityLogMeta(basePath, activeMilestone);
204
+ // 11. Run anomaly detectors
201
205
  if (metrics?.units)
202
206
  detectStuckLoops(metrics.units, anomalies);
203
207
  if (metrics?.units)
@@ -207,6 +211,7 @@ export async function buildForensicReport(basePath) {
207
211
  detectCrash(crashLock, anomalies);
208
212
  detectDoctorIssues(doctorIssues, anomalies);
209
213
  detectErrorTraces(unitTraces, anomalies);
214
+ detectJournalAnomalies(journalSummary, anomalies);
210
215
  return {
211
216
  gsdVersion,
212
217
  timestamp: new Date().toISOString(),
@@ -221,10 +226,14 @@ export async function buildForensicReport(basePath) {
221
226
  doctorIssues,
222
227
  anomalies,
223
228
  recentUnits,
229
+ journalSummary,
230
+ activityLogMeta,
224
231
  };
225
232
  }
226
233
  // ─── Activity Log Scanner ─────────────────────────────────────────────────────
227
234
  const ACTIVITY_FILENAME_RE = /^(\d+)-(.+?)-(.+)\.jsonl$/;
235
+ /** Threshold below which iteration cadence is considered rapid (thrashing). */
236
+ const RAPID_ITERATION_THRESHOLD_MS = 5000;
228
237
  function scanActivityLogs(basePath, activeMilestone) {
229
238
  const activityDirs = resolveActivityDirs(basePath, activeMilestone);
230
239
  const allTraces = [];
@@ -292,6 +301,152 @@ function resolveActivityDirs(basePath, activeMilestone) {
292
301
  dirs.push(rootActivityDir);
293
302
  return dirs;
294
303
  }
304
+ // ─── Journal Scanner ──────────────────────────────────────────────────────────
305
+ /**
306
+ * Max recent journal files to fully parse for event counts and recent events.
307
+ * Older files are line-counted only to avoid loading huge amounts of data.
308
+ */
309
+ const MAX_JOURNAL_RECENT_FILES = 3;
310
+ /** Max recent events to extract for the forensic report timeline. */
311
+ const MAX_JOURNAL_RECENT_EVENTS = 20;
312
+ /**
313
+ * Intelligently scan journal files for forensic summary.
314
+ *
315
+ * Journal files can be huge (thousands of JSONL entries over weeks of auto-mode).
316
+ * Instead of loading all entries into memory:
317
+ * - Only fully parse the most recent N daily files (event counts, flow tracking)
318
+ * - Line-count older files for approximate totals (no JSON parsing)
319
+ * - Extract only the last 20 events for the timeline
320
+ */
321
+ function scanJournalForForensics(basePath) {
322
+ try {
323
+ const journalDir = join(gsdRoot(basePath), "journal");
324
+ if (!existsSync(journalDir))
325
+ return null;
326
+ const files = readdirSync(journalDir).filter(f => f.endsWith(".jsonl")).sort();
327
+ if (files.length === 0)
328
+ return null;
329
+ // Split into recent (fully parsed) and older (line-counted only)
330
+ const recentFiles = files.slice(-MAX_JOURNAL_RECENT_FILES);
331
+ const olderFiles = files.slice(0, -MAX_JOURNAL_RECENT_FILES);
332
+ // Line-count older files without parsing — avoids loading megabytes of JSON
333
+ let olderEntryCount = 0;
334
+ let oldestEntry = null;
335
+ for (const file of olderFiles) {
336
+ try {
337
+ const raw = readFileSync(join(journalDir, file), "utf-8");
338
+ const lines = raw.split("\n");
339
+ for (const line of lines) {
340
+ if (!line.trim())
341
+ continue;
342
+ olderEntryCount++;
343
+ // Extract only the timestamp from the first non-empty line of the oldest file
344
+ if (!oldestEntry) {
345
+ try {
346
+ const parsed = JSON.parse(line);
347
+ if (parsed.ts)
348
+ oldestEntry = parsed.ts;
349
+ }
350
+ catch { /* skip malformed */ }
351
+ }
352
+ }
353
+ }
354
+ catch { /* skip unreadable files */ }
355
+ }
356
+ // Fully parse recent files for event counts and timeline
357
+ const eventCounts = {};
358
+ const flowIds = new Set();
359
+ const recentParsedEntries = [];
360
+ let recentEntryCount = 0;
361
+ for (const file of recentFiles) {
362
+ try {
363
+ const raw = readFileSync(join(journalDir, file), "utf-8");
364
+ for (const line of raw.split("\n")) {
365
+ if (!line.trim())
366
+ continue;
367
+ try {
368
+ const entry = JSON.parse(line);
369
+ recentEntryCount++;
370
+ eventCounts[entry.eventType] = (eventCounts[entry.eventType] ?? 0) + 1;
371
+ flowIds.add(entry.flowId);
372
+ if (!oldestEntry)
373
+ oldestEntry = entry.ts;
374
+ // Keep a rolling window of last N events — avoids accumulating unbounded arrays
375
+ recentParsedEntries.push({
376
+ ts: entry.ts,
377
+ flowId: entry.flowId,
378
+ eventType: entry.eventType,
379
+ rule: entry.rule,
380
+ unitId: entry.data?.unitId,
381
+ });
382
+ if (recentParsedEntries.length > MAX_JOURNAL_RECENT_EVENTS) {
383
+ recentParsedEntries.shift();
384
+ }
385
+ }
386
+ catch { /* skip malformed lines */ }
387
+ }
388
+ }
389
+ catch { /* skip unreadable files */ }
390
+ }
391
+ const totalEntries = olderEntryCount + recentEntryCount;
392
+ if (totalEntries === 0)
393
+ return null;
394
+ const newestEntry = recentParsedEntries.length > 0
395
+ ? recentParsedEntries[recentParsedEntries.length - 1].ts
396
+ : null;
397
+ return {
398
+ totalEntries,
399
+ flowCount: flowIds.size,
400
+ eventCounts,
401
+ recentEvents: recentParsedEntries,
402
+ oldestEntry,
403
+ newestEntry,
404
+ fileCount: files.length,
405
+ };
406
+ }
407
+ catch {
408
+ return null;
409
+ }
410
+ }
411
+ // ─── Activity Log Metadata ────────────────────────────────────────────────────
412
+ function gatherActivityLogMeta(basePath, activeMilestone) {
413
+ try {
414
+ const activityDirs = resolveActivityDirs(basePath, activeMilestone);
415
+ let fileCount = 0;
416
+ let totalSizeBytes = 0;
417
+ let oldestFile = null;
418
+ let newestFile = null;
419
+ let oldestMtime = Infinity;
420
+ let newestMtime = 0;
421
+ for (const activityDir of activityDirs) {
422
+ if (!existsSync(activityDir))
423
+ continue;
424
+ const files = readdirSync(activityDir).filter(f => f.endsWith(".jsonl"));
425
+ for (const file of files) {
426
+ const filePath = join(activityDir, file);
427
+ const stat = statSync(filePath, { throwIfNoEntry: false });
428
+ if (!stat)
429
+ continue;
430
+ fileCount++;
431
+ totalSizeBytes += stat.size;
432
+ if (stat.mtimeMs < oldestMtime) {
433
+ oldestMtime = stat.mtimeMs;
434
+ oldestFile = file;
435
+ }
436
+ if (stat.mtimeMs > newestMtime) {
437
+ newestMtime = stat.mtimeMs;
438
+ newestFile = file;
439
+ }
440
+ }
441
+ }
442
+ if (fileCount === 0)
443
+ return null;
444
+ return { fileCount, totalSizeBytes, oldestFile, newestFile };
445
+ }
446
+ catch {
447
+ return null;
448
+ }
449
+ }
295
450
  // ─── Completed Keys Loader ────────────────────────────────────────────────────
296
451
  function loadCompletedKeys(basePath) {
297
452
  const file = join(gsdRoot(basePath), "completed-units.json");
@@ -423,6 +578,64 @@ function detectErrorTraces(traces, anomalies) {
423
578
  }
424
579
  }
425
580
  }
581
+ function detectJournalAnomalies(journal, anomalies) {
582
+ if (!journal)
583
+ return;
584
+ // Detect stuck-detected events from the journal
585
+ const stuckCount = journal.eventCounts["stuck-detected"] ?? 0;
586
+ if (stuckCount > 0) {
587
+ anomalies.push({
588
+ type: "journal-stuck",
589
+ severity: stuckCount >= 3 ? "error" : "warning",
590
+ summary: `Journal recorded ${stuckCount} stuck-detected event(s)`,
591
+ details: `The auto-mode loop detected it was stuck ${stuckCount} time(s). Check journal events for flow IDs and causal chains to trace the root cause.`,
592
+ });
593
+ }
594
+ // Detect guard-block events (dispatch was blocked by a guard)
595
+ const guardCount = journal.eventCounts["guard-block"] ?? 0;
596
+ if (guardCount > 0) {
597
+ anomalies.push({
598
+ type: "journal-guard-block",
599
+ severity: guardCount >= 5 ? "warning" : "info",
600
+ summary: `Journal recorded ${guardCount} guard-block event(s)`,
601
+ details: `Dispatch was blocked by a guard condition ${guardCount} time(s). This may indicate a persistent blocking condition preventing progress.`,
602
+ });
603
+ }
604
+ // Detect rapid iterations (many flows in short time = likely thrashing)
605
+ if (journal.flowCount > 0 && journal.oldestEntry && journal.newestEntry) {
606
+ const oldest = new Date(journal.oldestEntry).getTime();
607
+ const newest = new Date(journal.newestEntry).getTime();
608
+ const spanMs = newest - oldest;
609
+ if (spanMs > 0 && journal.flowCount > 10) {
610
+ const avgMs = spanMs / journal.flowCount;
611
+ if (avgMs < RAPID_ITERATION_THRESHOLD_MS) {
612
+ anomalies.push({
613
+ type: "journal-rapid-iterations",
614
+ severity: "warning",
615
+ summary: `${journal.flowCount} iterations in ${formatDuration(spanMs)} (avg ${formatDuration(avgMs)}/iteration)`,
616
+ details: `Unusually rapid iteration cadence suggests the loop may be thrashing without making progress. Review recent journal events for dispatch-stop or terminal events.`,
617
+ });
618
+ }
619
+ }
620
+ }
621
+ // Detect worktree failures from journal events
622
+ const wtCreateFailed = journal.eventCounts["worktree-create-failed"] ?? 0;
623
+ const wtMergeFailed = journal.eventCounts["worktree-merge-failed"] ?? 0;
624
+ const wtFailures = wtCreateFailed + wtMergeFailed;
625
+ if (wtFailures > 0) {
626
+ const parts = [];
627
+ if (wtCreateFailed > 0)
628
+ parts.push(`${wtCreateFailed} create failure(s)`);
629
+ if (wtMergeFailed > 0)
630
+ parts.push(`${wtMergeFailed} merge failure(s)`);
631
+ anomalies.push({
632
+ type: "journal-worktree-failure",
633
+ severity: "warning",
634
+ summary: `Worktree failures: ${parts.join(", ")}`,
635
+ details: `Journal recorded worktree operation failures. These may indicate git state corruption or conflicting branches.`,
636
+ });
637
+ }
638
+ }
426
639
  // ─── Report Persistence ───────────────────────────────────────────────────────
427
640
  function saveForensicReport(basePath, report, problemDescription) {
428
641
  const dir = join(gsdRoot(basePath), "forensics");
@@ -491,6 +704,48 @@ function saveForensicReport(basePath, report, problemDescription) {
491
704
  sections.push(`## Crash Lock`, ``);
492
705
  sections.push(redact(formatCrashInfo(report.crashLock)), ``);
493
706
  }
707
+ // Activity log metadata
708
+ if (report.activityLogMeta) {
709
+ const meta = report.activityLogMeta;
710
+ sections.push(`## Activity Log Metadata`, ``);
711
+ sections.push(`- Files: ${meta.fileCount}`);
712
+ sections.push(`- Total size: ${(meta.totalSizeBytes / 1024).toFixed(1)} KB`);
713
+ if (meta.oldestFile)
714
+ sections.push(`- Oldest: ${meta.oldestFile}`);
715
+ if (meta.newestFile)
716
+ sections.push(`- Newest: ${meta.newestFile}`);
717
+ sections.push(``);
718
+ }
719
+ // Journal summary
720
+ if (report.journalSummary) {
721
+ const js = report.journalSummary;
722
+ sections.push(`## Journal Summary`, ``);
723
+ sections.push(`- Total entries: ${js.totalEntries}`);
724
+ sections.push(`- Distinct flows (iterations): ${js.flowCount}`);
725
+ sections.push(`- Daily files: ${js.fileCount}`);
726
+ if (js.oldestEntry)
727
+ sections.push(`- Date range: ${js.oldestEntry} — ${js.newestEntry}`);
728
+ sections.push(``);
729
+ sections.push(`### Event Type Distribution`, ``);
730
+ sections.push(`| Event Type | Count |`);
731
+ sections.push(`|------------|-------|`);
732
+ for (const [evType, count] of Object.entries(js.eventCounts).sort((a, b) => b[1] - a[1])) {
733
+ sections.push(`| ${evType} | ${count} |`);
734
+ }
735
+ sections.push(``);
736
+ if (js.recentEvents.length > 0) {
737
+ sections.push(`### Recent Journal Events (last ${js.recentEvents.length})`, ``);
738
+ for (const ev of js.recentEvents) {
739
+ const parts = [`${ev.ts} [${ev.eventType}] flow=${ev.flowId.slice(0, 8)}`];
740
+ if (ev.rule)
741
+ parts.push(`rule=${ev.rule}`);
742
+ if (ev.unitId)
743
+ parts.push(`unit=${ev.unitId}`);
744
+ sections.push(`- ${parts.join(" ")}`);
745
+ }
746
+ sections.push(``);
747
+ }
748
+ }
494
749
  writeFileSync(filePath, sections.join("\n"), "utf-8");
495
750
  return filePath;
496
751
  }
@@ -565,6 +820,42 @@ function formatReportForPrompt(report) {
565
820
  sections.push(`- Total duration: ${formatDuration(totals.duration)}`);
566
821
  sections.push("");
567
822
  }
823
+ // Activity log metadata
824
+ if (report.activityLogMeta) {
825
+ const meta = report.activityLogMeta;
826
+ sections.push("### Activity Log Overview");
827
+ sections.push(`- Files: ${meta.fileCount}, Total size: ${(meta.totalSizeBytes / 1024).toFixed(1)} KB`);
828
+ if (meta.oldestFile)
829
+ sections.push(`- Oldest: ${meta.oldestFile}`);
830
+ if (meta.newestFile)
831
+ sections.push(`- Newest: ${meta.newestFile}`);
832
+ sections.push("");
833
+ }
834
+ // Journal summary — structured event timeline
835
+ if (report.journalSummary) {
836
+ const js = report.journalSummary;
837
+ sections.push("### Journal Summary (Iteration Event Log)");
838
+ sections.push(`- Total entries: ${js.totalEntries}, Distinct flows: ${js.flowCount}, Daily files: ${js.fileCount}`);
839
+ if (js.oldestEntry)
840
+ sections.push(`- Date range: ${js.oldestEntry} — ${js.newestEntry}`);
841
+ // Event type distribution (compact)
842
+ const eventPairs = Object.entries(js.eventCounts).sort((a, b) => b[1] - a[1]);
843
+ sections.push(`- Events: ${eventPairs.map(([t, c]) => `${t}(${c})`).join(", ")}`);
844
+ // Recent events timeline (for tracing what just happened)
845
+ if (js.recentEvents.length > 0) {
846
+ sections.push("");
847
+ sections.push(`**Recent Journal Events (last ${js.recentEvents.length}):**`);
848
+ for (const ev of js.recentEvents) {
849
+ const parts = [`${ev.ts} [${ev.eventType}] flow=${ev.flowId.slice(0, 8)}`];
850
+ if (ev.rule)
851
+ parts.push(`rule=${ev.rule}`);
852
+ if (ev.unitId)
853
+ parts.push(`unit=${ev.unitId}`);
854
+ sections.push(`- ${parts.join(" ")}`);
855
+ }
856
+ }
857
+ sections.push("");
858
+ }
568
859
  // Completed keys count
569
860
  sections.push(`### Completed Keys: ${report.completedKeys.length}`);
570
861
  sections.push(`### GSD Version: ${report.gsdVersion}`);
@@ -20,21 +20,23 @@ import { getErrorMessage } from "./error-utils.js";
20
20
  export const VALID_BRANCH_NAME = /^[a-zA-Z0-9_\-\/.]+$/;
21
21
  /**
22
22
  * Build a meaningful conventional commit message from task execution context.
23
- * Format: `{type}({sliceId}/{taskId}): {description}`
23
+ * Format: `{type}: {description}` (clean conventional commit — no GSD IDs in subject).
24
+ *
25
+ * GSD metadata is placed in a `GSD-Task:` git trailer at the end of the body,
26
+ * following the same convention as `Signed-off-by:` or `Co-Authored-By:`.
24
27
  *
25
28
  * The description is the task summary one-liner if available (it describes
26
29
  * what was actually built), falling back to the task title (what was planned).
27
30
  */
28
31
  export function buildTaskCommitMessage(ctx) {
29
- const scope = ctx.taskId; // e.g. "S01/T02" or just "T02"
30
32
  const description = ctx.oneLiner || ctx.taskTitle;
31
33
  const type = inferCommitType(ctx.taskTitle, ctx.oneLiner);
32
- // Truncate description to ~72 chars for subject line
33
- const maxDescLen = 68 - type.length - scope.length;
34
+ // Truncate description to ~72 chars for subject line (full budget without scope)
35
+ const maxDescLen = 70 - type.length;
34
36
  const truncated = description.length > maxDescLen
35
37
  ? description.slice(0, maxDescLen - 1).trimEnd() + "…"
36
38
  : description;
37
- const subject = `${type}(${scope}): ${truncated}`;
39
+ const subject = `${type}: ${truncated}`;
38
40
  // Build body with key files if available
39
41
  const bodyParts = [];
40
42
  if (ctx.keyFiles && ctx.keyFiles.length > 0) {
@@ -44,13 +46,12 @@ export function buildTaskCommitMessage(ctx) {
44
46
  .join("\n");
45
47
  bodyParts.push(fileLines);
46
48
  }
49
+ // Trailers: GSD-Task first, then Resolves
50
+ bodyParts.push(`GSD-Task: ${ctx.taskId}`);
47
51
  if (ctx.issueNumber) {
48
52
  bodyParts.push(`Resolves #${ctx.issueNumber}`);
49
53
  }
50
- if (bodyParts.length > 0) {
51
- return `${subject}\n\n${bodyParts.join("\n\n")}`;
52
- }
53
- return subject;
54
+ return `${subject}\n\n${bodyParts.join("\n\n")}`;
54
55
  }
55
56
  /**
56
57
  * Thrown when a slice merge hits code conflicts in non-.gsd files.
@@ -385,7 +386,7 @@ export class GitServiceImpl {
385
386
  return null;
386
387
  const message = taskContext
387
388
  ? buildTaskCommitMessage(taskContext)
388
- : `chore(${unitId}): auto-commit after ${unitType}`;
389
+ : `chore: auto-commit after ${unitType}\n\nGSD-Unit: ${unitId}`;
389
390
  nativeCommit(this.basePath, message, { allowEmpty: false });
390
391
  return message;
391
392
  }