gsd-pi 2.34.0-dev.7d38042 → 2.34.0-dev.e6d9bed

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 (212) hide show
  1. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +5 -1
  2. package/dist/resources/extensions/gsd/docs/preferences-reference.md +10 -0
  3. package/dist/resources/extensions/gsd/doctor-checks.js +113 -5
  4. package/dist/resources/extensions/gsd/doctor-proactive.js +22 -0
  5. package/dist/resources/extensions/gsd/doctor.js +36 -0
  6. package/dist/resources/extensions/gsd/guided-flow.js +4 -2
  7. package/dist/resources/extensions/gsd/preferences-validation.js +38 -0
  8. package/dist/resources/extensions/gsd/preferences.js +2 -0
  9. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +4 -4
  10. package/package.json +1 -1
  11. package/packages/pi-agent-core/dist/agent-loop.d.ts +14 -0
  12. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  13. package/packages/pi-agent-core/dist/agent-loop.js +24 -27
  14. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  15. package/packages/pi-agent-core/dist/agent.d.ts +1 -0
  16. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  17. package/packages/pi-agent-core/dist/agent.js +11 -22
  18. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  19. package/packages/pi-agent-core/dist/proxy.d.ts.map +1 -1
  20. package/packages/pi-agent-core/dist/proxy.js +2 -8
  21. package/packages/pi-agent-core/dist/proxy.js.map +1 -1
  22. package/packages/pi-agent-core/src/agent-loop.ts +30 -27
  23. package/packages/pi-agent-core/src/agent.ts +12 -23
  24. package/packages/pi-agent-core/src/proxy.ts +2 -8
  25. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  26. package/packages/pi-ai/dist/providers/azure-openai-responses.js +5 -41
  27. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  28. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  29. package/packages/pi-ai/dist/providers/openai-completions.js +10 -73
  30. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  31. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  32. package/packages/pi-ai/dist/providers/openai-responses.js +8 -79
  33. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  34. package/packages/pi-ai/dist/providers/openai-shared.d.ts +65 -0
  35. package/packages/pi-ai/dist/providers/openai-shared.d.ts.map +1 -0
  36. package/packages/pi-ai/dist/providers/openai-shared.js +146 -0
  37. package/packages/pi-ai/dist/providers/openai-shared.js.map +1 -0
  38. package/packages/pi-ai/dist/utils/oauth/google-antigravity.d.ts.map +1 -1
  39. package/packages/pi-ai/dist/utils/oauth/google-antigravity.js +7 -135
  40. package/packages/pi-ai/dist/utils/oauth/google-antigravity.js.map +1 -1
  41. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -1
  42. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js +7 -135
  43. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js.map +1 -1
  44. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.d.ts +46 -0
  45. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.d.ts.map +1 -0
  46. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.js +160 -0
  47. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.js.map +1 -0
  48. package/packages/pi-ai/src/providers/azure-openai-responses.ts +11 -45
  49. package/packages/pi-ai/src/providers/openai-completions.ts +16 -86
  50. package/packages/pi-ai/src/providers/openai-responses.ts +15 -95
  51. package/packages/pi-ai/src/providers/openai-shared.ts +193 -0
  52. package/packages/pi-ai/src/utils/oauth/google-antigravity.ts +14 -162
  53. package/packages/pi-ai/src/utils/oauth/google-gemini-cli.ts +13 -161
  54. package/packages/pi-ai/src/utils/oauth/google-oauth-utils.ts +201 -0
  55. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +16 -63
  56. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  57. package/packages/pi-coding-agent/dist/core/agent-session.js +104 -641
  58. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  59. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +0 -1
  60. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  61. package/packages/pi-coding-agent/dist/core/auth-storage.js +4 -35
  62. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  63. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  64. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js +5 -43
  65. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
  66. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  67. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +11 -69
  68. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts +40 -0
  70. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
  71. package/packages/pi-coding-agent/dist/core/compaction/utils.js +78 -0
  72. package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
  73. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts +77 -0
  74. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -0
  75. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +331 -0
  76. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -0
  77. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +2 -2
  78. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
  79. package/packages/pi-coding-agent/dist/core/extensions/index.js +1 -1
  80. package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
  81. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +15 -0
  82. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  83. package/packages/pi-coding-agent/dist/core/extensions/runner.js +129 -243
  84. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  85. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +49 -42
  86. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  87. package/packages/pi-coding-agent/dist/core/extensions/types.js +2 -21
  88. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  89. package/packages/pi-coding-agent/dist/core/lock-utils.d.ts +39 -0
  90. package/packages/pi-coding-agent/dist/core/lock-utils.d.ts.map +1 -0
  91. package/packages/pi-coding-agent/dist/core/lock-utils.js +89 -0
  92. package/packages/pi-coding-agent/dist/core/lock-utils.js.map +1 -0
  93. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +2 -0
  94. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
  95. package/packages/pi-coding-agent/dist/core/lsp/config.js +4 -1
  96. package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  98. package/packages/pi-coding-agent/dist/core/lsp/index.js +52 -107
  99. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  100. package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +2 -21
  102. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -1
  103. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +0 -1
  104. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/lsp/types.js +0 -28
  106. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/package-manager.js +2 -4
  109. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +2 -4
  111. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/resource-loader.js +33 -58
  113. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +87 -0
  115. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -0
  116. package/packages/pi-coding-agent/dist/core/retry-handler.js +295 -0
  117. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -0
  118. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +0 -1
  119. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/session-manager.js +3 -28
  121. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/skills.js +1 -3
  124. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  126. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/index.js +1 -1
  128. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.d.ts +1 -1
  130. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  131. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js +9 -26
  132. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js.map +1 -1
  133. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  134. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +1 -13
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.d.ts +44 -0
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.d.ts.map +1 -0
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.js +61 -0
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.js.map +1 -0
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.js +6 -9
  142. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.d.ts +6 -0
  144. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.d.ts.map +1 -0
  145. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.js +15 -0
  146. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.js.map +1 -0
  147. package/packages/pi-coding-agent/dist/modes/print-mode.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/print-mode.js +2 -30
  149. package/packages/pi-coding-agent/dist/modes/print-mode.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  151. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +2 -28
  152. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  153. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.d.ts +19 -0
  154. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.d.ts.map +1 -0
  155. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.js +45 -0
  156. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.js.map +1 -0
  157. package/packages/pi-coding-agent/dist/utils/error.d.ts +5 -0
  158. package/packages/pi-coding-agent/dist/utils/error.d.ts.map +1 -0
  159. package/packages/pi-coding-agent/dist/utils/error.js +7 -0
  160. package/packages/pi-coding-agent/dist/utils/error.js.map +1 -0
  161. package/packages/pi-coding-agent/src/core/agent-session.ts +117 -745
  162. package/packages/pi-coding-agent/src/core/auth-storage.ts +4 -38
  163. package/packages/pi-coding-agent/src/core/compaction/branch-summarization.ts +7 -53
  164. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +14 -74
  165. package/packages/pi-coding-agent/src/core/compaction/utils.ts +100 -0
  166. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +424 -0
  167. package/packages/pi-coding-agent/src/core/extensions/index.ts +1 -21
  168. package/packages/pi-coding-agent/src/core/extensions/runner.ts +119 -243
  169. package/packages/pi-coding-agent/src/core/extensions/types.ts +50 -69
  170. package/packages/pi-coding-agent/src/core/lock-utils.ts +113 -0
  171. package/packages/pi-coding-agent/src/core/lsp/config.ts +4 -1
  172. package/packages/pi-coding-agent/src/core/lsp/index.ts +83 -152
  173. package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +2 -22
  174. package/packages/pi-coding-agent/src/core/lsp/types.ts +0 -29
  175. package/packages/pi-coding-agent/src/core/package-manager.ts +1 -4
  176. package/packages/pi-coding-agent/src/core/resource-loader.ts +43 -67
  177. package/packages/pi-coding-agent/src/core/retry-handler.ts +359 -0
  178. package/packages/pi-coding-agent/src/core/session-manager.ts +3 -30
  179. package/packages/pi-coding-agent/src/core/skills.ts +1 -4
  180. package/packages/pi-coding-agent/src/index.ts +1 -7
  181. package/packages/pi-coding-agent/src/modes/interactive/components/session-selector.ts +17 -29
  182. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1 -13
  183. package/packages/pi-coding-agent/src/modes/interactive/components/tree-render-utils.ts +81 -0
  184. package/packages/pi-coding-agent/src/modes/interactive/components/tree-selector.ts +14 -19
  185. package/packages/pi-coding-agent/src/modes/interactive/utils/shorten-path.ts +14 -0
  186. package/packages/pi-coding-agent/src/modes/print-mode.ts +2 -30
  187. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -28
  188. package/packages/pi-coding-agent/src/modes/shared/command-context-actions.ts +53 -0
  189. package/packages/pi-coding-agent/src/utils/error.ts +6 -0
  190. package/packages/pi-tui/dist/components/markdown.d.ts +5 -0
  191. package/packages/pi-tui/dist/components/markdown.d.ts.map +1 -1
  192. package/packages/pi-tui/dist/components/markdown.js +25 -31
  193. package/packages/pi-tui/dist/components/markdown.js.map +1 -1
  194. package/packages/pi-tui/dist/keys.d.ts +0 -4
  195. package/packages/pi-tui/dist/keys.d.ts.map +1 -1
  196. package/packages/pi-tui/dist/keys.js +94 -162
  197. package/packages/pi-tui/dist/keys.js.map +1 -1
  198. package/packages/pi-tui/src/components/markdown.ts +25 -29
  199. package/packages/pi-tui/src/keys.ts +94 -173
  200. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +5 -1
  201. package/src/resources/extensions/gsd/docs/preferences-reference.md +10 -0
  202. package/src/resources/extensions/gsd/doctor-checks.ts +107 -5
  203. package/src/resources/extensions/gsd/doctor-proactive.ts +24 -0
  204. package/src/resources/extensions/gsd/doctor-types.ts +9 -1
  205. package/src/resources/extensions/gsd/doctor.ts +35 -0
  206. package/src/resources/extensions/gsd/guided-flow.ts +4 -2
  207. package/src/resources/extensions/gsd/preferences-validation.ts +38 -0
  208. package/src/resources/extensions/gsd/preferences.ts +2 -0
  209. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +98 -2
  210. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +59 -3
  211. package/src/resources/extensions/gsd/tests/preferences.test.ts +28 -0
  212. package/src/resources/skills/create-gsd-extension/references/events-reference.md +4 -4
@@ -325,131 +325,74 @@ const FUNCTIONAL_CODEPOINTS = {
325
325
  end: -15,
326
326
  } as const;
327
327
 
328
- const LEGACY_KEY_SEQUENCES = {
329
- up: ["\x1b[A", "\x1bOA"],
330
- down: ["\x1b[B", "\x1bOB"],
331
- right: ["\x1b[C", "\x1bOC"],
332
- left: ["\x1b[D", "\x1bOD"],
333
- home: ["\x1b[H", "\x1bOH", "\x1b[1~", "\x1b[7~"],
334
- end: ["\x1b[F", "\x1bOF", "\x1b[4~", "\x1b[8~"],
335
- insert: ["\x1b[2~"],
336
- delete: ["\x1b[3~"],
337
- pageUp: ["\x1b[5~", "\x1b[[5~"],
338
- pageDown: ["\x1b[6~", "\x1b[[6~"],
339
- clear: ["\x1b[E", "\x1bOE"],
340
- f1: ["\x1bOP", "\x1b[11~", "\x1b[[A"],
341
- f2: ["\x1bOQ", "\x1b[12~", "\x1b[[B"],
342
- f3: ["\x1bOR", "\x1b[13~", "\x1b[[C"],
343
- f4: ["\x1bOS", "\x1b[14~", "\x1b[[D"],
344
- f5: ["\x1b[15~", "\x1b[[E"],
345
- f6: ["\x1b[17~"],
346
- f7: ["\x1b[18~"],
347
- f8: ["\x1b[19~"],
348
- f9: ["\x1b[20~"],
349
- f10: ["\x1b[21~"],
350
- f11: ["\x1b[23~"],
351
- f12: ["\x1b[24~"],
352
- } as const;
353
-
354
- const LEGACY_SHIFT_SEQUENCES = {
355
- up: ["\x1b[a"],
356
- down: ["\x1b[b"],
357
- right: ["\x1b[c"],
358
- left: ["\x1b[d"],
359
- clear: ["\x1b[e"],
360
- insert: ["\x1b[2$"],
361
- delete: ["\x1b[3$"],
362
- pageUp: ["\x1b[5$"],
363
- pageDown: ["\x1b[6$"],
364
- home: ["\x1b[7$"],
365
- end: ["\x1b[8$"],
366
- } as const;
367
-
368
- const LEGACY_CTRL_SEQUENCES = {
369
- up: ["\x1bOa"],
370
- down: ["\x1bOb"],
371
- right: ["\x1bOc"],
372
- left: ["\x1bOd"],
373
- clear: ["\x1bOe"],
374
- insert: ["\x1b[2^"],
375
- delete: ["\x1b[3^"],
376
- pageUp: ["\x1b[5^"],
377
- pageDown: ["\x1b[6^"],
378
- home: ["\x1b[7^"],
379
- end: ["\x1b[8^"],
380
- } as const;
381
-
382
- const LEGACY_SEQUENCE_KEY_IDS: Record<string, KeyId> = {
383
- "\x1bOA": "up",
384
- "\x1bOB": "down",
385
- "\x1bOC": "right",
386
- "\x1bOD": "left",
387
- "\x1bOH": "home",
388
- "\x1bOF": "end",
389
- "\x1b[E": "clear",
390
- "\x1bOE": "clear",
391
- "\x1bOe": "ctrl+clear",
392
- "\x1b[e": "shift+clear",
393
- "\x1b[2~": "insert",
394
- "\x1b[2$": "shift+insert",
395
- "\x1b[2^": "ctrl+insert",
396
- "\x1b[3$": "shift+delete",
397
- "\x1b[3^": "ctrl+delete",
398
- "\x1b[[5~": "pageUp",
399
- "\x1b[[6~": "pageDown",
400
- "\x1b[a": "shift+up",
401
- "\x1b[b": "shift+down",
402
- "\x1b[c": "shift+right",
403
- "\x1b[d": "shift+left",
404
- "\x1bOa": "ctrl+up",
405
- "\x1bOb": "ctrl+down",
406
- "\x1bOc": "ctrl+right",
407
- "\x1bOd": "ctrl+left",
408
- "\x1b[5$": "shift+pageUp",
409
- "\x1b[6$": "shift+pageDown",
410
- "\x1b[7$": "shift+home",
411
- "\x1b[8$": "shift+end",
412
- "\x1b[5^": "ctrl+pageUp",
413
- "\x1b[6^": "ctrl+pageDown",
414
- "\x1b[7^": "ctrl+home",
415
- "\x1b[8^": "ctrl+end",
416
- "\x1bOP": "f1",
417
- "\x1bOQ": "f2",
418
- "\x1bOR": "f3",
419
- "\x1bOS": "f4",
420
- "\x1b[11~": "f1",
421
- "\x1b[12~": "f2",
422
- "\x1b[13~": "f3",
423
- "\x1b[14~": "f4",
424
- "\x1b[[A": "f1",
425
- "\x1b[[B": "f2",
426
- "\x1b[[C": "f3",
427
- "\x1b[[D": "f4",
428
- "\x1b[[E": "f5",
429
- "\x1b[15~": "f5",
430
- "\x1b[17~": "f6",
431
- "\x1b[18~": "f7",
432
- "\x1b[19~": "f8",
433
- "\x1b[20~": "f9",
434
- "\x1b[21~": "f10",
435
- "\x1b[23~": "f11",
436
- "\x1b[24~": "f12",
437
- "\x1bb": "alt+left",
438
- "\x1bf": "alt+right",
439
- "\x1bp": "alt+up",
440
- "\x1bn": "alt+down",
328
+ /**
329
+ * Consolidated legacy terminal key sequences.
330
+ * Each key maps to its sequences for unmodified, shift-modified, and ctrl-modified variants.
331
+ * This single structure replaces three separate maps (LEGACY_KEY_SEQUENCES,
332
+ * LEGACY_SHIFT_SEQUENCES, LEGACY_CTRL_SEQUENCES) that shared the same key sets.
333
+ */
334
+ const LEGACY_SEQUENCES: Record<string, { plain?: readonly string[]; shift?: readonly string[]; ctrl?: readonly string[] }> = {
335
+ up: { plain: ["\x1b[A", "\x1bOA"], shift: ["\x1b[a"], ctrl: ["\x1bOa"] },
336
+ down: { plain: ["\x1b[B", "\x1bOB"], shift: ["\x1b[b"], ctrl: ["\x1bOb"] },
337
+ right: { plain: ["\x1b[C", "\x1bOC"], shift: ["\x1b[c"], ctrl: ["\x1bOc"] },
338
+ left: { plain: ["\x1b[D", "\x1bOD"], shift: ["\x1b[d"], ctrl: ["\x1bOd"] },
339
+ home: { plain: ["\x1b[H", "\x1bOH", "\x1b[1~", "\x1b[7~"], shift: ["\x1b[7$"], ctrl: ["\x1b[7^"] },
340
+ end: { plain: ["\x1b[F", "\x1bOF", "\x1b[4~", "\x1b[8~"], shift: ["\x1b[8$"], ctrl: ["\x1b[8^"] },
341
+ insert: { plain: ["\x1b[2~"], shift: ["\x1b[2$"], ctrl: ["\x1b[2^"] },
342
+ delete: { plain: ["\x1b[3~"], shift: ["\x1b[3$"], ctrl: ["\x1b[3^"] },
343
+ pageUp: { plain: ["\x1b[5~", "\x1b[[5~"], shift: ["\x1b[5$"], ctrl: ["\x1b[5^"] },
344
+ pageDown: { plain: ["\x1b[6~", "\x1b[[6~"], shift: ["\x1b[6$"], ctrl: ["\x1b[6^"] },
345
+ clear: { plain: ["\x1b[E", "\x1bOE"], shift: ["\x1b[e"], ctrl: ["\x1bOe"] },
346
+ f1: { plain: ["\x1bOP", "\x1b[11~", "\x1b[[A"] },
347
+ f2: { plain: ["\x1bOQ", "\x1b[12~", "\x1b[[B"] },
348
+ f3: { plain: ["\x1bOR", "\x1b[13~", "\x1b[[C"] },
349
+ f4: { plain: ["\x1bOS", "\x1b[14~", "\x1b[[D"] },
350
+ f5: { plain: ["\x1b[15~", "\x1b[[E"] },
351
+ f6: { plain: ["\x1b[17~"] },
352
+ f7: { plain: ["\x1b[18~"] },
353
+ f8: { plain: ["\x1b[19~"] },
354
+ f9: { plain: ["\x1b[20~"] },
355
+ f10: { plain: ["\x1b[21~"] },
356
+ f11: { plain: ["\x1b[23~"] },
357
+ f12: { plain: ["\x1b[24~"] },
441
358
  } as const;
442
359
 
443
- type LegacyModifierKey = keyof typeof LEGACY_SHIFT_SEQUENCES;
360
+ /**
361
+ * Reverse lookup from escape sequence to key identifier, auto-generated from LEGACY_SEQUENCES.
362
+ * Additional non-standard sequences (alt+arrow aliases) are appended after generation.
363
+ */
364
+ const LEGACY_SEQUENCE_KEY_IDS: Record<string, KeyId> = (() => {
365
+ const map: Record<string, KeyId> = {};
366
+ for (const [key, entry] of Object.entries(LEGACY_SEQUENCES)) {
367
+ const keyId = key as KeyId;
368
+ if (entry.plain) {
369
+ for (const seq of entry.plain) map[seq] = keyId;
370
+ }
371
+ if (entry.shift) {
372
+ for (const seq of entry.shift) map[seq] = `shift+${keyId}` as KeyId;
373
+ }
374
+ if (entry.ctrl) {
375
+ for (const seq of entry.ctrl) map[seq] = `ctrl+${keyId}` as KeyId;
376
+ }
377
+ }
378
+ // Non-standard alt+arrow aliases not derivable from the table
379
+ map["\x1bb"] = "alt+left";
380
+ map["\x1bf"] = "alt+right";
381
+ map["\x1bp"] = "alt+up";
382
+ map["\x1bn"] = "alt+down";
383
+ return map;
384
+ })();
444
385
 
445
386
  const matchesLegacySequence = (data: string, sequences: readonly string[]): boolean => sequences.includes(data);
446
387
 
447
- const matchesLegacyModifierSequence = (data: string, key: LegacyModifierKey, modifier: number): boolean => {
448
- if (modifier === MODIFIERS.shift) {
449
- return matchesLegacySequence(data, LEGACY_SHIFT_SEQUENCES[key]);
388
+ const matchesLegacyModifierSequence = (data: string, key: string, modifier: number): boolean => {
389
+ const entry = LEGACY_SEQUENCES[key];
390
+ if (!entry) return false;
391
+ if (modifier === MODIFIERS.shift && entry.shift) {
392
+ return matchesLegacySequence(data, entry.shift);
450
393
  }
451
- if (modifier === MODIFIERS.ctrl) {
452
- return matchesLegacySequence(data, LEGACY_CTRL_SEQUENCES[key]);
394
+ if (modifier === MODIFIERS.ctrl && entry.ctrl) {
395
+ return matchesLegacySequence(data, entry.ctrl);
453
396
  }
454
397
  return false;
455
398
  };
@@ -481,33 +424,29 @@ interface ParsedModifyOtherKeysSequence {
481
424
  let _lastEventType: KeyEventType = "press";
482
425
 
483
426
  /**
484
- * Check if the last parsed key event was a key release.
485
- * Only meaningful when Kitty keyboard protocol with flag 2 is active.
427
+ * Check if input data contains a Kitty event type marker.
428
+ * Event type markers appear as ":<eventType>" followed by a sequence terminator (u, ~, A-D, H, F).
429
+ * Ignores bracketed paste content which may contain similar patterns.
486
430
  */
487
- export function isKeyRelease(data: string): boolean {
488
- // Don't treat bracketed paste content as key release, even if it contains
489
- // patterns like ":3F" (e.g., bluetooth MAC addresses like "90:62:3F:A5").
490
- // Terminal.ts re-wraps paste content with bracketed paste markers before
491
- // passing to TUI, so pasted data will always contain \x1b[200~.
431
+ function hasKittyEventType(data: string, eventType: number): boolean {
492
432
  if (data.includes("\x1b[200~")) {
493
433
  return false;
494
434
  }
435
+ const marker = `:${eventType}`;
436
+ return (
437
+ data.includes(`${marker}u`) ||
438
+ data.includes(`${marker}~`) ||
439
+ data.includes(`${marker}A`) ||
440
+ data.includes(`${marker}B`) ||
441
+ data.includes(`${marker}C`) ||
442
+ data.includes(`${marker}D`) ||
443
+ data.includes(`${marker}H`) ||
444
+ data.includes(`${marker}F`)
445
+ );
446
+ }
495
447
 
496
- // Quick check: release events with flag 2 contain ":3"
497
- // Format: \x1b[<codepoint>;<modifier>:3u
498
- if (
499
- data.includes(":3u") ||
500
- data.includes(":3~") ||
501
- data.includes(":3A") ||
502
- data.includes(":3B") ||
503
- data.includes(":3C") ||
504
- data.includes(":3D") ||
505
- data.includes(":3H") ||
506
- data.includes(":3F")
507
- ) {
508
- return true;
509
- }
510
- return false;
448
+ export function isKeyRelease(data: string): boolean {
449
+ return hasKittyEventType(data, 3);
511
450
  }
512
451
 
513
452
  /**
@@ -515,25 +454,7 @@ export function isKeyRelease(data: string): boolean {
515
454
  * Only meaningful when Kitty keyboard protocol with flag 2 is active.
516
455
  */
517
456
  export function isKeyRepeat(data: string): boolean {
518
- // Don't treat bracketed paste content as key repeat, even if it contains
519
- // patterns like ":2F". See isKeyRelease() for details.
520
- if (data.includes("\x1b[200~")) {
521
- return false;
522
- }
523
-
524
- if (
525
- data.includes(":2u") ||
526
- data.includes(":2~") ||
527
- data.includes(":2A") ||
528
- data.includes(":2B") ||
529
- data.includes(":2C") ||
530
- data.includes(":2D") ||
531
- data.includes(":2H") ||
532
- data.includes(":2F")
533
- ) {
534
- return true;
535
- }
536
- return false;
457
+ return hasKittyEventType(data, 2);
537
458
  }
538
459
 
539
460
  function parseEventType(eventTypeStr: string | undefined): KeyEventType {
@@ -847,7 +768,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
847
768
  case "insert":
848
769
  if (modifier === 0) {
849
770
  return (
850
- matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.insert) ||
771
+ matchesLegacySequence(data, LEGACY_SEQUENCES.insert.plain!) ||
851
772
  matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.insert, 0)
852
773
  );
853
774
  }
@@ -859,7 +780,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
859
780
  case "delete":
860
781
  if (modifier === 0) {
861
782
  return (
862
- matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.delete) ||
783
+ matchesLegacySequence(data, LEGACY_SEQUENCES.delete.plain!) ||
863
784
  matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, 0)
864
785
  );
865
786
  }
@@ -870,14 +791,14 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
870
791
 
871
792
  case "clear":
872
793
  if (modifier === 0) {
873
- return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.clear);
794
+ return matchesLegacySequence(data, LEGACY_SEQUENCES.clear.plain!);
874
795
  }
875
796
  return matchesLegacyModifierSequence(data, "clear", modifier);
876
797
 
877
798
  case "home":
878
799
  if (modifier === 0) {
879
800
  return (
880
- matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.home) ||
801
+ matchesLegacySequence(data, LEGACY_SEQUENCES.home.plain!) ||
881
802
  matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, 0)
882
803
  );
883
804
  }
@@ -889,7 +810,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
889
810
  case "end":
890
811
  if (modifier === 0) {
891
812
  return (
892
- matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.end) ||
813
+ matchesLegacySequence(data, LEGACY_SEQUENCES.end.plain!) ||
893
814
  matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, 0)
894
815
  );
895
816
  }
@@ -901,7 +822,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
901
822
  case "pageup":
902
823
  if (modifier === 0) {
903
824
  return (
904
- matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.pageUp) ||
825
+ matchesLegacySequence(data, LEGACY_SEQUENCES.pageUp.plain!) ||
905
826
  matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageUp, 0)
906
827
  );
907
828
  }
@@ -913,7 +834,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
913
834
  case "pagedown":
914
835
  if (modifier === 0) {
915
836
  return (
916
- matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.pageDown) ||
837
+ matchesLegacySequence(data, LEGACY_SEQUENCES.pageDown.plain!) ||
917
838
  matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageDown, 0)
918
839
  );
919
840
  }
@@ -928,7 +849,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
928
849
  }
929
850
  if (modifier === 0) {
930
851
  return (
931
- matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.up) ||
852
+ matchesLegacySequence(data, LEGACY_SEQUENCES.up.plain!) ||
932
853
  matchesKittySequence(data, ARROW_CODEPOINTS.up, 0)
933
854
  );
934
855
  }
@@ -943,7 +864,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
943
864
  }
944
865
  if (modifier === 0) {
945
866
  return (
946
- matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.down) ||
867
+ matchesLegacySequence(data, LEGACY_SEQUENCES.down.plain!) ||
947
868
  matchesKittySequence(data, ARROW_CODEPOINTS.down, 0)
948
869
  );
949
870
  }
@@ -970,7 +891,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
970
891
  }
971
892
  if (modifier === 0) {
972
893
  return (
973
- matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.left) ||
894
+ matchesLegacySequence(data, LEGACY_SEQUENCES.left.plain!) ||
974
895
  matchesKittySequence(data, ARROW_CODEPOINTS.left, 0)
975
896
  );
976
897
  }
@@ -997,7 +918,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
997
918
  }
998
919
  if (modifier === 0) {
999
920
  return (
1000
- matchesLegacySequence(data, LEGACY_KEY_SEQUENCES.right) ||
921
+ matchesLegacySequence(data, LEGACY_SEQUENCES.right.plain!) ||
1001
922
  matchesKittySequence(data, ARROW_CODEPOINTS.right, 0)
1002
923
  );
1003
924
  }
@@ -1021,8 +942,8 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
1021
942
  if (modifier !== 0) {
1022
943
  return false;
1023
944
  }
1024
- const functionKey = key as keyof typeof LEGACY_KEY_SEQUENCES;
1025
- return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES[functionKey]);
945
+ const functionKey = key as keyof typeof LEGACY_SEQUENCES;
946
+ return matchesLegacySequence(data, LEGACY_SEQUENCES[functionKey]!.plain!);
1026
947
  }
1027
948
  }
1028
949
 
@@ -738,10 +738,14 @@ export function serializePreferencesToFrontmatter(prefs: Record<string, unknown>
738
738
  const orderedKeys = [
739
739
  "version", "mode", "always_use_skills", "prefer_skills", "avoid_skills",
740
740
  "skill_rules", "custom_instructions", "models", "skill_discovery",
741
- "auto_supervisor", "uat_dispatch", "unique_milestone_ids",
741
+ "skill_staleness_days", "auto_supervisor", "uat_dispatch", "unique_milestone_ids",
742
742
  "budget_ceiling", "budget_enforcement", "context_pause_threshold",
743
743
  "notifications", "remote_questions", "git",
744
744
  "post_unit_hooks", "pre_dispatch_hooks",
745
+ "dynamic_routing", "token_profile", "phases", "parallel",
746
+ "auto_visualize", "auto_report",
747
+ "verification_commands", "verification_auto_fix", "verification_max_retries",
748
+ "search_provider", "compression_strategy", "context_selection",
745
749
  ];
746
750
 
747
751
  const seen = new Set<string>();
@@ -134,6 +134,10 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
134
134
  - `isolation`: `"worktree"`, `"branch"`, or `"none"` — controls auto-mode git isolation strategy. `"worktree"` creates a milestone worktree for isolated work; `"branch"` works directly in the project root but creates a milestone branch (useful for submodule-heavy repos); `"none"` works directly on the current branch with no worktree or milestone branch (ideal for step-mode with hot reloads). Default: `"worktree"`.
135
135
  - `manage_gitignore`: boolean — when `false`, GSD will not touch `.gitignore` at all. Useful when your project has a strictly managed `.gitignore` and you don't want GSD adding entries. Default: `true`.
136
136
  - `worktree_post_create`: string — script to run after a worktree is created (both auto-mode and manual `/worktree`). Receives `SOURCE_DIR` and `WORKTREE_DIR` as environment variables. Can be absolute or relative to project root. Runs with 30-second timeout. Failure is non-fatal (logged as warning). Default: none.
137
+ - `auto_pr`: boolean — automatically create a GitHub pull request after a milestone branch is merged. Requires `gh` CLI to be installed. Default: `false`.
138
+ - `pr_target_branch`: string — branch to target when `auto_pr` is enabled. Defaults to `main_branch` when omitted.
139
+ - **Deprecated:** `commit_docs` — no longer valid; `.gsd/` is always gitignored. Remove this setting.
140
+ - **Deprecated:** `merge_to_main` — no longer valid; milestone-level merge is always used. Remove this setting.
137
141
 
138
142
  - `unique_milestone_ids`: boolean — when `true`, generates milestone IDs in `M{seq}-{rand6}` format (e.g. `M001-eh88as`) instead of plain sequential `M001`. Prevents ID collisions in team workflows where multiple contributors create milestones concurrently. Both formats coexist — existing `M001`-style milestones remain valid. Default: `false`.
139
143
 
@@ -181,6 +185,12 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
181
185
 
182
186
  - `auto_report`: boolean — generate an HTML report snapshot after each milestone completion. Default: `true`.
183
187
 
188
+ - `search_provider`: `"brave"`, `"tavily"`, `"ollama"`, `"native"`, or `"auto"` — selects the search backend for research phases. `"native"` forces Anthropic's built-in web search only; provider values force that backend and disable native search; `"auto"` uses the default heuristic. Default: `"auto"`.
189
+
190
+ - `compression_strategy`: `"truncate"` or `"compress"` — controls how context that exceeds the budget is reduced. `"truncate"` (default) drops sections from the end. `"compress"` applies heuristic compression before truncating, preserving more content at the cost of some fidelity. Default: `"truncate"`.
191
+
192
+ - `context_selection`: `"full"` or `"smart"` — controls how files are inlined into context. `"full"` inlines entire files; `"smart"` uses semantic chunking to include only the most relevant sections. Default is derived from `token_profile`.
193
+
184
194
  - `parallel`: configures parallel orchestration for running multiple slices concurrently. Keys:
185
195
  - `enabled`: boolean — enable parallel execution. Default: `false`.
186
196
  - `max_workers`: number — maximum concurrent workers (1-4). Default: `2`.
@@ -1,15 +1,15 @@
1
- import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
2
- import { join, sep } from "node:path";
1
+ import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, rmSync, statSync } from "node:fs";
2
+ import { basename, dirname, join, sep } from "node:path";
3
3
 
4
4
  import type { DoctorIssue, DoctorIssueCode } from "./doctor-types.js";
5
5
  import { loadFile, parseRoadmap } from "./files.js";
6
6
  import { resolveMilestoneFile, milestonesDir, gsdRoot, resolveGsdRootFile, relGsdRootFile } from "./paths.js";
7
7
  import { deriveState, isMilestoneComplete } from "./state.js";
8
8
  import { saveFile } from "./files.js";
9
- import { listWorktrees, resolveGitDir } from "./worktree-manager.js";
9
+ import { listWorktrees, resolveGitDir, worktreesDir } from "./worktree-manager.js";
10
10
  import { abortAndReset } from "./git-self-heal.js";
11
- import { RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
12
- import { nativeIsRepo, nativeWorktreeRemove, nativeBranchList, nativeBranchDelete, nativeLsFiles, nativeRmCached } from "./native-git-bridge.js";
11
+ import { RUNTIME_EXCLUSION_PATHS, readIntegrationBranch } from "./git-service.js";
12
+ import { nativeIsRepo, nativeBranchExists, nativeWorktreeList, nativeWorktreeRemove, nativeBranchList, nativeBranchDelete, nativeLsFiles, nativeRmCached } from "./native-git-bridge.js";
13
13
  import { readCrashLock, isLockProcessAlive, clearLock } from "./crash-recovery.js";
14
14
  import { ensureGitignore } from "./gitignore.js";
15
15
  import { readAllSessionStatuses, isSessionStale, removeSessionStatus } from "./session-status-io.js";
@@ -215,6 +215,70 @@ export async function checkGitHealth(
215
215
  } catch {
216
216
  // git branch list failed — skip
217
217
  }
218
+
219
+ // ── Integration branch existence ──────────────────────────────────────
220
+ // For each active (non-complete) milestone, verify the stored integration
221
+ // branch still exists in git. A missing integration branch blocks merge-back
222
+ // and causes the next merge operation to fail silently.
223
+ try {
224
+ const state = await deriveState(basePath);
225
+ for (const milestone of state.registry) {
226
+ if (milestone.status === "complete") continue;
227
+ const integrationBranch = readIntegrationBranch(basePath, milestone.id);
228
+ if (!integrationBranch) continue; // No stored branch — skip (not yet set)
229
+ if (!nativeBranchExists(basePath, integrationBranch)) {
230
+ issues.push({
231
+ severity: "error",
232
+ code: "integration_branch_missing",
233
+ scope: "milestone",
234
+ unitId: milestone.id,
235
+ message: `Milestone ${milestone.id} recorded integration branch "${integrationBranch}" but that branch no longer exists in git. Merge-back will fail.`,
236
+ fixable: false,
237
+ });
238
+ }
239
+ }
240
+ } catch {
241
+ // Non-fatal — integration branch check failed
242
+ }
243
+
244
+ // ── Orphaned worktree directories ────────────────────────────────────
245
+ // Worktree removal can fail after a branch delete, leaving a directory
246
+ // that is no longer registered with git. These orphaned dirs cause
247
+ // "already exists" errors when re-creating the same worktree name.
248
+ try {
249
+ const wtDir = worktreesDir(basePath);
250
+ if (existsSync(wtDir)) {
251
+ const registeredPaths = new Set(
252
+ nativeWorktreeList(basePath).map(entry => entry.path),
253
+ );
254
+ for (const entry of readdirSync(wtDir)) {
255
+ const fullPath = join(wtDir, entry);
256
+ try {
257
+ if (!statSync(fullPath).isDirectory()) continue;
258
+ } catch { continue; }
259
+ if (!registeredPaths.has(fullPath)) {
260
+ issues.push({
261
+ severity: "warning",
262
+ code: "worktree_directory_orphaned",
263
+ scope: "project",
264
+ unitId: entry,
265
+ message: `Worktree directory ${fullPath} exists on disk but is not registered with git. Run "git worktree prune" or doctor --fix to remove it.`,
266
+ fixable: true,
267
+ });
268
+ if (shouldFix("worktree_directory_orphaned")) {
269
+ try {
270
+ rmSync(fullPath, { recursive: true, force: true });
271
+ fixesApplied.push(`removed orphaned worktree directory ${fullPath}`);
272
+ } catch {
273
+ fixesApplied.push(`failed to remove orphaned worktree directory ${fullPath}`);
274
+ }
275
+ }
276
+ }
277
+ }
278
+ }
279
+ } catch {
280
+ // Non-fatal — orphaned worktree directory check failed
281
+ }
218
282
  }
219
283
 
220
284
  // ── Runtime Health Checks ──────────────────────────────────────────────────
@@ -255,6 +319,44 @@ export async function checkRuntimeHealth(
255
319
  // Non-fatal — crash lock check failed
256
320
  }
257
321
 
322
+ // ── Stranded lock directory ────────────────────────────────────────────
323
+ // proper-lockfile creates a `.gsd.lock/` directory as the OS-level lock
324
+ // mechanism. If the process was SIGKILLed or crashed hard, this directory
325
+ // can remain on disk without any live process holding it. The next session
326
+ // fails to acquire the lock until the directory is removed (#1245).
327
+ try {
328
+ const lockDir = join(dirname(root), `${basename(root)}.lock`);
329
+ if (existsSync(lockDir)) {
330
+ const statRes = statSync(lockDir);
331
+ if (statRes.isDirectory()) {
332
+ // Check if any live process actually holds this lock
333
+ const lock = readCrashLock(basePath);
334
+ const lockHolderAlive = lock ? isLockProcessAlive(lock) : false;
335
+ if (!lockHolderAlive) {
336
+ issues.push({
337
+ severity: "error",
338
+ code: "stranded_lock_directory",
339
+ scope: "project",
340
+ unitId: "project",
341
+ message: `Stranded lock directory "${lockDir}" exists but no live process holds the session lock. This blocks new auto-mode sessions from starting.`,
342
+ file: lockDir,
343
+ fixable: true,
344
+ });
345
+ if (shouldFix("stranded_lock_directory")) {
346
+ try {
347
+ rmSync(lockDir, { recursive: true, force: true });
348
+ fixesApplied.push(`removed stranded lock directory ${lockDir}`);
349
+ } catch {
350
+ fixesApplied.push(`failed to remove stranded lock directory ${lockDir}`);
351
+ }
352
+ }
353
+ }
354
+ }
355
+ }
356
+ } catch {
357
+ // Non-fatal — stranded lock directory check failed
358
+ }
359
+
258
360
  // ── Stale parallel sessions ────────────────────────────────────────────
259
361
  try {
260
362
  const parallelStatuses = readAllSessionStatuses(basePath);
@@ -20,6 +20,9 @@ import { gsdRoot, resolveGsdRootFile } from "./paths.js";
20
20
  import { readCrashLock, isLockProcessAlive, clearLock } from "./crash-recovery.js";
21
21
  import { abortAndReset } from "./git-self-heal.js";
22
22
  import { rebuildState } from "./doctor.js";
23
+ import { deriveState } from "./state.js";
24
+ import { readIntegrationBranch } from "./git-service.js";
25
+ import { nativeBranchExists, nativeIsRepo } from "./native-git-bridge.js";
23
26
 
24
27
  // ── Health Score Tracking ──────────────────────────────────────────────────
25
28
 
@@ -191,6 +194,27 @@ export async function preDispatchHealthGate(basePath: string): Promise<PreDispat
191
194
  // Non-fatal — dispatch continues without STATE.md if rebuild fails
192
195
  }
193
196
 
197
+ // ── Integration branch existence check ──
198
+ // If the active milestone's recorded integration branch no longer exists in
199
+ // git, the merge-back at the end of the milestone will fail. Block dispatch
200
+ // now to surface this before work is lost.
201
+ try {
202
+ if (nativeIsRepo(basePath)) {
203
+ const state = await deriveState(basePath);
204
+ if (state.activeMilestone) {
205
+ const integrationBranch = readIntegrationBranch(basePath, state.activeMilestone.id);
206
+ if (integrationBranch && !nativeBranchExists(basePath, integrationBranch)) {
207
+ issues.push(
208
+ `Integration branch "${integrationBranch}" for milestone ${state.activeMilestone.id} no longer exists in git. ` +
209
+ `Restore the branch or update the integration branch before dispatching. Run /gsd doctor for details.`,
210
+ );
211
+ }
212
+ }
213
+ }
214
+ } catch {
215
+ // Non-fatal — dispatch continues if state/branch check fails
216
+ }
217
+
194
218
  // If we had critical issues that couldn't be auto-healed, block dispatch
195
219
  if (issues.length > 0) {
196
220
  return {
@@ -45,7 +45,15 @@ export type DoctorIssueCode =
45
45
  | "env_python"
46
46
  | "env_cargo"
47
47
  | "env_go"
48
- | "env_git_remote";
48
+ | "env_git_remote"
49
+ // Provider / auth checks
50
+ | "provider_key_missing"
51
+ | "provider_key_backedoff"
52
+ // Lock infrastructure checks
53
+ | "stranded_lock_directory"
54
+ // Git / worktree integrity checks
55
+ | "integration_branch_missing"
56
+ | "worktree_directory_orphaned";
49
57
 
50
58
  /**
51
59
  * Issue codes that represent expected completion-transition states.
@@ -11,6 +11,7 @@ import type { DoctorIssue, DoctorIssueCode } from "./doctor-types.js";
11
11
  import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
12
12
  import { checkGitHealth, checkRuntimeHealth } from "./doctor-checks.js";
13
13
  import { checkEnvironmentHealth } from "./doctor-environment.js";
14
+ import { runProviderChecks } from "./doctor-providers.js";
14
15
 
15
16
  // ── Re-exports ─────────────────────────────────────────────────────────────
16
17
  // All public types and functions from extracted modules are re-exported here
@@ -406,6 +407,40 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
406
407
  issues.push(...auditRequirements(requirementsContent));
407
408
 
408
409
  const state = await deriveState(basePath);
410
+
411
+ // Provider / auth health checks — only relevant when there is active work to dispatch.
412
+ // Skipped for idle projects (no active milestone) to avoid noise in environments
413
+ // where CI/test runners have no API key configured.
414
+ if (state.activeMilestone) {
415
+ try {
416
+ const providerResults = runProviderChecks();
417
+ for (const result of providerResults) {
418
+ if (!result.required) continue;
419
+ if (result.status === "error") {
420
+ issues.push({
421
+ severity: "warning",
422
+ code: "provider_key_missing",
423
+ scope: "project",
424
+ unitId: "project",
425
+ message: result.message + (result.detail ? ` — ${result.detail}` : ""),
426
+ fixable: false,
427
+ });
428
+ } else if (result.status === "warning") {
429
+ issues.push({
430
+ severity: "warning",
431
+ code: "provider_key_backedoff",
432
+ scope: "project",
433
+ unitId: "project",
434
+ message: result.message + (result.detail ? ` — ${result.detail}` : ""),
435
+ fixable: false,
436
+ });
437
+ }
438
+ }
439
+ } catch {
440
+ // Non-fatal — provider check failure should not block other checks
441
+ }
442
+ }
443
+
409
444
  for (const milestone of state.registry) {
410
445
  const milestoneId = milestone.id;
411
446
  const milestonePath = resolveMilestonePath(basePath, milestoneId);