gsd-pi 2.38.0-dev.eeb3520 → 2.39.0-dev.64cd3ed

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 (255) 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 +34 -1
  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/env-utils.js +29 -0
  16. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  17. package/dist/resources/extensions/github-sync/cli.js +284 -0
  18. package/dist/resources/extensions/github-sync/index.js +73 -0
  19. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  20. package/dist/resources/extensions/github-sync/sync.js +424 -0
  21. package/dist/resources/extensions/github-sync/templates.js +118 -0
  22. package/dist/resources/extensions/github-sync/types.js +7 -0
  23. package/dist/resources/extensions/google-search/package.json +3 -1
  24. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  25. package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
  26. package/dist/resources/extensions/gsd/auto-loop.js +650 -588
  27. package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
  28. package/dist/resources/extensions/gsd/auto-prompts.js +202 -48
  29. package/dist/resources/extensions/gsd/auto-start.js +13 -2
  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 +143 -96
  33. package/dist/resources/extensions/gsd/captures.js +9 -1
  34. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  35. package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
  36. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  37. package/dist/resources/extensions/gsd/commands.js +24 -3
  38. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  39. package/dist/resources/extensions/gsd/detection.js +1 -2
  40. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  41. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  42. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  43. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  44. package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
  45. package/dist/resources/extensions/gsd/doctor.js +204 -12
  46. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  47. package/dist/resources/extensions/gsd/export.js +1 -1
  48. package/dist/resources/extensions/gsd/files.js +48 -9
  49. package/dist/resources/extensions/gsd/forensics.js +1 -1
  50. package/dist/resources/extensions/gsd/git-service.js +30 -12
  51. package/dist/resources/extensions/gsd/gitignore.js +16 -3
  52. package/dist/resources/extensions/gsd/guided-flow.js +149 -38
  53. package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
  54. package/dist/resources/extensions/gsd/health-widget.js +3 -86
  55. package/dist/resources/extensions/gsd/index.js +24 -20
  56. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  57. package/dist/resources/extensions/gsd/migrate-external.js +18 -1
  58. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  59. package/dist/resources/extensions/gsd/package.json +1 -1
  60. package/dist/resources/extensions/gsd/paths.js +3 -0
  61. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  62. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  63. package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
  64. package/dist/resources/extensions/gsd/preferences.js +22 -11
  65. package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
  66. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  67. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  68. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  69. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
  70. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  71. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  72. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  73. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  74. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  75. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  76. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  77. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  78. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  79. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  80. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  81. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  82. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  83. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  84. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  85. package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
  86. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  87. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  88. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  89. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  90. package/dist/resources/extensions/gsd/state.js +42 -23
  91. package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
  92. package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
  93. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  94. package/dist/resources/extensions/gsd/worktree.js +35 -16
  95. package/dist/resources/extensions/mcp-client/index.js +14 -1
  96. package/dist/resources/extensions/remote-questions/status.js +4 -1
  97. package/dist/resources/extensions/remote-questions/store.js +4 -1
  98. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  99. package/dist/resources/extensions/shared/frontmatter.js +1 -1
  100. package/dist/resources/extensions/subagent/index.js +12 -3
  101. package/dist/resources/extensions/subagent/isolation.js +2 -1
  102. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  103. package/dist/resources/extensions/universal-config/package.json +1 -1
  104. package/dist/welcome-screen.d.ts +12 -0
  105. package/dist/welcome-screen.js +53 -0
  106. package/package.json +1 -1
  107. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  108. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  109. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  110. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  112. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  113. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  115. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
  117. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/skills.js +6 -1
  119. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  121. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/index.js +1 -1
  123. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  124. package/packages/pi-coding-agent/package.json +1 -1
  125. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  126. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  127. package/packages/pi-coding-agent/src/core/skills.ts +9 -1
  128. package/packages/pi-coding-agent/src/index.ts +1 -0
  129. package/pkg/package.json +1 -1
  130. package/src/resources/extensions/async-jobs/index.ts +11 -0
  131. package/src/resources/extensions/browser-tools/index.ts +3 -0
  132. package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
  133. package/src/resources/extensions/cmux/index.ts +57 -1
  134. package/src/resources/extensions/env-utils.ts +31 -0
  135. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  136. package/src/resources/extensions/github-sync/cli.ts +364 -0
  137. package/src/resources/extensions/github-sync/index.ts +93 -0
  138. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  139. package/src/resources/extensions/github-sync/sync.ts +556 -0
  140. package/src/resources/extensions/github-sync/templates.ts +183 -0
  141. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  142. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  143. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  144. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  145. package/src/resources/extensions/github-sync/types.ts +47 -0
  146. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  147. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
  148. package/src/resources/extensions/gsd/auto-loop.ts +553 -546
  149. package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
  150. package/src/resources/extensions/gsd/auto-prompts.ts +247 -50
  151. package/src/resources/extensions/gsd/auto-start.ts +18 -2
  152. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  153. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  154. package/src/resources/extensions/gsd/auto.ts +139 -101
  155. package/src/resources/extensions/gsd/captures.ts +10 -1
  156. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  157. package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
  158. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  159. package/src/resources/extensions/gsd/commands.ts +26 -4
  160. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  161. package/src/resources/extensions/gsd/detection.ts +2 -2
  162. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  163. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  164. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  165. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  166. package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
  167. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  168. package/src/resources/extensions/gsd/doctor.ts +199 -14
  169. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  170. package/src/resources/extensions/gsd/export.ts +1 -1
  171. package/src/resources/extensions/gsd/files.ts +51 -11
  172. package/src/resources/extensions/gsd/forensics.ts +1 -1
  173. package/src/resources/extensions/gsd/git-service.ts +44 -10
  174. package/src/resources/extensions/gsd/gitignore.ts +17 -3
  175. package/src/resources/extensions/gsd/guided-flow.ts +177 -44
  176. package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
  177. package/src/resources/extensions/gsd/health-widget.ts +3 -89
  178. package/src/resources/extensions/gsd/index.ts +24 -17
  179. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  180. package/src/resources/extensions/gsd/migrate-external.ts +18 -1
  181. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  182. package/src/resources/extensions/gsd/paths.ts +4 -0
  183. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  184. package/src/resources/extensions/gsd/preferences-types.ts +4 -4
  185. package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
  186. package/src/resources/extensions/gsd/preferences.ts +25 -11
  187. package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
  188. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  189. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  190. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  191. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
  192. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  193. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  194. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  195. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  196. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  197. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  198. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  199. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  200. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  201. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  202. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  203. package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  204. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  205. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  206. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  207. package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
  208. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  209. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  210. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  211. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  212. package/src/resources/extensions/gsd/state.ts +39 -21
  213. package/src/resources/extensions/gsd/templates/runtime.md +21 -0
  214. package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
  215. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  216. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
  217. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
  218. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  219. package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
  220. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  221. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
  222. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
  223. package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
  224. package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
  225. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
  226. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  227. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  228. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  229. package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
  230. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
  231. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  232. package/src/resources/extensions/gsd/types.ts +18 -1
  233. package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
  234. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  235. package/src/resources/extensions/gsd/worktree.ts +35 -15
  236. package/src/resources/extensions/mcp-client/index.ts +17 -1
  237. package/src/resources/extensions/remote-questions/status.ts +5 -1
  238. package/src/resources/extensions/remote-questions/store.ts +5 -1
  239. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  240. package/src/resources/extensions/shared/frontmatter.ts +1 -1
  241. package/src/resources/extensions/subagent/index.ts +12 -3
  242. package/src/resources/extensions/subagent/isolation.ts +3 -1
  243. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  244. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  245. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  246. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  247. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  248. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  249. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  250. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  251. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  252. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  253. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  254. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  255. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -0,0 +1,424 @@
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
+ import { existsSync, readdirSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { loadFile, parseRoadmap, parsePlan, parseSummary } from "../gsd/files.js";
13
+ import { resolveMilestoneFile, resolveSliceFile, resolveTaskFile, } from "../gsd/paths.js";
14
+ import { debugLog } from "../gsd/debug-logger.js";
15
+ import { loadEffectiveGSDPreferences } from "../gsd/preferences.js";
16
+ import { loadSyncMapping, saveSyncMapping, createEmptyMapping, getMilestoneRecord, getSliceRecord, getTaskRecord, setMilestoneRecord, setSliceRecord, setTaskRecord, } from "./mapping.js";
17
+ import { ghIsAvailable, ghHasRateLimit, ghDetectRepo, ghCreateIssue, ghCloseIssue, ghAddComment, ghCreateMilestone, ghCloseMilestone, ghCreatePR, ghMarkPRReady, ghMergePR, ghCreateBranch, ghPushBranch, ghAddToProject, } from "./cli.js";
18
+ import { formatMilestoneIssueBody, formatSlicePRBody, formatTaskIssueBody, formatSummaryComment, } from "./templates.js";
19
+ // ─── Entry Point ────────────────────────────────────────────────────────────
20
+ /**
21
+ * Main sync entry point — called from GSD post-unit pipeline.
22
+ * Routes to the appropriate sync function based on unit type.
23
+ */
24
+ export async function runGitHubSync(basePath, unitType, unitId) {
25
+ try {
26
+ const config = loadGitHubSyncConfig(basePath);
27
+ if (!config?.enabled)
28
+ return;
29
+ if (!ghIsAvailable()) {
30
+ debugLog("github-sync", { skip: "gh CLI not available" });
31
+ return;
32
+ }
33
+ // Resolve repo
34
+ const repo = config.repo ?? resolveRepo(basePath);
35
+ if (!repo) {
36
+ debugLog("github-sync", { skip: "could not detect repo" });
37
+ return;
38
+ }
39
+ // Rate limit check
40
+ if (!ghHasRateLimit(basePath)) {
41
+ debugLog("github-sync", { skip: "rate limit low" });
42
+ return;
43
+ }
44
+ // Load or init mapping
45
+ let mapping = loadSyncMapping(basePath) ?? createEmptyMapping(repo);
46
+ mapping.repo = repo;
47
+ // Parse unit ID parts
48
+ const parts = unitId.split("/");
49
+ const [mid, sid, tid] = parts;
50
+ // Route by unit type
51
+ switch (unitType) {
52
+ case "plan-milestone":
53
+ if (mid)
54
+ await syncMilestonePlan(basePath, mapping, config, mid);
55
+ break;
56
+ case "plan-slice":
57
+ case "research-slice":
58
+ if (mid && sid)
59
+ await syncSlicePlan(basePath, mapping, config, mid, sid);
60
+ break;
61
+ case "execute-task":
62
+ case "reactive-execute":
63
+ if (mid && sid && tid)
64
+ await syncTaskComplete(basePath, mapping, config, mid, sid, tid);
65
+ break;
66
+ case "complete-slice":
67
+ if (mid && sid)
68
+ await syncSliceComplete(basePath, mapping, config, mid, sid);
69
+ break;
70
+ case "complete-milestone":
71
+ if (mid)
72
+ await syncMilestoneComplete(basePath, mapping, config, mid);
73
+ break;
74
+ }
75
+ saveSyncMapping(basePath, mapping);
76
+ }
77
+ catch (err) {
78
+ debugLog("github-sync", { error: String(err) });
79
+ }
80
+ }
81
+ // ─── Per-Event Sync Functions ───────────────────────────────────────────────
82
+ async function syncMilestonePlan(basePath, mapping, config, mid) {
83
+ // Skip if already synced
84
+ if (getMilestoneRecord(mapping, mid))
85
+ return;
86
+ // Load roadmap data
87
+ const roadmapPath = resolveMilestoneFile(basePath, mid, "ROADMAP");
88
+ if (!roadmapPath)
89
+ return;
90
+ const content = await loadFile(roadmapPath);
91
+ if (!content)
92
+ return;
93
+ const roadmap = parseRoadmap(content);
94
+ const title = `${mid}: ${roadmap.title || "Milestone"}`;
95
+ // Create GitHub Milestone
96
+ const milestoneResult = ghCreateMilestone(basePath, mapping.repo, title, roadmap.vision || "");
97
+ if (!milestoneResult.ok) {
98
+ debugLog("github-sync", { phase: "create-milestone", error: milestoneResult.error });
99
+ return;
100
+ }
101
+ const ghMilestoneNumber = milestoneResult.data;
102
+ // Create tracking issue
103
+ const issueBody = formatMilestoneIssueBody({
104
+ id: mid,
105
+ title: roadmap.title || "Milestone",
106
+ vision: roadmap.vision,
107
+ successCriteria: roadmap.successCriteria,
108
+ slices: roadmap.slices?.map(s => ({
109
+ id: s.id,
110
+ title: s.title,
111
+ })),
112
+ });
113
+ const issueResult = ghCreateIssue(basePath, {
114
+ repo: mapping.repo,
115
+ title: `${mid}: ${roadmap.title || "Milestone"} — Tracking`,
116
+ body: issueBody,
117
+ labels: config.labels,
118
+ milestone: ghMilestoneNumber,
119
+ });
120
+ if (!issueResult.ok) {
121
+ debugLog("github-sync", { phase: "create-tracking-issue", error: issueResult.error });
122
+ return;
123
+ }
124
+ // Add to project if configured
125
+ if (config.project) {
126
+ ghAddToProject(basePath, mapping.repo, config.project, issueResult.data);
127
+ }
128
+ setMilestoneRecord(mapping, mid, {
129
+ issueNumber: issueResult.data,
130
+ ghMilestoneNumber,
131
+ lastSyncedAt: new Date().toISOString(),
132
+ state: "open",
133
+ });
134
+ debugLog("github-sync", {
135
+ phase: "milestone-synced",
136
+ mid,
137
+ milestone: ghMilestoneNumber,
138
+ issue: issueResult.data,
139
+ });
140
+ }
141
+ async function syncSlicePlan(basePath, mapping, config, mid, sid) {
142
+ // Skip if already synced
143
+ if (getSliceRecord(mapping, mid, sid))
144
+ return;
145
+ // Ensure milestone is synced first
146
+ if (!getMilestoneRecord(mapping, mid)) {
147
+ await syncMilestonePlan(basePath, mapping, config, mid);
148
+ }
149
+ const milestoneRecord = getMilestoneRecord(mapping, mid);
150
+ // Load slice plan
151
+ const planPath = resolveSliceFile(basePath, mid, sid, "PLAN");
152
+ if (!planPath)
153
+ return;
154
+ const content = await loadFile(planPath);
155
+ if (!content)
156
+ return;
157
+ const plan = parsePlan(content);
158
+ const sliceBranch = `milestone/${mid}/${sid}`;
159
+ const milestoneBranch = `milestone/${mid}`;
160
+ // Create task sub-issues first (so we can link them in the PR body)
161
+ const taskIssueNumbers = [];
162
+ if (plan.tasks) {
163
+ for (const task of plan.tasks) {
164
+ // Skip if already synced
165
+ if (getTaskRecord(mapping, mid, sid, task.id)) {
166
+ const existing = getTaskRecord(mapping, mid, sid, task.id);
167
+ taskIssueNumbers.push({ id: task.id, title: task.title, issueNumber: existing.issueNumber });
168
+ continue;
169
+ }
170
+ const taskBody = formatTaskIssueBody({
171
+ id: task.id,
172
+ title: task.title,
173
+ description: task.description,
174
+ files: task.files,
175
+ verifyCriteria: task.verify ? [task.verify] : undefined,
176
+ });
177
+ const taskResult = ghCreateIssue(basePath, {
178
+ repo: mapping.repo,
179
+ title: `${mid}/${sid}/${task.id}: ${task.title}`,
180
+ body: taskBody,
181
+ labels: config.labels,
182
+ milestone: milestoneRecord?.ghMilestoneNumber,
183
+ parentIssue: milestoneRecord?.issueNumber,
184
+ });
185
+ if (taskResult.ok) {
186
+ setTaskRecord(mapping, mid, sid, task.id, {
187
+ issueNumber: taskResult.data,
188
+ lastSyncedAt: new Date().toISOString(),
189
+ state: "open",
190
+ });
191
+ taskIssueNumbers.push({ id: task.id, title: task.title, issueNumber: taskResult.data });
192
+ if (config.project) {
193
+ ghAddToProject(basePath, mapping.repo, config.project, taskResult.data);
194
+ }
195
+ }
196
+ else {
197
+ taskIssueNumbers.push({ id: task.id, title: task.title });
198
+ }
199
+ }
200
+ }
201
+ if (config.slice_prs === false) {
202
+ // Slice PRs disabled — just record without PR
203
+ setSliceRecord(mapping, mid, sid, {
204
+ issueNumber: 0,
205
+ prNumber: 0,
206
+ branch: sliceBranch,
207
+ lastSyncedAt: new Date().toISOString(),
208
+ state: "open",
209
+ });
210
+ return;
211
+ }
212
+ // Create slice branch from milestone branch
213
+ const branchResult = ghCreateBranch(basePath, sliceBranch, milestoneBranch);
214
+ if (!branchResult.ok) {
215
+ debugLog("github-sync", { phase: "create-slice-branch", error: branchResult.error });
216
+ // Branch might already exist — continue anyway
217
+ }
218
+ // Push the slice branch
219
+ const pushResult = ghPushBranch(basePath, sliceBranch);
220
+ if (!pushResult.ok) {
221
+ debugLog("github-sync", { phase: "push-slice-branch", error: pushResult.error });
222
+ }
223
+ // Create draft PR
224
+ const prBody = formatSlicePRBody({
225
+ id: sid,
226
+ title: plan.title || sid,
227
+ goal: plan.goal,
228
+ mustHaves: plan.mustHaves,
229
+ demoCriterion: plan.demo,
230
+ tasks: taskIssueNumbers,
231
+ });
232
+ const prResult = ghCreatePR(basePath, {
233
+ repo: mapping.repo,
234
+ base: milestoneBranch,
235
+ head: sliceBranch,
236
+ title: `${sid}: ${plan.title || sid}`,
237
+ body: prBody,
238
+ draft: true,
239
+ });
240
+ const prNumber = prResult.ok ? prResult.data : 0;
241
+ if (!prResult.ok) {
242
+ debugLog("github-sync", { phase: "create-slice-pr", error: prResult.error });
243
+ }
244
+ setSliceRecord(mapping, mid, sid, {
245
+ issueNumber: 0, // Slice doesn't get its own issue — tracked via PR
246
+ prNumber,
247
+ branch: sliceBranch,
248
+ lastSyncedAt: new Date().toISOString(),
249
+ state: "open",
250
+ });
251
+ debugLog("github-sync", {
252
+ phase: "slice-synced",
253
+ mid,
254
+ sid,
255
+ pr: prNumber,
256
+ taskIssues: taskIssueNumbers.filter(t => t.issueNumber).length,
257
+ });
258
+ }
259
+ async function syncTaskComplete(basePath, mapping, config, mid, sid, tid) {
260
+ const taskRecord = getTaskRecord(mapping, mid, sid, tid);
261
+ if (!taskRecord || taskRecord.state === "closed")
262
+ return;
263
+ // Load task summary
264
+ const summaryPath = resolveTaskFile(basePath, mid, sid, tid, "SUMMARY");
265
+ if (summaryPath) {
266
+ const content = await loadFile(summaryPath);
267
+ if (content) {
268
+ const summary = parseSummary(content);
269
+ const comment = formatSummaryComment({
270
+ oneLiner: summary.oneLiner,
271
+ body: summary.whatHappened,
272
+ frontmatter: summary.frontmatter,
273
+ });
274
+ ghAddComment(basePath, mapping.repo, taskRecord.issueNumber, comment);
275
+ }
276
+ }
277
+ // Close the task issue
278
+ ghCloseIssue(basePath, mapping.repo, taskRecord.issueNumber);
279
+ taskRecord.state = "closed";
280
+ taskRecord.lastSyncedAt = new Date().toISOString();
281
+ setTaskRecord(mapping, mid, sid, tid, taskRecord);
282
+ debugLog("github-sync", { phase: "task-closed", mid, sid, tid, issue: taskRecord.issueNumber });
283
+ }
284
+ async function syncSliceComplete(basePath, mapping, config, mid, sid) {
285
+ const sliceRecord = getSliceRecord(mapping, mid, sid);
286
+ if (!sliceRecord || sliceRecord.state === "closed")
287
+ return;
288
+ // Post slice summary as PR comment
289
+ const summaryPath = resolveSliceFile(basePath, mid, sid, "SUMMARY");
290
+ if (summaryPath && sliceRecord.prNumber) {
291
+ const content = await loadFile(summaryPath);
292
+ if (content) {
293
+ const summary = parseSummary(content);
294
+ const comment = formatSummaryComment({
295
+ oneLiner: summary.oneLiner,
296
+ body: summary.whatHappened,
297
+ frontmatter: summary.frontmatter,
298
+ });
299
+ ghAddComment(basePath, mapping.repo, sliceRecord.prNumber, comment);
300
+ }
301
+ }
302
+ // Mark PR ready for review, then merge
303
+ if (sliceRecord.prNumber) {
304
+ ghMarkPRReady(basePath, mapping.repo, sliceRecord.prNumber);
305
+ // Squash-merge into milestone branch
306
+ ghMergePR(basePath, mapping.repo, sliceRecord.prNumber, "squash");
307
+ }
308
+ sliceRecord.state = "closed";
309
+ sliceRecord.lastSyncedAt = new Date().toISOString();
310
+ setSliceRecord(mapping, mid, sid, sliceRecord);
311
+ debugLog("github-sync", { phase: "slice-completed", mid, sid, pr: sliceRecord.prNumber });
312
+ }
313
+ async function syncMilestoneComplete(basePath, mapping, config, mid) {
314
+ const record = getMilestoneRecord(mapping, mid);
315
+ if (!record || record.state === "closed")
316
+ return;
317
+ // Close tracking issue
318
+ ghCloseIssue(basePath, mapping.repo, record.issueNumber, `Milestone ${mid} completed.`);
319
+ // Close GitHub milestone
320
+ ghCloseMilestone(basePath, mapping.repo, record.ghMilestoneNumber);
321
+ record.state = "closed";
322
+ record.lastSyncedAt = new Date().toISOString();
323
+ setMilestoneRecord(mapping, mid, record);
324
+ debugLog("github-sync", { phase: "milestone-completed", mid });
325
+ }
326
+ // ─── Bootstrap ──────────────────────────────────────────────────────────────
327
+ /**
328
+ * Walk the `.gsd/milestones/` tree and create GitHub entities for any
329
+ * that are missing from the sync mapping. Safe to run multiple times.
330
+ */
331
+ export async function bootstrapSync(basePath) {
332
+ const config = loadGitHubSyncConfig(basePath);
333
+ if (!config?.enabled)
334
+ return { milestones: 0, slices: 0, tasks: 0 };
335
+ if (!ghIsAvailable())
336
+ return { milestones: 0, slices: 0, tasks: 0 };
337
+ const repo = config.repo ?? resolveRepo(basePath);
338
+ if (!repo)
339
+ return { milestones: 0, slices: 0, tasks: 0 };
340
+ let mapping = loadSyncMapping(basePath) ?? createEmptyMapping(repo);
341
+ mapping.repo = repo;
342
+ const taskCountBefore = Object.keys(mapping.tasks).length;
343
+ const counts = { milestones: 0, slices: 0, tasks: 0 };
344
+ const milestonesDir = join(basePath, ".gsd", "milestones");
345
+ if (!existsSync(milestonesDir))
346
+ return counts;
347
+ const milestoneIds = readdirSync(milestonesDir, { withFileTypes: true })
348
+ .filter(d => d.isDirectory())
349
+ .map(d => d.name)
350
+ .sort();
351
+ for (const mid of milestoneIds) {
352
+ if (!getMilestoneRecord(mapping, mid)) {
353
+ await syncMilestonePlan(basePath, mapping, config, mid);
354
+ counts.milestones++;
355
+ }
356
+ // Find slices
357
+ const slicesDir = join(milestonesDir, mid, "slices");
358
+ if (!existsSync(slicesDir))
359
+ continue;
360
+ const sliceIds = readdirSync(slicesDir, { withFileTypes: true })
361
+ .filter(d => d.isDirectory())
362
+ .map(d => d.name)
363
+ .sort();
364
+ for (const sid of sliceIds) {
365
+ if (!getSliceRecord(mapping, mid, sid)) {
366
+ await syncSlicePlan(basePath, mapping, config, mid, sid);
367
+ counts.slices++;
368
+ }
369
+ }
370
+ }
371
+ counts.tasks = Object.keys(mapping.tasks).length - taskCountBefore;
372
+ saveSyncMapping(basePath, mapping);
373
+ return counts;
374
+ }
375
+ // ─── Config Loading ─────────────────────────────────────────────────────────
376
+ let _cachedConfig;
377
+ function loadGitHubSyncConfig(_basePath) {
378
+ if (_cachedConfig !== undefined)
379
+ return _cachedConfig;
380
+ try {
381
+ const prefs = loadEffectiveGSDPreferences();
382
+ const github = prefs?.preferences?.github;
383
+ if (!github || typeof github !== "object") {
384
+ _cachedConfig = null;
385
+ return null;
386
+ }
387
+ _cachedConfig = github;
388
+ return _cachedConfig;
389
+ }
390
+ catch {
391
+ _cachedConfig = null;
392
+ return null;
393
+ }
394
+ }
395
+ /** Reset config cache (for testing). */
396
+ export function _resetConfigCache() {
397
+ _cachedConfig = undefined;
398
+ }
399
+ function resolveRepo(basePath) {
400
+ const result = ghDetectRepo(basePath);
401
+ return result.ok ? result.data : null;
402
+ }
403
+ // ─── Commit Linking ─────────────────────────────────────────────────────────
404
+ /**
405
+ * Look up the GitHub issue number for a task so the commit message
406
+ * can include `Resolves #N`. Called from git-service commit building.
407
+ */
408
+ export function getTaskIssueNumberForCommit(basePath, mid, sid, tid) {
409
+ try {
410
+ const config = loadGitHubSyncConfig(basePath);
411
+ if (!config?.enabled)
412
+ return null;
413
+ if (config.auto_link_commits === false)
414
+ return null;
415
+ const mapping = loadSyncMapping(basePath);
416
+ if (!mapping)
417
+ return null;
418
+ const record = getTaskRecord(mapping, mid, sid, tid);
419
+ return record?.issueNumber ?? null;
420
+ }
421
+ catch {
422
+ return null;
423
+ }
424
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Markdown formatters for GitHub issue bodies, PR descriptions,
3
+ * and summary comments.
4
+ *
5
+ * All functions produce GitHub-flavored markdown strings ready
6
+ * for the `gh` CLI body parameters.
7
+ */
8
+ export function formatMilestoneIssueBody(data) {
9
+ const lines = [];
10
+ lines.push(`# ${data.id}: ${data.title}`);
11
+ lines.push("");
12
+ if (data.vision) {
13
+ lines.push("## Vision");
14
+ lines.push(data.vision);
15
+ lines.push("");
16
+ }
17
+ if (data.successCriteria?.length) {
18
+ lines.push("## Success Criteria");
19
+ for (const criterion of data.successCriteria) {
20
+ lines.push(`- [ ] ${criterion}`);
21
+ }
22
+ lines.push("");
23
+ }
24
+ if (data.slices?.length) {
25
+ lines.push("## Slices");
26
+ lines.push("");
27
+ lines.push("| Slice | Title | Tasks |");
28
+ lines.push("|-------|-------|-------|");
29
+ for (const slice of data.slices) {
30
+ lines.push(`| ${slice.id} | ${slice.title} | ${slice.taskCount ?? "—"} |`);
31
+ }
32
+ lines.push("");
33
+ }
34
+ lines.push("---");
35
+ lines.push("*Auto-generated by GSD GitHub Sync*");
36
+ return lines.join("\n");
37
+ }
38
+ export function formatSlicePRBody(data) {
39
+ const lines = [];
40
+ lines.push(`## ${data.id}: ${data.title}`);
41
+ lines.push("");
42
+ if (data.goal) {
43
+ lines.push("### Goal");
44
+ lines.push(data.goal);
45
+ lines.push("");
46
+ }
47
+ if (data.mustHaves?.length) {
48
+ lines.push("### Must-Haves");
49
+ for (const item of data.mustHaves) {
50
+ lines.push(`- ${item}`);
51
+ }
52
+ lines.push("");
53
+ }
54
+ if (data.demoCriterion) {
55
+ lines.push("### Demo Criterion");
56
+ lines.push(data.demoCriterion);
57
+ lines.push("");
58
+ }
59
+ if (data.tasks?.length) {
60
+ lines.push("### Tasks");
61
+ for (const task of data.tasks) {
62
+ const ref = task.issueNumber ? ` (#${task.issueNumber})` : "";
63
+ lines.push(`- [ ] ${task.id}: ${task.title}${ref}`);
64
+ }
65
+ lines.push("");
66
+ }
67
+ lines.push("---");
68
+ lines.push("*Auto-generated by GSD GitHub Sync*");
69
+ return lines.join("\n");
70
+ }
71
+ export function formatTaskIssueBody(data) {
72
+ const lines = [];
73
+ lines.push(`## ${data.id}: ${data.title}`);
74
+ lines.push("");
75
+ if (data.description) {
76
+ lines.push(data.description);
77
+ lines.push("");
78
+ }
79
+ if (data.files?.length) {
80
+ lines.push("### Files");
81
+ for (const file of data.files) {
82
+ lines.push(`- \`${file}\``);
83
+ }
84
+ lines.push("");
85
+ }
86
+ if (data.verifyCriteria?.length) {
87
+ lines.push("### Verification");
88
+ for (const criterion of data.verifyCriteria) {
89
+ lines.push(`- [ ] ${criterion}`);
90
+ }
91
+ lines.push("");
92
+ }
93
+ return lines.join("\n");
94
+ }
95
+ export function formatSummaryComment(data) {
96
+ const lines = [];
97
+ if (data.oneLiner) {
98
+ lines.push(`**Summary:** ${data.oneLiner}`);
99
+ lines.push("");
100
+ }
101
+ if (data.body) {
102
+ lines.push(data.body);
103
+ lines.push("");
104
+ }
105
+ if (data.frontmatter && Object.keys(data.frontmatter).length > 0) {
106
+ lines.push("<details>");
107
+ lines.push("<summary>Metadata</summary>");
108
+ lines.push("");
109
+ lines.push("```yaml");
110
+ for (const [key, value] of Object.entries(data.frontmatter)) {
111
+ lines.push(`${key}: ${JSON.stringify(value)}`);
112
+ }
113
+ lines.push("```");
114
+ lines.push("");
115
+ lines.push("</details>");
116
+ }
117
+ return lines.join("\n");
118
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Type definitions for the GitHub Sync extension.
3
+ *
4
+ * Config shape (stored in GSD preferences under `github` key) and
5
+ * sync mapping records (stored in `.gsd/github-sync.json`).
6
+ */
7
+ export {};
@@ -4,6 +4,8 @@
4
4
  "version": "1.0.0",
5
5
  "type": "module",
6
6
  "pi": {
7
- "extensions": ["./index.ts"]
7
+ "extensions": [
8
+ "./index.js"
9
+ ]
8
10
  }
9
11
  }
@@ -60,6 +60,8 @@ export class AutoSession {
60
60
  lastStateRebuildAt = 0;
61
61
  // ── Sidecar queue ─────────────────────────────────────────────────────
62
62
  sidecarQueue = [];
63
+ // ── Dispatch circuit breakers ──────────────────────────────────────
64
+ rewriteAttemptCount = 0;
63
65
  // ── Metrics ──────────────────────────────────────────────────────────────
64
66
  autoStartTime = 0;
65
67
  lastPromptCharCount;
@@ -68,25 +70,8 @@ export class AutoSession {
68
70
  // ── Signal handler ───────────────────────────────────────────────────────
69
71
  sigtermHandler = null;
70
72
  // ── Loop promise state ──────────────────────────────────────────────────
71
- /**
72
- * True only while runUnit is rotating into a fresh session. agent_end events
73
- * emitted from the previous session's abort during this window must be
74
- * ignored; they do not belong to the new unit.
75
- */
76
- sessionSwitchInFlight = false;
77
- /**
78
- * One-shot resolver for the current unit's agent_end promise.
79
- * Non-null only while a unit is in-flight (between sendMessage and agent_end).
80
- * Scoped to the session to prevent concurrent session corruption.
81
- */
82
- pendingResolve = null;
83
- /**
84
- * Queue for agent_end events that arrive when no pendingResolve exists.
85
- * This happens when error-recovery sendMessage retries produce agent_end
86
- * events between loop iterations. The next runUnit drains this queue
87
- * instead of waiting for a new event.
88
- */
89
- pendingAgentEndQueue = [];
73
+ // Per-unit resolve function and session-switch guard live at module level
74
+ // in auto-loop.ts (_currentResolve, _sessionSwitchInFlight).
90
75
  // ── Methods ──────────────────────────────────────────────────────────────
91
76
  clearTimers() {
92
77
  if (this.unitTimeoutHandle) {
@@ -160,12 +145,10 @@ export class AutoSession {
160
145
  this.lastBaselineCharCount = undefined;
161
146
  this.pendingQuickTasks = [];
162
147
  this.sidecarQueue = [];
148
+ this.rewriteAttemptCount = 0;
163
149
  // Signal handler
164
150
  this.sigtermHandler = null;
165
- // Loop promise state
166
- this.sessionSwitchInFlight = false;
167
- this.pendingResolve = null;
168
- this.pendingAgentEndQueue = [];
151
+ // Loop promise state lives in auto-loop.ts module scope
169
152
  }
170
153
  toJSON() {
171
154
  return {
@@ -22,25 +22,24 @@ function missingSliceStop(mid, phase) {
22
22
  }
23
23
  // ─── Rewrite Circuit Breaker ──────────────────────────────────────────────
24
24
  const MAX_REWRITE_ATTEMPTS = 3;
25
- let rewriteAttemptCount = 0;
26
- export function resetRewriteCircuitBreaker() {
27
- rewriteAttemptCount = 0;
28
- }
29
25
  // ─── Rules ────────────────────────────────────────────────────────────────
30
26
  const DISPATCH_RULES = [
31
27
  {
32
28
  name: "rewrite-docs (override gate)",
33
- match: async ({ mid, midTitle, state, basePath }) => {
29
+ match: async ({ mid, midTitle, state, basePath, session }) => {
34
30
  const pendingOverrides = await loadActiveOverrides(basePath);
35
31
  if (pendingOverrides.length === 0)
36
32
  return null;
37
- if (rewriteAttemptCount >= MAX_REWRITE_ATTEMPTS) {
33
+ const count = session?.rewriteAttemptCount ?? 0;
34
+ if (count >= MAX_REWRITE_ATTEMPTS) {
38
35
  const { resolveAllOverrides } = await import("./files.js");
39
36
  await resolveAllOverrides(basePath);
40
- rewriteAttemptCount = 0;
37
+ if (session)
38
+ session.rewriteAttemptCount = 0;
41
39
  return null;
42
40
  }
43
- rewriteAttemptCount++;
41
+ if (session)
42
+ session.rewriteAttemptCount++;
44
43
  const unitId = state.activeSlice ? `${mid}/${state.activeSlice.id}` : mid;
45
44
  return {
46
45
  action: "dispatch",
@@ -81,7 +80,7 @@ const DISPATCH_RULES = [
81
80
  unitType: "run-uat",
82
81
  unitId: `${mid}/${sliceId}`,
83
82
  prompt: await buildRunUatPrompt(mid, sliceId, relSliceFile(basePath, mid, sliceId, "UAT"), uatContent ?? "", basePath),
84
- pauseAfterDispatch: uatType !== "artifact-driven",
83
+ pauseAfterDispatch: uatType !== "artifact-driven" && uatType !== "browser-executable" && uatType !== "runtime-executable",
85
84
  };
86
85
  },
87
86
  },