gsd-pi 2.77.0-dev.c9c8932c6 → 2.77.0-dev.eaa4973bc

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 (245) hide show
  1. package/dist/cli-web-branch.d.ts +1 -0
  2. package/dist/cli-web-branch.js +3 -0
  3. package/dist/cli.js +24 -2
  4. package/dist/provider-migrations.d.ts +18 -0
  5. package/dist/provider-migrations.js +14 -0
  6. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +14 -10
  7. package/dist/resources/extensions/gsd/abandon-detect.js +44 -0
  8. package/dist/resources/extensions/gsd/auto/resolve.js +24 -0
  9. package/dist/resources/extensions/gsd/auto/run-unit.js +10 -2
  10. package/dist/resources/extensions/gsd/auto/turn-epoch.js +95 -0
  11. package/dist/resources/extensions/gsd/auto-dispatch.js +24 -0
  12. package/dist/resources/extensions/gsd/auto-loop.js +1 -1
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +31 -0
  14. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +11 -5
  15. package/dist/resources/extensions/gsd/auto-unit-closeout.js +11 -2
  16. package/dist/resources/extensions/gsd/auto-worktree.js +58 -8
  17. package/dist/resources/extensions/gsd/auto.js +4 -0
  18. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +4 -2
  19. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +31 -4
  20. package/dist/resources/extensions/gsd/file-lock.js +49 -9
  21. package/dist/resources/extensions/gsd/git-service.js +1 -0
  22. package/dist/resources/extensions/gsd/gitignore.js +1 -0
  23. package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -1
  24. package/dist/resources/extensions/gsd/journal.js +17 -2
  25. package/dist/resources/extensions/gsd/milestone-actions.js +15 -0
  26. package/dist/resources/extensions/gsd/reports.js +5 -4
  27. package/dist/resources/extensions/gsd/tools/complete-slice.js +21 -0
  28. package/dist/resources/extensions/gsd/tools/complete-task.js +31 -0
  29. package/dist/resources/extensions/gsd/uok/audit.js +18 -2
  30. package/dist/resources/extensions/gsd/workflow-logger.js +10 -2
  31. package/dist/resources/extensions/gsd/worktree-manager.js +1 -0
  32. package/dist/resources/extensions/mcp-client/auth.js +10 -1
  33. package/dist/resources/extensions/mcp-client/index.js +118 -9
  34. package/dist/resources/skills/create-skill/SKILL.md +2 -2
  35. package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +4 -4
  36. package/dist/resources/skills/create-skill/workflows/audit-skill.md +4 -4
  37. package/dist/resources/skills/create-skill/workflows/create-new-skill.md +5 -5
  38. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  39. package/dist/web/standalone/.next/BUILD_ID +1 -1
  40. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  41. package/dist/web/standalone/.next/build-manifest.json +3 -3
  42. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  43. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  62. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  64. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  66. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  68. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  70. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  72. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  74. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  76. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  78. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  80. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  82. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  84. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  86. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  88. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  90. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  92. package/dist/web/standalone/.next/server/app/api/notifications/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  94. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  96. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  98. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  100. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  102. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  104. package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  106. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  108. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  110. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  112. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  114. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  116. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  118. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  120. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +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.js.nft.json +1 -1
  124. package/dist/web/standalone/.next/server/app/index.html +1 -1
  125. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  126. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  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 +1 -1
  130. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  132. package/dist/web/standalone/.next/server/chunks/1926.js +1 -0
  133. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  134. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  136. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  138. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  139. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  140. package/dist/web/standalone/.next/static/chunks/2826.e9f5195e91f9cad2.js +11 -0
  141. package/dist/web/standalone/.next/static/chunks/{webpack-1832629448831fdc.js → webpack-2e68521d7c82f7c2.js} +1 -1
  142. package/package.json +3 -3
  143. package/packages/mcp-server/README.md +3 -3
  144. package/packages/mcp-server/dist/env-writer.d.ts +1 -0
  145. package/packages/mcp-server/dist/env-writer.d.ts.map +1 -1
  146. package/packages/mcp-server/dist/env-writer.js +74 -6
  147. package/packages/mcp-server/dist/env-writer.js.map +1 -1
  148. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  149. package/packages/mcp-server/dist/server.js +25 -2
  150. package/packages/mcp-server/dist/server.js.map +1 -1
  151. package/packages/mcp-server/src/env-writer.test.ts +79 -1
  152. package/packages/mcp-server/src/env-writer.ts +76 -6
  153. package/packages/mcp-server/src/server.ts +29 -2
  154. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  155. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts +1 -0
  156. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +21 -16
  158. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.d.ts +2 -0
  160. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.d.ts.map +1 -0
  161. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.js +130 -0
  162. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.js.map +1 -0
  163. package/packages/pi-coding-agent/dist/core/sdk.d.ts +1 -0
  164. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  165. package/packages/pi-coding-agent/dist/core/sdk.js +4 -1
  166. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  167. package/packages/pi-coding-agent/dist/core/sdk.test.js +19 -1
  168. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -1
  169. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +15 -6
  170. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -1
  171. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -5
  173. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +7 -1
  175. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  176. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +31 -9
  177. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  178. package/packages/pi-coding-agent/src/core/compaction-orchestrator.test.ts +154 -0
  179. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +24 -17
  180. package/packages/pi-coding-agent/src/core/sdk.test.ts +25 -1
  181. package/packages/pi-coding-agent/src/core/sdk.ts +10 -3
  182. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +17 -7
  183. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +14 -5
  184. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +45 -11
  185. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  186. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +19 -0
  187. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
  188. package/packages/pi-tui/dist/stdin-buffer.d.ts +7 -0
  189. package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
  190. package/packages/pi-tui/dist/stdin-buffer.js +20 -0
  191. package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
  192. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +25 -0
  193. package/packages/pi-tui/src/stdin-buffer.ts +26 -0
  194. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  195. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +14 -10
  196. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +22 -16
  197. package/src/resources/extensions/gsd/abandon-detect.ts +62 -0
  198. package/src/resources/extensions/gsd/auto/resolve.ts +29 -0
  199. package/src/resources/extensions/gsd/auto/run-unit.ts +16 -2
  200. package/src/resources/extensions/gsd/auto/turn-epoch.ts +108 -0
  201. package/src/resources/extensions/gsd/auto-dispatch.ts +20 -0
  202. package/src/resources/extensions/gsd/auto-loop.ts +1 -1
  203. package/src/resources/extensions/gsd/auto-post-unit.ts +30 -0
  204. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -5
  205. package/src/resources/extensions/gsd/auto-unit-closeout.ts +14 -3
  206. package/src/resources/extensions/gsd/auto-worktree.ts +60 -5
  207. package/src/resources/extensions/gsd/auto.ts +5 -0
  208. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +6 -2
  209. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +27 -8
  210. package/src/resources/extensions/gsd/file-lock.ts +84 -11
  211. package/src/resources/extensions/gsd/git-service.ts +1 -0
  212. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  213. package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -1
  214. package/src/resources/extensions/gsd/journal.ts +27 -2
  215. package/src/resources/extensions/gsd/milestone-actions.ts +18 -0
  216. package/src/resources/extensions/gsd/reports.ts +5 -4
  217. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +11 -2
  218. package/src/resources/extensions/gsd/tests/auto-mode-guards.test.ts +79 -0
  219. package/src/resources/extensions/gsd/tests/file-lock.test.ts +86 -12
  220. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +30 -0
  221. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +3 -2
  222. package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +76 -0
  223. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +39 -25
  224. package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +181 -0
  225. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +13 -7
  226. package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +170 -0
  227. package/src/resources/extensions/gsd/tests/rewrite-docs-abandon-detect.test.ts +195 -0
  228. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +2 -2
  229. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +50 -2
  230. package/src/resources/extensions/gsd/tests/turn-epoch.test.ts +162 -0
  231. package/src/resources/extensions/gsd/tools/complete-slice.ts +38 -0
  232. package/src/resources/extensions/gsd/tools/complete-task.ts +49 -0
  233. package/src/resources/extensions/gsd/uok/audit.ts +20 -2
  234. package/src/resources/extensions/gsd/workflow-logger.ts +22 -3
  235. package/src/resources/extensions/gsd/worktree-manager.ts +1 -0
  236. package/src/resources/extensions/mcp-client/auth.ts +12 -1
  237. package/src/resources/extensions/mcp-client/index.ts +129 -10
  238. package/src/resources/skills/create-skill/SKILL.md +2 -2
  239. package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +4 -4
  240. package/src/resources/skills/create-skill/workflows/audit-skill.md +4 -4
  241. package/src/resources/skills/create-skill/workflows/create-new-skill.md +5 -5
  242. package/dist/web/standalone/.next/server/chunks/7461.js +0 -1
  243. package/dist/web/standalone/.next/static/chunks/2826.d445fb428ef41fa1.js +0 -9
  244. /package/dist/web/standalone/.next/static/{H51SzzS_EEgdBsDVhrLBv → 5wbu35_C2_MQ3Jj1lEVDx}/_buildManifest.js +0 -0
  245. /package/dist/web/standalone/.next/static/{H51SzzS_EEgdBsDVhrLBv → 5wbu35_C2_MQ3Jj1lEVDx}/_ssgManifest.js +0 -0
@@ -52,7 +52,7 @@ test("withFileLock: executes callback when file does not exist", async () => {
52
52
  }
53
53
  });
54
54
 
55
- test("withFileLockSync: falls back to unlocked callback on ELOCKED", () => {
55
+ test("withFileLockSync: throws ELOCKED by default (no silent fallback)", () => {
56
56
  if (!hasProperLockfile() || process.platform === "win32") {
57
57
  return;
58
58
  }
@@ -65,19 +65,56 @@ test("withFileLockSync: falls back to unlocked callback on ELOCKED", () => {
65
65
  const release = lockfile.lockSync(filePath, { retries: 0, stale: 10000 });
66
66
  try {
67
67
  let called = 0;
68
- const result = withFileLockSync(filePath, () => {
69
- called++;
70
- return "fallback-ok";
71
- });
68
+ assert.throws(
69
+ () => {
70
+ withFileLockSync(
71
+ filePath,
72
+ () => {
73
+ called++;
74
+ return "should-not-return";
75
+ },
76
+ { retries: 0 },
77
+ );
78
+ },
79
+ (err: any) => err?.code === "ELOCKED",
80
+ );
81
+ assert.equal(called, 0, "callback must not run when lock cannot be acquired");
82
+ } finally {
83
+ release();
84
+ rmSync(dir, { recursive: true, force: true });
85
+ }
86
+ });
87
+
88
+ test("withFileLockSync: onLocked=\"skip\" runs callback unlocked on ELOCKED", () => {
89
+ if (!hasProperLockfile() || process.platform === "win32") {
90
+ return;
91
+ }
92
+
93
+ const lockfile = require("proper-lockfile");
94
+ const dir = mkdtempSync(join(tmpdir(), "gsd-file-lock-test-"));
95
+ const filePath = join(dir, "locked.jsonl");
96
+ writeFileSync(filePath, "{}\n", "utf-8");
97
+
98
+ const release = lockfile.lockSync(filePath, { retries: 0, stale: 10000 });
99
+ try {
100
+ let called = 0;
101
+ const result = withFileLockSync(
102
+ filePath,
103
+ () => {
104
+ called++;
105
+ return "fallback-ok";
106
+ },
107
+ { retries: 0, onLocked: "skip" },
108
+ );
72
109
  assert.equal(result, "fallback-ok");
73
- assert.equal(called, 1, "callback should run even when lock acquisition fails");
110
+ assert.equal(called, 1, "callback should run when onLocked is skip");
74
111
  } finally {
75
112
  release();
76
113
  rmSync(dir, { recursive: true, force: true });
77
114
  }
78
115
  });
79
116
 
80
- test("withFileLock: falls back to unlocked callback on ELOCKED", async () => {
117
+ test("withFileLock: throws ELOCKED by default (no silent fallback)", async () => {
81
118
  if (!hasProperLockfile() || process.platform === "win32") {
82
119
  return;
83
120
  }
@@ -90,12 +127,49 @@ test("withFileLock: falls back to unlocked callback on ELOCKED", async () => {
90
127
  const release = await lockfile.lock(filePath, { retries: 0, stale: 10000 });
91
128
  try {
92
129
  let called = 0;
93
- const result = await withFileLock(filePath, async () => {
94
- called++;
95
- return "fallback-ok";
96
- });
130
+ await assert.rejects(
131
+ async () => {
132
+ await withFileLock(
133
+ filePath,
134
+ async () => {
135
+ called++;
136
+ return "should-not-return";
137
+ },
138
+ { retries: 0 },
139
+ );
140
+ },
141
+ (err: any) => err?.code === "ELOCKED",
142
+ );
143
+ assert.equal(called, 0, "callback must not run when lock cannot be acquired");
144
+ } finally {
145
+ await release();
146
+ rmSync(dir, { recursive: true, force: true });
147
+ }
148
+ });
149
+
150
+ test("withFileLock: onLocked=\"skip\" runs callback unlocked on ELOCKED", async () => {
151
+ if (!hasProperLockfile() || process.platform === "win32") {
152
+ return;
153
+ }
154
+
155
+ const lockfile = require("proper-lockfile");
156
+ const dir = mkdtempSync(join(tmpdir(), "gsd-file-lock-test-"));
157
+ const filePath = join(dir, "locked.jsonl");
158
+ writeFileSync(filePath, "{}\n", "utf-8");
159
+
160
+ const release = await lockfile.lock(filePath, { retries: 0, stale: 10000 });
161
+ try {
162
+ let called = 0;
163
+ const result = await withFileLock(
164
+ filePath,
165
+ async () => {
166
+ called++;
167
+ return "fallback-ok";
168
+ },
169
+ { retries: 0, onLocked: "skip" },
170
+ );
97
171
  assert.equal(result, "fallback-ok");
98
- assert.equal(called, 1, "callback should run even when lock acquisition fails");
172
+ assert.equal(called, 1, "callback should run when onLocked is skip");
99
173
  } finally {
100
174
  await release();
101
175
  rmSync(dir, { recursive: true, force: true });
@@ -854,4 +854,34 @@ describe("auto-worktree-milestone-merge", { timeout: 300_000 }, () => {
854
854
  "#1906: codeFilesChanged must be true when real code files were merged");
855
855
  assert.ok(existsSync(join(repo, "real-code.ts")), "real-code.ts merged to main");
856
856
  });
857
+
858
+ // #2505 regression: when a per-entry restore of the milestone shelter fails,
859
+ // the shelter must be retained so the queued milestone files (whose sources
860
+ // were deleted during the shelter step) remain recoverable. Deleting the
861
+ // shelter unconditionally would permanently lose that data.
862
+ test("#2505: shelter retained when restore fails; cleaned up on success", () => {
863
+ const repo = freshRepo();
864
+ const wtPath = createAutoWorktree(repo, "M200");
865
+
866
+ addSliceToMilestone(repo, wtPath, "M200", "S01", "Feature", [
867
+ { file: "feature.ts", content: "export const f = 1;\n", message: "add feature" },
868
+ ]);
869
+
870
+ // Seed a queued (non-target) milestone in .gsd/milestones/ that will be
871
+ // sheltered during the merge and restored afterwards.
872
+ const queuedDir = join(repo, ".gsd", "milestones", "M201");
873
+ mkdirSync(queuedDir, { recursive: true });
874
+ writeFileSync(join(queuedDir, "CONTEXT.md"), "# queued\n");
875
+
876
+ const roadmap = makeRoadmap("M200", "Milestone w/ queued sibling", [
877
+ { id: "S01", title: "Feature" },
878
+ ]);
879
+
880
+ const result = mergeMilestoneToMain(repo, "M200", roadmap);
881
+
882
+ // Normal success path: queued milestone restored, shelter cleaned up.
883
+ assert.ok(existsSync(join(queuedDir, "CONTEXT.md")), "queued milestone restored from shelter");
884
+ assert.ok(!existsSync(join(repo, ".gsd", ".milestone-shelter")), "shelter removed on successful restore");
885
+ assert.ok(result.commitMessage.length > 0, "merge completed");
886
+ });
857
887
  });
@@ -248,12 +248,13 @@ describe('git-service', async () => {
248
248
 
249
249
  assert.deepStrictEqual(
250
250
  RUNTIME_EXCLUSION_PATHS.length,
251
- 15,
252
- "exactly 15 runtime exclusion paths"
251
+ 16,
252
+ "exactly 16 runtime exclusion paths"
253
253
  );
254
254
 
255
255
  const expectedPaths = [
256
256
  ".gsd/activity/",
257
+ ".gsd/audit/",
257
258
  ".gsd/forensics/",
258
259
  ".gsd/runtime/",
259
260
  ".gsd/worktrees/",
@@ -0,0 +1,76 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { readFileSync } from "node:fs";
4
+
5
+ import {
6
+ _buildMcpChildEnvForTest,
7
+ _buildMcpTrustConfirmOptionsForTest,
8
+ } from "../../mcp-client/index.ts";
9
+
10
+ test("MCP stdio child env only includes safe inherited keys plus explicit config env", () => {
11
+ const previousSecret = process.env.SECRET_MCP_TEST_TOKEN;
12
+ const previousPath = process.env.PATH;
13
+ try {
14
+ process.env.SECRET_MCP_TEST_TOKEN = "should-not-leak";
15
+ process.env.PATH = "/usr/bin";
16
+
17
+ const env = _buildMcpChildEnvForTest({
18
+ EXPLICIT_TOKEN: "${SECRET_MCP_TEST_TOKEN}",
19
+ PLAIN_VALUE: "ok",
20
+ });
21
+
22
+ assert.equal(env.PATH, "/usr/bin");
23
+ assert.equal(env.SECRET_MCP_TEST_TOKEN, undefined);
24
+ assert.equal(env.EXPLICIT_TOKEN, "should-not-leak");
25
+ assert.equal(env.PLAIN_VALUE, "ok");
26
+ } finally {
27
+ if (previousSecret === undefined) delete process.env.SECRET_MCP_TEST_TOKEN;
28
+ else process.env.SECRET_MCP_TEST_TOKEN = previousSecret;
29
+ if (previousPath === undefined) delete process.env.PATH;
30
+ else process.env.PATH = previousPath;
31
+ }
32
+ });
33
+
34
+ test("MCP stdio trust confirmation is abort-aware", () => {
35
+ const controller = new AbortController();
36
+ const options = _buildMcpTrustConfirmOptionsForTest(controller.signal);
37
+
38
+ assert.equal(options.timeout, 120_000);
39
+ assert.equal(options.signal, controller.signal);
40
+ });
41
+
42
+ test("MCP client uses a single in-flight connection per canonical server", () => {
43
+ const source = readFileSync(new URL("../../mcp-client/index.ts", import.meta.url), "utf8");
44
+
45
+ assert.match(source, /const pendingConnections = new Map<string, Promise<Client>>\(\)/);
46
+ assert.match(source, /const pending = pendingConnections\.get\(config\.name\)/);
47
+ assert.match(source, /pendingConnections\.set\(config\.name, connectionPromise\)/);
48
+ assert.match(source, /pendingConnections\.delete\(config\.name\)/);
49
+ assert.match(source, /env: config\.env \?\? \{\}/);
50
+ });
51
+
52
+ test("MCP stdio trust is persisted only after a successful connection", () => {
53
+ const source = readFileSync(new URL("../../mcp-client/index.ts", import.meta.url), "utf8");
54
+ const connectIndex = source.indexOf("await client.connect(transport");
55
+ const trustIndex = source.indexOf("trustedStdioServers.add(approvedTrustKey)");
56
+
57
+ assert.ok(connectIndex > -1, "connectServer should await client.connect");
58
+ assert.ok(trustIndex > connectIndex, "trust should be recorded after client.connect succeeds");
59
+ assert.doesNotMatch(source, /assertTrustedStdioServer[\s\S]*trustedStdioServers\.add\(trustKey\)/);
60
+ });
61
+
62
+ test("MCP client closes transports after failed connection attempts", () => {
63
+ const source = readFileSync(new URL("../../mcp-client/index.ts", import.meta.url), "utf8");
64
+
65
+ assert.match(source, /catch \(err\) \{[\s\S]*await transport\.close\(\)/);
66
+ assert.match(source, /catch \(err\) \{[\s\S]*await client\.close\(\)/);
67
+ assert.match(source, /catch \(err\) \{[\s\S]*throw err/);
68
+ });
69
+
70
+ test("MCP client clears process-local trust and closes transports on session cleanup", () => {
71
+ const source = readFileSync(new URL("../../mcp-client/index.ts", import.meta.url), "utf8");
72
+
73
+ assert.match(source, /async function closeAll\(\)[\s\S]*await conn\.transport\.close\(\)/);
74
+ assert.match(source, /async function closeAll\(\)[\s\S]*pendingConnections\.clear\(\)/);
75
+ assert.match(source, /async function closeAll\(\)[\s\S]*trustedStdioServers\.clear\(\)/);
76
+ });
@@ -404,6 +404,8 @@ test("resumeAutoAfterProviderDelay restarts paused auto-mode from the recorded b
404
404
  stepMode: true,
405
405
  basePath: "/tmp/project",
406
406
  }),
407
+ resetTransientRetryState: () => {},
408
+ resetSessionTimeoutState: () => {},
407
409
  startAuto: async (_ctx, _pi, base, verboseMode, options) => {
408
410
  startCalls.push({ base, verboseMode, step: options?.step });
409
411
  },
@@ -428,6 +430,8 @@ test("resumeAutoAfterProviderDelay does not double-start when auto-mode is alrea
428
430
  stepMode: false,
429
431
  basePath: "/tmp/project",
430
432
  }),
433
+ resetTransientRetryState: () => {},
434
+ resetSessionTimeoutState: () => {},
431
435
  startAuto: async () => {
432
436
  startCalls += 1;
433
437
  },
@@ -458,6 +462,8 @@ test("resumeAutoAfterProviderDelay leaves auto paused when no base path is avail
458
462
  stepMode: false,
459
463
  basePath: "",
460
464
  }),
465
+ resetTransientRetryState: () => {},
466
+ resetSessionTimeoutState: () => {},
461
467
  startAuto: async () => {
462
468
  startCalls += 1;
463
469
  },
@@ -474,6 +480,39 @@ test("resumeAutoAfterProviderDelay leaves auto paused when no base path is avail
474
480
  ]);
475
481
  });
476
482
 
483
+ test("resumeAutoAfterProviderDelay resets provider retry state before restarting auto-mode", async () => {
484
+ const calls: string[] = [];
485
+
486
+ const result = await resumeAutoAfterProviderDelay(
487
+ {} as any,
488
+ { ui: { notify() {} } } as any,
489
+ {
490
+ getSnapshot: () => ({
491
+ active: false,
492
+ paused: true,
493
+ stepMode: false,
494
+ basePath: "/tmp/project",
495
+ }),
496
+ resetTransientRetryState: () => {
497
+ calls.push("reset-transient");
498
+ },
499
+ resetSessionTimeoutState: () => {
500
+ calls.push("reset-session-timeout");
501
+ },
502
+ startAuto: async () => {
503
+ calls.push("start-auto");
504
+ },
505
+ },
506
+ );
507
+
508
+ assert.equal(result, "resumed");
509
+ assert.deepEqual(calls, [
510
+ "reset-transient",
511
+ "reset-session-timeout",
512
+ "start-auto",
513
+ ]);
514
+ });
515
+
477
516
  // ── Escalating backoff for transient errors (#1166) ─────────────────────────
478
517
 
479
518
  test("agent-end-recovery.ts tracks consecutive transient errors for escalating backoff", () => {
@@ -567,31 +606,6 @@ test("resetTransientRetryState is exported from agent-end-recovery.ts", () => {
567
606
  );
568
607
  });
569
608
 
570
- test("provider-error-resume.ts calls resetTransientRetryState before startAuto", () => {
571
- const src = readFileSync(join(__dirname, "..", "bootstrap", "provider-error-resume.ts"), "utf-8");
572
- assert.ok(
573
- src.includes("resetTransientRetryState"),
574
- "provider-error-resume.ts must import and call resetTransientRetryState",
575
- );
576
- // Ensure reset is called BEFORE startAuto — order matters
577
- const resetIdx = src.indexOf("resetTransientRetryState()");
578
- const startIdx = src.indexOf("await deps.startAuto(");
579
- assert.ok(
580
- resetIdx !== -1 && startIdx !== -1 && resetIdx < startIdx,
581
- "resetTransientRetryState() must be called before deps.startAuto()",
582
- );
583
- // Session timeout counter must also be reset before startAuto
584
- assert.ok(
585
- src.includes("resetSessionTimeoutState"),
586
- "provider-error-resume.ts must import and call resetSessionTimeoutState",
587
- );
588
- const sessionResetIdx = src.indexOf("resetSessionTimeoutState()");
589
- assert.ok(
590
- sessionResetIdx !== -1 && startIdx !== -1 && sessionResetIdx < startIdx,
591
- "resetSessionTimeoutState() must be called before deps.startAuto()",
592
- );
593
- });
594
-
595
609
  // ── Fix 2: Session creation timeout treated as transient in phases.ts ───────
596
610
 
597
611
  test("phases.ts handles timeout session-creation failures with pause instead of stopAuto", () => {
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Tests that /gsd queue is blocked when auto-mode is active.
3
+ *
4
+ * Relates to #4704: /gsd queue writes .gsd/PROJECT.md + QUEUE-ORDER.json
5
+ * directly into the project-root worktree, racing with auto-mode's
6
+ * pre-merge dirty-tree check and causing __dirty_working_tree__ failures.
7
+ *
8
+ * The fix adds an isAutoActive() guard in handleWorkflowCommand before
9
+ * delegating to showQueue, mirroring the existing /gsd quick guard (#2417).
10
+ */
11
+
12
+ import { describe, it } from "node:test";
13
+ import assert from "node:assert/strict";
14
+ import { readFileSync } from "node:fs";
15
+ import { join } from "node:path";
16
+
17
+ // ─── Structural test: verify the guard exists in source ──────────────────────
18
+
19
+ describe("/gsd queue auto-mode guard (#4704)", () => {
20
+ it("handleWorkflowCommand checks isAutoActive() before calling showQueue", () => {
21
+ const src = readFileSync(
22
+ join(
23
+ import.meta.dirname,
24
+ "..",
25
+ "commands",
26
+ "handlers",
27
+ "workflow.ts",
28
+ ),
29
+ "utf-8",
30
+ );
31
+
32
+ // Find the queue command block
33
+ const queueBlockMatch = src.match(
34
+ /if\s*\(\s*trimmed\s*===\s*"queue"\s*\)\s*\{([\s\S]*?)\n \}/,
35
+ );
36
+ assert.ok(queueBlockMatch, "queue command block exists in handleWorkflowCommand");
37
+
38
+ const queueBlock = queueBlockMatch[1];
39
+
40
+ // Verify auto-mode guard comes BEFORE showQueue call. Accepts either the
41
+ // inline isAutoActive() check (Tier 1) or the shared requireNotAutoActive()
42
+ // helper (Tier 2 / #4712).
43
+ const guardIndex = Math.max(
44
+ queueBlock.indexOf("isAutoActive()"),
45
+ queueBlock.indexOf("requireNotAutoActive("),
46
+ );
47
+ const showQueueIndex = queueBlock.indexOf("showQueue(");
48
+
49
+ assert.ok(guardIndex !== -1, "auto-mode guard exists in queue command block");
50
+ assert.ok(showQueueIndex !== -1, "showQueue() call exists in queue command block");
51
+ assert.ok(
52
+ guardIndex < showQueueIndex,
53
+ "auto-mode guard appears before showQueue() call",
54
+ );
55
+ });
56
+
57
+ it("guard shows error message mentioning /gsd stop", () => {
58
+ const src = readFileSync(
59
+ join(
60
+ import.meta.dirname,
61
+ "..",
62
+ "commands",
63
+ "handlers",
64
+ "workflow.ts",
65
+ ),
66
+ "utf-8",
67
+ );
68
+
69
+ assert.ok(
70
+ src.includes("cannot run while auto-mode is active"),
71
+ "error message explains that the command cannot run during auto-mode",
72
+ );
73
+ assert.ok(
74
+ src.includes("/gsd stop"),
75
+ "error message mentions /gsd stop as the resolution",
76
+ );
77
+ });
78
+
79
+ it("guard returns true (handled) to prevent falling through", () => {
80
+ const src = readFileSync(
81
+ join(
82
+ import.meta.dirname,
83
+ "..",
84
+ "commands",
85
+ "handlers",
86
+ "workflow.ts",
87
+ ),
88
+ "utf-8",
89
+ );
90
+
91
+ const queueBlockMatch = src.match(
92
+ /if\s*\(\s*trimmed\s*===\s*"queue"\s*\)\s*\{([\s\S]*?)\n \}/,
93
+ );
94
+ assert.ok(queueBlockMatch);
95
+ const queueBlock = queueBlockMatch[1];
96
+
97
+ // The guard block should have its own return true before showQueue
98
+ const guardBlock = queueBlock.slice(0, queueBlock.indexOf("showQueue("));
99
+ assert.ok(
100
+ guardBlock.includes("return true"),
101
+ "guard block returns true before showQueue is reached",
102
+ );
103
+ });
104
+ });
105
+
106
+ // ─── .gsd/audit/ runtime classification regression ──────────────────────────
107
+
108
+ describe(".gsd/audit/ runtime classification (#4704)", () => {
109
+ it("GSD_RUNTIME_PATTERNS in gitignore.ts includes .gsd/audit/", () => {
110
+ const src = readFileSync(
111
+ join(import.meta.dirname, "..", "gitignore.ts"),
112
+ "utf-8",
113
+ );
114
+ assert.ok(
115
+ src.includes('".gsd/audit/"'),
116
+ ".gsd/audit/ listed in GSD_RUNTIME_PATTERNS",
117
+ );
118
+ });
119
+
120
+ it("RUNTIME_EXCLUSION_PATHS in git-service.ts includes .gsd/audit/", () => {
121
+ const src = readFileSync(
122
+ join(import.meta.dirname, "..", "git-service.ts"),
123
+ "utf-8",
124
+ );
125
+ assert.ok(
126
+ src.includes('".gsd/audit/"'),
127
+ ".gsd/audit/ listed in RUNTIME_EXCLUSION_PATHS",
128
+ );
129
+ });
130
+
131
+ it("SKIP_PATHS in worktree-manager.ts includes .gsd/audit/", () => {
132
+ const src = readFileSync(
133
+ join(import.meta.dirname, "..", "worktree-manager.ts"),
134
+ "utf-8",
135
+ );
136
+ assert.ok(
137
+ src.includes('".gsd/audit/"'),
138
+ ".gsd/audit/ listed in SKIP_PATHS",
139
+ );
140
+ });
141
+ });
142
+
143
+ // ─── Windows gsd.db close+reopen around pre-merge stash ─────────────────────
144
+
145
+ describe("Windows pre-merge DB release (#4704)", () => {
146
+ it("mergeMilestoneToMain closes gsd.db on win32 before git stash", () => {
147
+ const src = readFileSync(
148
+ join(import.meta.dirname, "..", "auto-worktree.ts"),
149
+ "utf-8",
150
+ );
151
+
152
+ // Locate the stash push line and ensure a win32-gated closeDatabase()
153
+ // call precedes it in the same function scope.
154
+ const stashIndex = src.indexOf('"stash", "push", "--include-untracked"');
155
+ assert.ok(stashIndex !== -1, "pre-merge stash push exists");
156
+
157
+ const beforeStash = src.slice(0, stashIndex);
158
+ const win32Index = beforeStash.lastIndexOf('process.platform === "win32"');
159
+ const closeIndex = beforeStash.lastIndexOf("closeDatabase()");
160
+
161
+ assert.ok(win32Index !== -1, "win32 platform guard appears before stash");
162
+ assert.ok(closeIndex !== -1, "closeDatabase() invoked before stash");
163
+ assert.ok(
164
+ win32Index < closeIndex && closeIndex < stashIndex,
165
+ "platform guard wraps the closeDatabase() call before the stash",
166
+ );
167
+ });
168
+
169
+ it("openDatabase is called after the stash to reopen the connection", () => {
170
+ const src = readFileSync(
171
+ join(import.meta.dirname, "..", "auto-worktree.ts"),
172
+ "utf-8",
173
+ );
174
+ const stashIndex = src.indexOf('"stash", "push", "--include-untracked"');
175
+ const afterStash = src.slice(stashIndex);
176
+ assert.ok(
177
+ afterStash.includes("openDatabase("),
178
+ "openDatabase() called after the pre-merge stash",
179
+ );
180
+ });
181
+ });
@@ -35,15 +35,20 @@ describe("/gsd quick auto-mode guard (#2417)", () => {
35
35
 
36
36
  const quickBlock = quickBlockMatch[1];
37
37
 
38
- // Verify isAutoActive guard comes BEFORE handleQuick call
39
- const guardIndex = quickBlock.indexOf("isAutoActive()");
38
+ // Verify the shared auto-mode guard comes BEFORE handleQuick call.
39
+ // Accepts either the inline isAutoActive() check (legacy) or the shared
40
+ // requireNotAutoActive() helper (#4712).
41
+ const guardIndex = Math.max(
42
+ quickBlock.indexOf("isAutoActive()"),
43
+ quickBlock.indexOf("requireNotAutoActive("),
44
+ );
40
45
  const handleQuickIndex = quickBlock.indexOf("handleQuick(");
41
46
 
42
- assert.ok(guardIndex !== -1, "isAutoActive() guard exists in quick command block");
47
+ assert.ok(guardIndex !== -1, "auto-mode guard exists in quick command block");
43
48
  assert.ok(handleQuickIndex !== -1, "handleQuick() call exists in quick command block");
44
49
  assert.ok(
45
50
  guardIndex < handleQuickIndex,
46
- "isAutoActive() guard appears before handleQuick() call",
51
+ "auto-mode guard appears before handleQuick() call",
47
52
  );
48
53
  });
49
54
 
@@ -59,10 +64,11 @@ describe("/gsd quick auto-mode guard (#2417)", () => {
59
64
  "utf-8",
60
65
  );
61
66
 
62
- // The error message should tell the user to stop auto-mode first
67
+ // The shared helper assembles the message dynamically from the command name,
68
+ // so assert on the message fragment and the /gsd stop instruction.
63
69
  assert.ok(
64
- src.includes("/gsd quick cannot run while auto-mode is active"),
65
- "error message explains that quick cannot run during auto-mode",
70
+ src.includes("cannot run while auto-mode is active"),
71
+ "error message explains that the command cannot run during auto-mode",
66
72
  );
67
73
  assert.ok(
68
74
  src.includes("/gsd stop"),