gsd-pi 2.38.0-dev.eeb3520 → 2.39.0-dev.20aba06

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 (346) hide show
  1. package/README.md +15 -11
  2. package/dist/app-paths.js +1 -1
  3. package/dist/cli.js +9 -0
  4. package/dist/extension-discovery.d.ts +5 -3
  5. package/dist/extension-discovery.js +14 -9
  6. package/dist/extension-registry.js +2 -2
  7. package/dist/remote-questions-config.js +2 -2
  8. package/dist/resource-loader.js +100 -3
  9. package/dist/resources/extensions/async-jobs/index.js +10 -0
  10. package/dist/resources/extensions/browser-tools/index.js +3 -1
  11. package/dist/resources/extensions/browser-tools/package.json +3 -1
  12. package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
  13. package/dist/resources/extensions/cmux/index.js +55 -1
  14. package/dist/resources/extensions/context7/package.json +1 -1
  15. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  16. package/dist/resources/extensions/github-sync/cli.js +284 -0
  17. package/dist/resources/extensions/github-sync/index.js +73 -0
  18. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  19. package/dist/resources/extensions/github-sync/sync.js +424 -0
  20. package/dist/resources/extensions/github-sync/templates.js +118 -0
  21. package/dist/resources/extensions/github-sync/types.js +7 -0
  22. package/dist/resources/extensions/google-search/package.json +3 -1
  23. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  24. package/dist/resources/extensions/gsd/auto-dashboard.js +7 -0
  25. package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
  26. package/dist/resources/extensions/gsd/auto-loop.js +923 -787
  27. package/dist/resources/extensions/gsd/auto-post-unit.js +107 -70
  28. package/dist/resources/extensions/gsd/auto-prompts.js +205 -51
  29. package/dist/resources/extensions/gsd/auto-start.js +19 -3
  30. package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
  31. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  32. package/dist/resources/extensions/gsd/auto.js +149 -100
  33. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +126 -0
  34. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +233 -0
  35. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +59 -0
  36. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +38 -0
  37. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +156 -0
  38. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +46 -0
  39. package/dist/resources/extensions/gsd/bootstrap/system-context.js +300 -0
  40. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +38 -0
  41. package/dist/resources/extensions/gsd/captures.js +9 -1
  42. package/dist/resources/extensions/gsd/commands/catalog.js +278 -0
  43. package/dist/resources/extensions/gsd/commands/context.js +84 -0
  44. package/dist/resources/extensions/gsd/commands/dispatcher.js +21 -0
  45. package/dist/resources/extensions/gsd/commands/handlers/auto.js +72 -0
  46. package/dist/resources/extensions/gsd/commands/handlers/core.js +246 -0
  47. package/dist/resources/extensions/gsd/commands/handlers/ops.js +166 -0
  48. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +94 -0
  49. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +102 -0
  50. package/dist/resources/extensions/gsd/commands/index.js +11 -0
  51. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  52. package/dist/resources/extensions/gsd/commands-handlers.js +17 -4
  53. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  54. package/dist/resources/extensions/gsd/commands.js +8 -1169
  55. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  56. package/dist/resources/extensions/gsd/dashboard-overlay.js +9 -0
  57. package/dist/resources/extensions/gsd/detection.js +1 -2
  58. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  59. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  60. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  61. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  62. package/dist/resources/extensions/gsd/doctor-proactive.js +80 -10
  63. package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
  64. package/dist/resources/extensions/gsd/doctor.js +234 -12
  65. package/dist/resources/extensions/gsd/env-utils.js +29 -0
  66. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  67. package/dist/resources/extensions/gsd/export-html.js +46 -0
  68. package/dist/resources/extensions/gsd/export.js +1 -1
  69. package/dist/resources/extensions/gsd/files.js +48 -9
  70. package/dist/resources/extensions/gsd/forensics.js +1 -1
  71. package/dist/resources/extensions/gsd/git-service.js +30 -12
  72. package/dist/resources/extensions/gsd/gitignore.js +16 -3
  73. package/dist/resources/extensions/gsd/guided-flow.js +149 -38
  74. package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
  75. package/dist/resources/extensions/gsd/health-widget.js +4 -87
  76. package/dist/resources/extensions/gsd/index.js +4 -1111
  77. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  78. package/dist/resources/extensions/gsd/migrate-external.js +18 -1
  79. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  80. package/dist/resources/extensions/gsd/package.json +1 -1
  81. package/dist/resources/extensions/gsd/paths.js +3 -0
  82. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  83. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  84. package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
  85. package/dist/resources/extensions/gsd/preferences.js +22 -11
  86. package/dist/resources/extensions/gsd/progress-score.js +20 -1
  87. package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
  88. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  89. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  90. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  91. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
  92. package/dist/resources/extensions/gsd/prompts/forensics.md +121 -46
  93. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  94. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  95. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  96. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  97. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  98. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  99. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  100. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  101. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  102. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  103. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  104. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  105. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  106. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  107. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  108. package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
  109. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  110. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  111. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  112. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  113. package/dist/resources/extensions/gsd/state.js +42 -23
  114. package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
  115. package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
  116. package/dist/resources/extensions/gsd/visualizer-data.js +27 -2
  117. package/dist/resources/extensions/gsd/visualizer-views.js +52 -0
  118. package/dist/resources/extensions/gsd/worktree.js +35 -16
  119. package/dist/resources/extensions/mcp-client/index.js +14 -1
  120. package/dist/resources/extensions/remote-questions/status.js +4 -1
  121. package/dist/resources/extensions/remote-questions/store.js +4 -1
  122. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  123. package/dist/resources/extensions/shared/frontmatter.js +1 -1
  124. package/dist/resources/extensions/subagent/index.js +12 -3
  125. package/dist/resources/extensions/subagent/isolation.js +2 -1
  126. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  127. package/dist/resources/extensions/universal-config/package.json +1 -1
  128. package/dist/welcome-screen.d.ts +13 -0
  129. package/dist/welcome-screen.js +97 -0
  130. package/package.json +1 -1
  131. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  132. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  133. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  134. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
  135. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/agent-session.js +107 -24
  137. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  140. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  143. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  144. package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts +2 -0
  145. package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts.map +1 -0
  146. package/packages/pi-coding-agent/dist/core/skill-tool.test.js +70 -0
  147. package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -0
  148. package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
  149. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  150. package/packages/pi-coding-agent/dist/core/skills.js +8 -2
  151. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  153. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  154. package/packages/pi-coding-agent/dist/index.js +1 -1
  155. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  156. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +17 -0
  157. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -0
  158. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +244 -0
  159. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -0
  160. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts +3 -0
  161. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -0
  162. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +58 -0
  163. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -0
  164. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +12 -0
  165. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -0
  166. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +54 -0
  167. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -0
  168. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts +6 -0
  169. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -0
  170. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +63 -0
  171. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -0
  172. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +38 -0
  173. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -0
  174. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js +2 -0
  175. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -0
  176. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -1
  177. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  178. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -457
  179. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  180. package/packages/pi-coding-agent/package.json +1 -1
  181. package/packages/pi-coding-agent/src/core/agent-session.ts +122 -23
  182. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  183. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  184. package/packages/pi-coding-agent/src/core/skill-tool.test.ts +89 -0
  185. package/packages/pi-coding-agent/src/core/skills.ts +11 -2
  186. package/packages/pi-coding-agent/src/index.ts +1 -0
  187. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +302 -0
  188. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +59 -0
  189. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +68 -0
  190. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +71 -0
  191. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +37 -0
  192. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +18 -510
  193. package/pkg/package.json +1 -1
  194. package/src/resources/extensions/async-jobs/index.ts +11 -0
  195. package/src/resources/extensions/browser-tools/index.ts +3 -0
  196. package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
  197. package/src/resources/extensions/cmux/index.ts +57 -1
  198. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  199. package/src/resources/extensions/github-sync/cli.ts +364 -0
  200. package/src/resources/extensions/github-sync/index.ts +93 -0
  201. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  202. package/src/resources/extensions/github-sync/sync.ts +556 -0
  203. package/src/resources/extensions/github-sync/templates.ts +183 -0
  204. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  205. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  206. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  207. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  208. package/src/resources/extensions/github-sync/types.ts +47 -0
  209. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  210. package/src/resources/extensions/gsd/auto-dashboard.ts +10 -0
  211. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
  212. package/src/resources/extensions/gsd/auto-loop.ts +1285 -1138
  213. package/src/resources/extensions/gsd/auto-post-unit.ts +90 -46
  214. package/src/resources/extensions/gsd/auto-prompts.ts +250 -53
  215. package/src/resources/extensions/gsd/auto-start.ts +24 -3
  216. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  217. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  218. package/src/resources/extensions/gsd/auto.ts +152 -111
  219. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +142 -0
  220. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +238 -0
  221. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +90 -0
  222. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +46 -0
  223. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +167 -0
  224. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +55 -0
  225. package/src/resources/extensions/gsd/bootstrap/system-context.ts +340 -0
  226. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +51 -0
  227. package/src/resources/extensions/gsd/captures.ts +10 -1
  228. package/src/resources/extensions/gsd/commands/catalog.ts +301 -0
  229. package/src/resources/extensions/gsd/commands/context.ts +101 -0
  230. package/src/resources/extensions/gsd/commands/dispatcher.ts +32 -0
  231. package/src/resources/extensions/gsd/commands/handlers/auto.ts +74 -0
  232. package/src/resources/extensions/gsd/commands/handlers/core.ts +274 -0
  233. package/src/resources/extensions/gsd/commands/handlers/ops.ts +169 -0
  234. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +118 -0
  235. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +109 -0
  236. package/src/resources/extensions/gsd/commands/index.ts +14 -0
  237. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  238. package/src/resources/extensions/gsd/commands-handlers.ts +18 -3
  239. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  240. package/src/resources/extensions/gsd/commands.ts +10 -1307
  241. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  242. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
  243. package/src/resources/extensions/gsd/detection.ts +2 -2
  244. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  245. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  246. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  247. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  248. package/src/resources/extensions/gsd/doctor-proactive.ts +106 -10
  249. package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
  250. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  251. package/src/resources/extensions/gsd/doctor.ts +243 -14
  252. package/src/resources/extensions/gsd/env-utils.ts +31 -0
  253. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  254. package/src/resources/extensions/gsd/export-html.ts +51 -0
  255. package/src/resources/extensions/gsd/export.ts +1 -1
  256. package/src/resources/extensions/gsd/files.ts +51 -11
  257. package/src/resources/extensions/gsd/forensics.ts +1 -1
  258. package/src/resources/extensions/gsd/git-service.ts +44 -10
  259. package/src/resources/extensions/gsd/gitignore.ts +17 -3
  260. package/src/resources/extensions/gsd/guided-flow.ts +177 -44
  261. package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
  262. package/src/resources/extensions/gsd/health-widget.ts +4 -89
  263. package/src/resources/extensions/gsd/index.ts +12 -1307
  264. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  265. package/src/resources/extensions/gsd/migrate-external.ts +18 -1
  266. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  267. package/src/resources/extensions/gsd/paths.ts +4 -0
  268. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  269. package/src/resources/extensions/gsd/preferences-types.ts +4 -4
  270. package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
  271. package/src/resources/extensions/gsd/preferences.ts +25 -11
  272. package/src/resources/extensions/gsd/progress-score.ts +23 -0
  273. package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
  274. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  275. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  276. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  277. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
  278. package/src/resources/extensions/gsd/prompts/forensics.md +121 -46
  279. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  280. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  281. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  282. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  283. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  284. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  285. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  286. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  287. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  288. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  289. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  290. package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  291. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  292. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  293. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  294. package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
  295. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  296. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  297. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  298. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  299. package/src/resources/extensions/gsd/state.ts +39 -21
  300. package/src/resources/extensions/gsd/templates/runtime.md +21 -0
  301. package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
  302. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  303. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +135 -77
  304. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
  305. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  306. package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
  307. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  308. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +3 -3
  309. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
  310. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
  311. package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
  312. package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
  313. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
  314. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  315. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  316. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +16 -16
  317. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  318. package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
  319. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
  320. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +10 -10
  321. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  322. package/src/resources/extensions/gsd/types.ts +18 -1
  323. package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
  324. package/src/resources/extensions/gsd/visualizer-data.ts +52 -2
  325. package/src/resources/extensions/gsd/visualizer-views.ts +58 -0
  326. package/src/resources/extensions/gsd/worktree.ts +35 -15
  327. package/src/resources/extensions/mcp-client/index.ts +17 -1
  328. package/src/resources/extensions/remote-questions/status.ts +5 -1
  329. package/src/resources/extensions/remote-questions/store.ts +5 -1
  330. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  331. package/src/resources/extensions/shared/frontmatter.ts +1 -1
  332. package/src/resources/extensions/subagent/index.ts +12 -3
  333. package/src/resources/extensions/subagent/isolation.ts +3 -1
  334. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  335. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  336. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  337. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  338. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  339. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  340. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  341. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  342. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  343. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  344. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  345. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  346. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Persistence layer for the GitHub sync mapping.
3
+ *
4
+ * The mapping lives at `.gsd/github-sync.json` and tracks which GSD
5
+ * entities have been synced to which GitHub entities (issues, PRs,
6
+ * milestones) along with their numbers and sync timestamps.
7
+ */
8
+
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { atomicWriteSync } from "../gsd/atomic-write.js";
12
+ import type { SyncMapping, MilestoneSyncRecord, SliceSyncRecord, SyncEntityRecord } from "./types.js";
13
+
14
+ const MAPPING_FILENAME = "github-sync.json";
15
+
16
+ function mappingPath(basePath: string): string {
17
+ return join(basePath, ".gsd", MAPPING_FILENAME);
18
+ }
19
+
20
+ // ─── Load / Save ────────────────────────────────────────────────────────────
21
+
22
+ export function loadSyncMapping(basePath: string): SyncMapping | null {
23
+ const path = mappingPath(basePath);
24
+ if (!existsSync(path)) return null;
25
+ try {
26
+ const raw = readFileSync(path, "utf-8");
27
+ const parsed = JSON.parse(raw);
28
+ if (parsed?.version !== 1) return null;
29
+ return parsed as SyncMapping;
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ export function saveSyncMapping(basePath: string, mapping: SyncMapping): void {
36
+ const path = mappingPath(basePath);
37
+ atomicWriteSync(path, JSON.stringify(mapping, null, 2) + "\n");
38
+ }
39
+
40
+ export function createEmptyMapping(repo: string): SyncMapping {
41
+ return {
42
+ version: 1,
43
+ repo,
44
+ milestones: {},
45
+ slices: {},
46
+ tasks: {},
47
+ };
48
+ }
49
+
50
+ // ─── Accessors ──────────────────────────────────────────────────────────────
51
+
52
+ export function getMilestoneRecord(mapping: SyncMapping, mid: string): MilestoneSyncRecord | null {
53
+ return mapping.milestones[mid] ?? null;
54
+ }
55
+
56
+ export function getSliceRecord(mapping: SyncMapping, mid: string, sid: string): SliceSyncRecord | null {
57
+ return mapping.slices[`${mid}/${sid}`] ?? null;
58
+ }
59
+
60
+ export function getTaskRecord(mapping: SyncMapping, mid: string, sid: string, tid: string): SyncEntityRecord | null {
61
+ return mapping.tasks[`${mid}/${sid}/${tid}`] ?? null;
62
+ }
63
+
64
+ export function getTaskIssueNumber(mapping: SyncMapping, mid: string, sid: string, tid: string): number | null {
65
+ const record = getTaskRecord(mapping, mid, sid, tid);
66
+ return record?.issueNumber ?? null;
67
+ }
68
+
69
+ // ─── Mutators ───────────────────────────────────────────────────────────────
70
+
71
+ export function setMilestoneRecord(mapping: SyncMapping, mid: string, record: MilestoneSyncRecord): void {
72
+ mapping.milestones[mid] = record;
73
+ }
74
+
75
+ export function setSliceRecord(mapping: SyncMapping, mid: string, sid: string, record: SliceSyncRecord): void {
76
+ mapping.slices[`${mid}/${sid}`] = record;
77
+ }
78
+
79
+ export function setTaskRecord(mapping: SyncMapping, mid: string, sid: string, tid: string, record: SyncEntityRecord): void {
80
+ mapping.tasks[`${mid}/${sid}/${tid}`] = record;
81
+ }
@@ -0,0 +1,556 @@
1
+ /**
2
+ * Core GitHub sync engine.
3
+ *
4
+ * Entry point: `runGitHubSync()` — called from the GSD post-unit pipeline.
5
+ * Routes to per-event sync functions based on the unit type, reads GSD
6
+ * files to build GitHub entities, and persists the sync mapping.
7
+ *
8
+ * All errors are caught internally — sync failures never block execution.
9
+ */
10
+
11
+ import { existsSync, readdirSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { loadFile, parseRoadmap, parsePlan, parseSummary } from "../gsd/files.js";
14
+ import {
15
+ resolveMilestoneFile,
16
+ resolveSliceFile,
17
+ resolveTaskFile,
18
+ } from "../gsd/paths.js";
19
+ import { debugLog } from "../gsd/debug-logger.js";
20
+ import { loadEffectiveGSDPreferences } from "../gsd/preferences.js";
21
+
22
+ import type { GitHubSyncConfig, SyncMapping } from "./types.js";
23
+ import {
24
+ loadSyncMapping,
25
+ saveSyncMapping,
26
+ createEmptyMapping,
27
+ getMilestoneRecord,
28
+ getSliceRecord,
29
+ getTaskRecord,
30
+ setMilestoneRecord,
31
+ setSliceRecord,
32
+ setTaskRecord,
33
+ } from "./mapping.js";
34
+ import {
35
+ ghIsAvailable,
36
+ ghHasRateLimit,
37
+ ghDetectRepo,
38
+ ghCreateIssue,
39
+ ghCloseIssue,
40
+ ghAddComment,
41
+ ghCreateMilestone,
42
+ ghCloseMilestone,
43
+ ghCreatePR,
44
+ ghMarkPRReady,
45
+ ghMergePR,
46
+ ghCreateBranch,
47
+ ghPushBranch,
48
+ ghAddToProject,
49
+ } from "./cli.js";
50
+ import {
51
+ formatMilestoneIssueBody,
52
+ formatSlicePRBody,
53
+ formatTaskIssueBody,
54
+ formatSummaryComment,
55
+ } from "./templates.js";
56
+
57
+ // ─── Entry Point ────────────────────────────────────────────────────────────
58
+
59
+ /**
60
+ * Main sync entry point — called from GSD post-unit pipeline.
61
+ * Routes to the appropriate sync function based on unit type.
62
+ */
63
+ export async function runGitHubSync(
64
+ basePath: string,
65
+ unitType: string,
66
+ unitId: string,
67
+ ): Promise<void> {
68
+ try {
69
+ const config = loadGitHubSyncConfig(basePath);
70
+ if (!config?.enabled) return;
71
+ if (!ghIsAvailable()) {
72
+ debugLog("github-sync", { skip: "gh CLI not available" });
73
+ return;
74
+ }
75
+
76
+ // Resolve repo
77
+ const repo = config.repo ?? resolveRepo(basePath);
78
+ if (!repo) {
79
+ debugLog("github-sync", { skip: "could not detect repo" });
80
+ return;
81
+ }
82
+
83
+ // Rate limit check
84
+ if (!ghHasRateLimit(basePath)) {
85
+ debugLog("github-sync", { skip: "rate limit low" });
86
+ return;
87
+ }
88
+
89
+ // Load or init mapping
90
+ let mapping = loadSyncMapping(basePath) ?? createEmptyMapping(repo);
91
+ mapping.repo = repo;
92
+
93
+ // Parse unit ID parts
94
+ const parts = unitId.split("/");
95
+ const [mid, sid, tid] = parts;
96
+
97
+ // Route by unit type
98
+ switch (unitType) {
99
+ case "plan-milestone":
100
+ if (mid) await syncMilestonePlan(basePath, mapping, config, mid);
101
+ break;
102
+ case "plan-slice":
103
+ case "research-slice":
104
+ if (mid && sid) await syncSlicePlan(basePath, mapping, config, mid, sid);
105
+ break;
106
+ case "execute-task":
107
+ case "reactive-execute":
108
+ if (mid && sid && tid) await syncTaskComplete(basePath, mapping, config, mid, sid, tid);
109
+ break;
110
+ case "complete-slice":
111
+ if (mid && sid) await syncSliceComplete(basePath, mapping, config, mid, sid);
112
+ break;
113
+ case "complete-milestone":
114
+ if (mid) await syncMilestoneComplete(basePath, mapping, config, mid);
115
+ break;
116
+ }
117
+
118
+ saveSyncMapping(basePath, mapping);
119
+ } catch (err) {
120
+ debugLog("github-sync", { error: String(err) });
121
+ }
122
+ }
123
+
124
+ // ─── Per-Event Sync Functions ───────────────────────────────────────────────
125
+
126
+ async function syncMilestonePlan(
127
+ basePath: string,
128
+ mapping: SyncMapping,
129
+ config: GitHubSyncConfig,
130
+ mid: string,
131
+ ): Promise<void> {
132
+ // Skip if already synced
133
+ if (getMilestoneRecord(mapping, mid)) return;
134
+
135
+ // Load roadmap data
136
+ const roadmapPath = resolveMilestoneFile(basePath, mid, "ROADMAP");
137
+ if (!roadmapPath) return;
138
+ const content = await loadFile(roadmapPath);
139
+ if (!content) return;
140
+
141
+ const roadmap = parseRoadmap(content);
142
+ const title = `${mid}: ${roadmap.title || "Milestone"}`;
143
+
144
+ // Create GitHub Milestone
145
+ const milestoneResult = ghCreateMilestone(
146
+ basePath,
147
+ mapping.repo,
148
+ title,
149
+ roadmap.vision || "",
150
+ );
151
+ if (!milestoneResult.ok) {
152
+ debugLog("github-sync", { phase: "create-milestone", error: milestoneResult.error });
153
+ return;
154
+ }
155
+ const ghMilestoneNumber = milestoneResult.data!;
156
+
157
+ // Create tracking issue
158
+ const issueBody = formatMilestoneIssueBody({
159
+ id: mid,
160
+ title: roadmap.title || "Milestone",
161
+ vision: roadmap.vision,
162
+ successCriteria: roadmap.successCriteria,
163
+ slices: roadmap.slices?.map(s => ({
164
+ id: s.id,
165
+ title: s.title,
166
+ })),
167
+ });
168
+
169
+ const issueResult = ghCreateIssue(basePath, {
170
+ repo: mapping.repo,
171
+ title: `${mid}: ${roadmap.title || "Milestone"} — Tracking`,
172
+ body: issueBody,
173
+ labels: config.labels,
174
+ milestone: ghMilestoneNumber,
175
+ });
176
+ if (!issueResult.ok) {
177
+ debugLog("github-sync", { phase: "create-tracking-issue", error: issueResult.error });
178
+ return;
179
+ }
180
+
181
+ // Add to project if configured
182
+ if (config.project) {
183
+ ghAddToProject(basePath, mapping.repo, config.project, issueResult.data!);
184
+ }
185
+
186
+ setMilestoneRecord(mapping, mid, {
187
+ issueNumber: issueResult.data!,
188
+ ghMilestoneNumber,
189
+ lastSyncedAt: new Date().toISOString(),
190
+ state: "open",
191
+ });
192
+
193
+ debugLog("github-sync", {
194
+ phase: "milestone-synced",
195
+ mid,
196
+ milestone: ghMilestoneNumber,
197
+ issue: issueResult.data,
198
+ });
199
+ }
200
+
201
+ async function syncSlicePlan(
202
+ basePath: string,
203
+ mapping: SyncMapping,
204
+ config: GitHubSyncConfig,
205
+ mid: string,
206
+ sid: string,
207
+ ): Promise<void> {
208
+ // Skip if already synced
209
+ if (getSliceRecord(mapping, mid, sid)) return;
210
+
211
+ // Ensure milestone is synced first
212
+ if (!getMilestoneRecord(mapping, mid)) {
213
+ await syncMilestonePlan(basePath, mapping, config, mid);
214
+ }
215
+ const milestoneRecord = getMilestoneRecord(mapping, mid);
216
+
217
+ // Load slice plan
218
+ const planPath = resolveSliceFile(basePath, mid, sid, "PLAN");
219
+ if (!planPath) return;
220
+ const content = await loadFile(planPath);
221
+ if (!content) return;
222
+
223
+ const plan = parsePlan(content);
224
+ const sliceBranch = `milestone/${mid}/${sid}`;
225
+ const milestoneBranch = `milestone/${mid}`;
226
+
227
+ // Create task sub-issues first (so we can link them in the PR body)
228
+ const taskIssueNumbers: Array<{ id: string; title: string; issueNumber?: number }> = [];
229
+
230
+ if (plan.tasks) {
231
+ for (const task of plan.tasks) {
232
+ // Skip if already synced
233
+ if (getTaskRecord(mapping, mid, sid, task.id)) {
234
+ const existing = getTaskRecord(mapping, mid, sid, task.id)!;
235
+ taskIssueNumbers.push({ id: task.id, title: task.title, issueNumber: existing.issueNumber });
236
+ continue;
237
+ }
238
+
239
+ const taskBody = formatTaskIssueBody({
240
+ id: task.id,
241
+ title: task.title,
242
+ description: task.description,
243
+ files: task.files,
244
+ verifyCriteria: task.verify ? [task.verify] : undefined,
245
+ });
246
+
247
+ const taskResult = ghCreateIssue(basePath, {
248
+ repo: mapping.repo,
249
+ title: `${mid}/${sid}/${task.id}: ${task.title}`,
250
+ body: taskBody,
251
+ labels: config.labels,
252
+ milestone: milestoneRecord?.ghMilestoneNumber,
253
+ parentIssue: milestoneRecord?.issueNumber,
254
+ });
255
+
256
+ if (taskResult.ok) {
257
+ setTaskRecord(mapping, mid, sid, task.id, {
258
+ issueNumber: taskResult.data!,
259
+ lastSyncedAt: new Date().toISOString(),
260
+ state: "open",
261
+ });
262
+ taskIssueNumbers.push({ id: task.id, title: task.title, issueNumber: taskResult.data! });
263
+
264
+ if (config.project) {
265
+ ghAddToProject(basePath, mapping.repo, config.project, taskResult.data!);
266
+ }
267
+ } else {
268
+ taskIssueNumbers.push({ id: task.id, title: task.title });
269
+ }
270
+ }
271
+ }
272
+
273
+ if (config.slice_prs === false) {
274
+ // Slice PRs disabled — just record without PR
275
+ setSliceRecord(mapping, mid, sid, {
276
+ issueNumber: 0,
277
+ prNumber: 0,
278
+ branch: sliceBranch,
279
+ lastSyncedAt: new Date().toISOString(),
280
+ state: "open",
281
+ });
282
+ return;
283
+ }
284
+
285
+ // Create slice branch from milestone branch
286
+ const branchResult = ghCreateBranch(basePath, sliceBranch, milestoneBranch);
287
+ if (!branchResult.ok) {
288
+ debugLog("github-sync", { phase: "create-slice-branch", error: branchResult.error });
289
+ // Branch might already exist — continue anyway
290
+ }
291
+
292
+ // Push the slice branch
293
+ const pushResult = ghPushBranch(basePath, sliceBranch);
294
+ if (!pushResult.ok) {
295
+ debugLog("github-sync", { phase: "push-slice-branch", error: pushResult.error });
296
+ }
297
+
298
+ // Create draft PR
299
+ const prBody = formatSlicePRBody({
300
+ id: sid,
301
+ title: plan.title || sid,
302
+ goal: plan.goal,
303
+ mustHaves: plan.mustHaves,
304
+ demoCriterion: plan.demo,
305
+ tasks: taskIssueNumbers,
306
+ });
307
+
308
+ const prResult = ghCreatePR(basePath, {
309
+ repo: mapping.repo,
310
+ base: milestoneBranch,
311
+ head: sliceBranch,
312
+ title: `${sid}: ${plan.title || sid}`,
313
+ body: prBody,
314
+ draft: true,
315
+ });
316
+
317
+ const prNumber = prResult.ok ? prResult.data! : 0;
318
+ if (!prResult.ok) {
319
+ debugLog("github-sync", { phase: "create-slice-pr", error: prResult.error });
320
+ }
321
+
322
+ setSliceRecord(mapping, mid, sid, {
323
+ issueNumber: 0, // Slice doesn't get its own issue — tracked via PR
324
+ prNumber,
325
+ branch: sliceBranch,
326
+ lastSyncedAt: new Date().toISOString(),
327
+ state: "open",
328
+ });
329
+
330
+ debugLog("github-sync", {
331
+ phase: "slice-synced",
332
+ mid,
333
+ sid,
334
+ pr: prNumber,
335
+ taskIssues: taskIssueNumbers.filter(t => t.issueNumber).length,
336
+ });
337
+ }
338
+
339
+ async function syncTaskComplete(
340
+ basePath: string,
341
+ mapping: SyncMapping,
342
+ config: GitHubSyncConfig,
343
+ mid: string,
344
+ sid: string,
345
+ tid: string,
346
+ ): Promise<void> {
347
+ const taskRecord = getTaskRecord(mapping, mid, sid, tid);
348
+ if (!taskRecord || taskRecord.state === "closed") return;
349
+
350
+ // Load task summary
351
+ const summaryPath = resolveTaskFile(basePath, mid, sid, tid, "SUMMARY");
352
+ if (summaryPath) {
353
+ const content = await loadFile(summaryPath);
354
+ if (content) {
355
+ const summary = parseSummary(content);
356
+ const comment = formatSummaryComment({
357
+ oneLiner: summary.oneLiner,
358
+ body: summary.whatHappened,
359
+ frontmatter: summary.frontmatter as unknown as Record<string, unknown>,
360
+ });
361
+ ghAddComment(basePath, mapping.repo, taskRecord.issueNumber, comment);
362
+ }
363
+ }
364
+
365
+ // Close the task issue
366
+ ghCloseIssue(basePath, mapping.repo, taskRecord.issueNumber);
367
+
368
+ taskRecord.state = "closed";
369
+ taskRecord.lastSyncedAt = new Date().toISOString();
370
+ setTaskRecord(mapping, mid, sid, tid, taskRecord);
371
+
372
+ debugLog("github-sync", { phase: "task-closed", mid, sid, tid, issue: taskRecord.issueNumber });
373
+ }
374
+
375
+ async function syncSliceComplete(
376
+ basePath: string,
377
+ mapping: SyncMapping,
378
+ config: GitHubSyncConfig,
379
+ mid: string,
380
+ sid: string,
381
+ ): Promise<void> {
382
+ const sliceRecord = getSliceRecord(mapping, mid, sid);
383
+ if (!sliceRecord || sliceRecord.state === "closed") return;
384
+
385
+ // Post slice summary as PR comment
386
+ const summaryPath = resolveSliceFile(basePath, mid, sid, "SUMMARY");
387
+ if (summaryPath && sliceRecord.prNumber) {
388
+ const content = await loadFile(summaryPath);
389
+ if (content) {
390
+ const summary = parseSummary(content);
391
+ const comment = formatSummaryComment({
392
+ oneLiner: summary.oneLiner,
393
+ body: summary.whatHappened,
394
+ frontmatter: summary.frontmatter as unknown as Record<string, unknown>,
395
+ });
396
+ ghAddComment(basePath, mapping.repo, sliceRecord.prNumber, comment);
397
+ }
398
+ }
399
+
400
+ // Mark PR ready for review, then merge
401
+ if (sliceRecord.prNumber) {
402
+ ghMarkPRReady(basePath, mapping.repo, sliceRecord.prNumber);
403
+ // Squash-merge into milestone branch
404
+ ghMergePR(basePath, mapping.repo, sliceRecord.prNumber, "squash");
405
+ }
406
+
407
+ sliceRecord.state = "closed";
408
+ sliceRecord.lastSyncedAt = new Date().toISOString();
409
+ setSliceRecord(mapping, mid, sid, sliceRecord);
410
+
411
+ debugLog("github-sync", { phase: "slice-completed", mid, sid, pr: sliceRecord.prNumber });
412
+ }
413
+
414
+ async function syncMilestoneComplete(
415
+ basePath: string,
416
+ mapping: SyncMapping,
417
+ config: GitHubSyncConfig,
418
+ mid: string,
419
+ ): Promise<void> {
420
+ const record = getMilestoneRecord(mapping, mid);
421
+ if (!record || record.state === "closed") return;
422
+
423
+ // Close tracking issue
424
+ ghCloseIssue(
425
+ basePath,
426
+ mapping.repo,
427
+ record.issueNumber,
428
+ `Milestone ${mid} completed.`,
429
+ );
430
+
431
+ // Close GitHub milestone
432
+ ghCloseMilestone(basePath, mapping.repo, record.ghMilestoneNumber);
433
+
434
+ record.state = "closed";
435
+ record.lastSyncedAt = new Date().toISOString();
436
+ setMilestoneRecord(mapping, mid, record);
437
+
438
+ debugLog("github-sync", { phase: "milestone-completed", mid });
439
+ }
440
+
441
+ // ─── Bootstrap ──────────────────────────────────────────────────────────────
442
+
443
+ /**
444
+ * Walk the `.gsd/milestones/` tree and create GitHub entities for any
445
+ * that are missing from the sync mapping. Safe to run multiple times.
446
+ */
447
+ export async function bootstrapSync(basePath: string): Promise<{
448
+ milestones: number;
449
+ slices: number;
450
+ tasks: number;
451
+ }> {
452
+ const config = loadGitHubSyncConfig(basePath);
453
+ if (!config?.enabled) return { milestones: 0, slices: 0, tasks: 0 };
454
+ if (!ghIsAvailable()) return { milestones: 0, slices: 0, tasks: 0 };
455
+
456
+ const repo = config.repo ?? resolveRepo(basePath);
457
+ if (!repo) return { milestones: 0, slices: 0, tasks: 0 };
458
+
459
+ let mapping = loadSyncMapping(basePath) ?? createEmptyMapping(repo);
460
+ mapping.repo = repo;
461
+
462
+ const taskCountBefore = Object.keys(mapping.tasks).length;
463
+ const counts = { milestones: 0, slices: 0, tasks: 0 };
464
+ const milestonesDir = join(basePath, ".gsd", "milestones");
465
+ if (!existsSync(milestonesDir)) return counts;
466
+
467
+ const milestoneIds = readdirSync(milestonesDir, { withFileTypes: true })
468
+ .filter(d => d.isDirectory())
469
+ .map(d => d.name)
470
+ .sort();
471
+
472
+ for (const mid of milestoneIds) {
473
+ if (!getMilestoneRecord(mapping, mid)) {
474
+ await syncMilestonePlan(basePath, mapping, config, mid);
475
+ counts.milestones++;
476
+ }
477
+
478
+ // Find slices
479
+ const slicesDir = join(milestonesDir, mid, "slices");
480
+ if (!existsSync(slicesDir)) continue;
481
+
482
+ const sliceIds = readdirSync(slicesDir, { withFileTypes: true })
483
+ .filter(d => d.isDirectory())
484
+ .map(d => d.name)
485
+ .sort();
486
+
487
+ for (const sid of sliceIds) {
488
+ if (!getSliceRecord(mapping, mid, sid)) {
489
+ await syncSlicePlan(basePath, mapping, config, mid, sid);
490
+ counts.slices++;
491
+ }
492
+ }
493
+ }
494
+
495
+ counts.tasks = Object.keys(mapping.tasks).length - taskCountBefore;
496
+ saveSyncMapping(basePath, mapping);
497
+ return counts;
498
+ }
499
+
500
+ // ─── Config Loading ─────────────────────────────────────────────────────────
501
+
502
+ let _cachedConfig: GitHubSyncConfig | null | undefined;
503
+
504
+ function loadGitHubSyncConfig(_basePath: string): GitHubSyncConfig | null {
505
+ if (_cachedConfig !== undefined) return _cachedConfig;
506
+ try {
507
+ const prefs = loadEffectiveGSDPreferences();
508
+ const github = (prefs?.preferences as Record<string, unknown>)?.github;
509
+ if (!github || typeof github !== "object") {
510
+ _cachedConfig = null;
511
+ return null;
512
+ }
513
+ _cachedConfig = github as GitHubSyncConfig;
514
+ return _cachedConfig;
515
+ } catch {
516
+ _cachedConfig = null;
517
+ return null;
518
+ }
519
+ }
520
+
521
+ /** Reset config cache (for testing). */
522
+ export function _resetConfigCache(): void {
523
+ _cachedConfig = undefined;
524
+ }
525
+
526
+ function resolveRepo(basePath: string): string | null {
527
+ const result = ghDetectRepo(basePath);
528
+ return result.ok ? result.data! : null;
529
+ }
530
+
531
+ // ─── Commit Linking ─────────────────────────────────────────────────────────
532
+
533
+ /**
534
+ * Look up the GitHub issue number for a task so the commit message
535
+ * can include `Resolves #N`. Called from git-service commit building.
536
+ */
537
+ export function getTaskIssueNumberForCommit(
538
+ basePath: string,
539
+ mid: string,
540
+ sid: string,
541
+ tid: string,
542
+ ): number | null {
543
+ try {
544
+ const config = loadGitHubSyncConfig(basePath);
545
+ if (!config?.enabled) return null;
546
+ if (config.auto_link_commits === false) return null;
547
+
548
+ const mapping = loadSyncMapping(basePath);
549
+ if (!mapping) return null;
550
+
551
+ const record = getTaskRecord(mapping, mid, sid, tid);
552
+ return record?.issueNumber ?? null;
553
+ } catch {
554
+ return null;
555
+ }
556
+ }