specweave 1.0.350 → 1.0.352

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 (282) hide show
  1. package/bin/specweave.js +9 -0
  2. package/dist/plugins/specweave-ado/lib/ado-client-v2.d.ts +5 -0
  3. package/dist/plugins/specweave-ado/lib/ado-client-v2.d.ts.map +1 -1
  4. package/dist/plugins/specweave-ado/lib/ado-client-v2.js +61 -23
  5. package/dist/plugins/specweave-ado/lib/ado-client-v2.js.map +1 -1
  6. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.d.ts.map +1 -1
  7. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js +3 -2
  8. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js.map +1 -1
  9. package/dist/plugins/specweave-ado/lib/ado-profile-resolver.d.ts.map +1 -1
  10. package/dist/plugins/specweave-ado/lib/ado-profile-resolver.js +2 -1
  11. package/dist/plugins/specweave-ado/lib/ado-profile-resolver.js.map +1 -1
  12. package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts +1 -1
  13. package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts.map +1 -1
  14. package/dist/plugins/specweave-ado/lib/ado-spec-sync.js +25 -9
  15. package/dist/plugins/specweave-ado/lib/ado-spec-sync.js.map +1 -1
  16. package/dist/plugins/specweave-ado/lib/conflict-resolver.d.ts.map +1 -1
  17. package/dist/plugins/specweave-ado/lib/conflict-resolver.js +17 -1
  18. package/dist/plugins/specweave-ado/lib/conflict-resolver.js.map +1 -1
  19. package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts +3 -0
  20. package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts.map +1 -1
  21. package/dist/plugins/specweave-ado/lib/per-us-sync.js +14 -1
  22. package/dist/plugins/specweave-ado/lib/per-us-sync.js.map +1 -1
  23. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
  24. package/dist/plugins/specweave-github/lib/github-client-v2.js +10 -7
  25. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  26. package/dist/plugins/specweave-github/lib/github-client.d.ts +1 -1
  27. package/dist/plugins/specweave-github/lib/github-client.d.ts.map +1 -1
  28. package/dist/plugins/specweave-github/lib/github-client.js +7 -5
  29. package/dist/plugins/specweave-github/lib/github-client.js.map +1 -1
  30. package/dist/plugins/specweave-github/lib/github-cross-repo-sync.js +13 -3
  31. package/dist/plugins/specweave-github/lib/github-cross-repo-sync.js.map +1 -1
  32. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.d.ts +24 -1
  33. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.d.ts.map +1 -1
  34. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js +36 -20
  35. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js.map +1 -1
  36. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +4 -2
  37. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
  38. package/dist/plugins/specweave-github/lib/github-feature-sync.js +38 -9
  39. package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
  40. package/dist/plugins/specweave-github/lib/github-graphql-client.d.ts +1 -0
  41. package/dist/plugins/specweave-github/lib/github-graphql-client.d.ts.map +1 -1
  42. package/dist/plugins/specweave-github/lib/github-graphql-client.js +32 -22
  43. package/dist/plugins/specweave-github/lib/github-graphql-client.js.map +1 -1
  44. package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.js +144 -8
  45. package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.js.map +1 -1
  46. package/dist/plugins/specweave-github/lib/github-spec-sync.d.ts +8 -1
  47. package/dist/plugins/specweave-github/lib/github-spec-sync.d.ts.map +1 -1
  48. package/dist/plugins/specweave-github/lib/github-spec-sync.js +94 -24
  49. package/dist/plugins/specweave-github/lib/github-spec-sync.js.map +1 -1
  50. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.d.ts +1 -0
  51. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.d.ts.map +1 -1
  52. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.js +2 -1
  53. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.js.map +1 -1
  54. package/dist/plugins/specweave-github/lib/github-us-auto-closer.d.ts.map +1 -1
  55. package/dist/plugins/specweave-github/lib/github-us-auto-closer.js +25 -0
  56. package/dist/plugins/specweave-github/lib/github-us-auto-closer.js.map +1 -1
  57. package/dist/plugins/specweave-github/lib/per-us-sync.d.ts +3 -0
  58. package/dist/plugins/specweave-github/lib/per-us-sync.d.ts.map +1 -1
  59. package/dist/plugins/specweave-github/lib/per-us-sync.js +29 -9
  60. package/dist/plugins/specweave-github/lib/per-us-sync.js.map +1 -1
  61. package/dist/plugins/specweave-jira/lib/content-format-adapter.d.ts +59 -0
  62. package/dist/plugins/specweave-jira/lib/content-format-adapter.d.ts.map +1 -0
  63. package/dist/plugins/specweave-jira/lib/content-format-adapter.js +159 -0
  64. package/dist/plugins/specweave-jira/lib/content-format-adapter.js.map +1 -0
  65. package/dist/plugins/specweave-jira/lib/jira-deployment-detector.d.ts +45 -0
  66. package/dist/plugins/specweave-jira/lib/jira-deployment-detector.d.ts.map +1 -0
  67. package/dist/plugins/specweave-jira/lib/jira-deployment-detector.js +92 -0
  68. package/dist/plugins/specweave-jira/lib/jira-deployment-detector.js.map +1 -0
  69. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.d.ts.map +1 -1
  70. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js +13 -28
  71. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js.map +1 -1
  72. package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts +2 -1
  73. package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts.map +1 -1
  74. package/dist/plugins/specweave-jira/lib/jira-epic-sync.js +19 -7
  75. package/dist/plugins/specweave-jira/lib/jira-epic-sync.js.map +1 -1
  76. package/dist/plugins/specweave-jira/lib/jira-field-discovery.d.ts +47 -0
  77. package/dist/plugins/specweave-jira/lib/jira-field-discovery.d.ts.map +1 -0
  78. package/dist/plugins/specweave-jira/lib/jira-field-discovery.js +110 -0
  79. package/dist/plugins/specweave-jira/lib/jira-field-discovery.js.map +1 -0
  80. package/dist/plugins/specweave-jira/lib/jira-paginated-search.d.ts +26 -0
  81. package/dist/plugins/specweave-jira/lib/jira-paginated-search.d.ts.map +1 -0
  82. package/dist/plugins/specweave-jira/lib/jira-paginated-search.js +77 -0
  83. package/dist/plugins/specweave-jira/lib/jira-paginated-search.js.map +1 -0
  84. package/dist/plugins/specweave-jira/lib/jira-spec-commit-sync.d.ts.map +1 -1
  85. package/dist/plugins/specweave-jira/lib/jira-spec-commit-sync.js +5 -3
  86. package/dist/plugins/specweave-jira/lib/jira-spec-commit-sync.js.map +1 -1
  87. package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts +17 -2
  88. package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts.map +1 -1
  89. package/dist/plugins/specweave-jira/lib/jira-spec-sync.js +103 -33
  90. package/dist/plugins/specweave-jira/lib/jira-spec-sync.js.map +1 -1
  91. package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +4 -0
  92. package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -1
  93. package/dist/plugins/specweave-jira/lib/jira-status-sync.js +19 -6
  94. package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -1
  95. package/dist/plugins/specweave-jira/lib/metadata-paths.d.ts +29 -0
  96. package/dist/plugins/specweave-jira/lib/metadata-paths.d.ts.map +1 -0
  97. package/dist/plugins/specweave-jira/lib/metadata-paths.js +73 -0
  98. package/dist/plugins/specweave-jira/lib/metadata-paths.js.map +1 -0
  99. package/dist/plugins/specweave-jira/lib/reorganization-detector.d.ts +15 -2
  100. package/dist/plugins/specweave-jira/lib/reorganization-detector.d.ts.map +1 -1
  101. package/dist/plugins/specweave-jira/lib/reorganization-detector.js +121 -33
  102. package/dist/plugins/specweave-jira/lib/reorganization-detector.js.map +1 -1
  103. package/dist/src/cli/commands/init.d.ts.map +1 -1
  104. package/dist/src/cli/commands/init.js +23 -18
  105. package/dist/src/cli/commands/init.js.map +1 -1
  106. package/dist/src/cli/commands/sync-progress.d.ts +6 -0
  107. package/dist/src/cli/commands/sync-progress.d.ts.map +1 -1
  108. package/dist/src/cli/commands/sync-progress.js +37 -0
  109. package/dist/src/cli/commands/sync-progress.js.map +1 -1
  110. package/dist/src/cli/commands/sync-task.d.ts +16 -0
  111. package/dist/src/cli/commands/sync-task.d.ts.map +1 -0
  112. package/dist/src/cli/commands/sync-task.js +42 -0
  113. package/dist/src/cli/commands/sync-task.js.map +1 -0
  114. package/dist/src/cli/helpers/init/instruction-file-merger.js +3 -3
  115. package/dist/src/cli/helpers/init/instruction-file-merger.js.map +1 -1
  116. package/dist/src/core/hooks/LifecycleHookDispatcher.d.ts +9 -1
  117. package/dist/src/core/hooks/LifecycleHookDispatcher.d.ts.map +1 -1
  118. package/dist/src/core/hooks/LifecycleHookDispatcher.js +26 -8
  119. package/dist/src/core/hooks/LifecycleHookDispatcher.js.map +1 -1
  120. package/dist/src/core/increment/metadata-manager.d.ts +13 -0
  121. package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
  122. package/dist/src/core/increment/metadata-manager.js +144 -17
  123. package/dist/src/core/increment/metadata-manager.js.map +1 -1
  124. package/dist/src/core/increment/status-change-sync-trigger.d.ts +1 -1
  125. package/dist/src/core/increment/status-change-sync-trigger.d.ts.map +1 -1
  126. package/dist/src/core/increment/status-change-sync-trigger.js +2 -1
  127. package/dist/src/core/increment/status-change-sync-trigger.js.map +1 -1
  128. package/dist/src/core/increment/status-commands.d.ts.map +1 -1
  129. package/dist/src/core/increment/status-commands.js +33 -11
  130. package/dist/src/core/increment/status-commands.js.map +1 -1
  131. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  132. package/dist/src/core/repo-structure/repo-structure-manager.js +2 -1
  133. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  134. package/dist/src/locales/de/cli.json +252 -77
  135. package/dist/src/locales/en/cli.json +7 -0
  136. package/dist/src/locales/es/cli.json +245 -3
  137. package/dist/src/locales/fr/cli.json +259 -84
  138. package/dist/src/locales/ja/cli.json +253 -78
  139. package/dist/src/locales/ko/cli.json +253 -78
  140. package/dist/src/locales/pt/cli.json +252 -77
  141. package/dist/src/locales/ru/cli.json +17 -3
  142. package/dist/src/locales/zh/cli.json +258 -83
  143. package/dist/src/sync/ado-reconciler.d.ts.map +1 -1
  144. package/dist/src/sync/ado-reconciler.js +5 -1
  145. package/dist/src/sync/ado-reconciler.js.map +1 -1
  146. package/dist/src/sync/base-reconciler.d.ts.map +1 -1
  147. package/dist/src/sync/base-reconciler.js +6 -1
  148. package/dist/src/sync/base-reconciler.js.map +1 -1
  149. package/dist/src/sync/config.d.ts +4 -0
  150. package/dist/src/sync/config.d.ts.map +1 -1
  151. package/dist/src/sync/config.js +6 -4
  152. package/dist/src/sync/config.js.map +1 -1
  153. package/dist/src/sync/external-issue-auto-creator.d.ts +3 -0
  154. package/dist/src/sync/external-issue-auto-creator.d.ts.map +1 -1
  155. package/dist/src/sync/external-issue-auto-creator.js +53 -17
  156. package/dist/src/sync/external-issue-auto-creator.js.map +1 -1
  157. package/dist/src/sync/external-item-sync-service.d.ts +9 -0
  158. package/dist/src/sync/external-item-sync-service.d.ts.map +1 -1
  159. package/dist/src/sync/external-item-sync-service.js +210 -9
  160. package/dist/src/sync/external-item-sync-service.js.map +1 -1
  161. package/dist/src/sync/github-reconciler.d.ts +30 -0
  162. package/dist/src/sync/github-reconciler.d.ts.map +1 -1
  163. package/dist/src/sync/github-reconciler.js +242 -3
  164. package/dist/src/sync/github-reconciler.js.map +1 -1
  165. package/dist/src/sync/jira-reconciler.d.ts.map +1 -1
  166. package/dist/src/sync/jira-reconciler.js +5 -1
  167. package/dist/src/sync/jira-reconciler.js.map +1 -1
  168. package/dist/src/sync/provider-router.d.ts.map +1 -1
  169. package/dist/src/sync/provider-router.js +2 -1
  170. package/dist/src/sync/provider-router.js.map +1 -1
  171. package/dist/src/sync/providers/ado.d.ts +4 -0
  172. package/dist/src/sync/providers/ado.d.ts.map +1 -1
  173. package/dist/src/sync/providers/ado.js +36 -11
  174. package/dist/src/sync/providers/ado.js.map +1 -1
  175. package/dist/src/sync/providers/github.d.ts.map +1 -1
  176. package/dist/src/sync/providers/github.js +48 -35
  177. package/dist/src/sync/providers/github.js.map +1 -1
  178. package/dist/src/sync/providers/jira.d.ts.map +1 -1
  179. package/dist/src/sync/providers/jira.js +42 -26
  180. package/dist/src/sync/providers/jira.js.map +1 -1
  181. package/dist/src/sync/status-mapper.d.ts +3 -1
  182. package/dist/src/sync/status-mapper.d.ts.map +1 -1
  183. package/dist/src/sync/status-mapper.js +10 -2
  184. package/dist/src/sync/status-mapper.js.map +1 -1
  185. package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
  186. package/dist/src/sync/sync-coordinator.js +29 -19
  187. package/dist/src/sync/sync-coordinator.js.map +1 -1
  188. package/package.json +1 -1
  189. package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +31 -0
  190. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +13 -0
  191. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +144 -17
  192. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
  193. package/plugins/specweave/lib/vendor/sync/github-reconciler.d.ts +30 -0
  194. package/plugins/specweave/lib/vendor/sync/github-reconciler.js +242 -3
  195. package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -1
  196. package/plugins/specweave/skills/architect/SKILL.md +2 -0
  197. package/plugins/specweave/skills/grill/SKILL.md +2 -0
  198. package/plugins/specweave/skills/team-lead/SKILL.md +43 -320
  199. package/plugins/specweave/skills/team-lead/agents/backend.md +60 -0
  200. package/plugins/specweave/skills/team-lead/agents/database.md +51 -0
  201. package/plugins/specweave/skills/team-lead/agents/frontend.md +61 -0
  202. package/plugins/specweave/skills/team-lead/agents/security.md +52 -0
  203. package/plugins/specweave/skills/team-lead/agents/testing.md +57 -0
  204. package/plugins/specweave/skills/test-aware-planner/SKILL.md +2 -0
  205. package/plugins/specweave-ado/hooks/post-task-completion.sh +2 -2
  206. package/plugins/specweave-ado/lib/ado-client-v2.js +51 -21
  207. package/plugins/specweave-ado/lib/ado-client-v2.ts +62 -23
  208. package/plugins/specweave-ado/lib/ado-duplicate-detector.js +4 -4
  209. package/plugins/specweave-ado/lib/ado-duplicate-detector.ts +4 -3
  210. package/plugins/specweave-ado/lib/ado-hierarchical-sync.js +54 -12
  211. package/plugins/specweave-ado/lib/ado-hierarchical-sync.ts +88 -18
  212. package/plugins/specweave-ado/lib/ado-profile-resolver.js +1 -1
  213. package/plugins/specweave-ado/lib/ado-profile-resolver.ts +3 -1
  214. package/plugins/specweave-ado/lib/ado-spec-sync.js +22 -9
  215. package/plugins/specweave-ado/lib/ado-spec-sync.ts +27 -9
  216. package/plugins/specweave-ado/lib/conflict-resolver.js +17 -1
  217. package/plugins/specweave-ado/lib/conflict-resolver.ts +17 -1
  218. package/plugins/specweave-ado/lib/enhanced-ado-sync.js +11 -1
  219. package/plugins/specweave-ado/lib/per-us-sync.js +8 -1
  220. package/plugins/specweave-ado/lib/per-us-sync.ts +17 -2
  221. package/plugins/specweave-github/hooks/github-auto-create-handler.sh +28 -2
  222. package/plugins/specweave-github/hooks/post-task-completion.sh +6 -3
  223. package/plugins/specweave-github/lib/enhanced-github-sync.js +35 -6
  224. package/plugins/specweave-github/lib/github-board-resolver.js +4 -4
  225. package/plugins/specweave-github/lib/github-board-resolver.ts +4 -4
  226. package/plugins/specweave-github/lib/github-client-v2.js +6 -6
  227. package/plugins/specweave-github/lib/github-client-v2.ts +11 -7
  228. package/plugins/specweave-github/lib/github-client.js +5 -4
  229. package/plugins/specweave-github/lib/github-client.ts +7 -5
  230. package/plugins/specweave-github/lib/github-cross-repo-sync.js +17 -3
  231. package/plugins/specweave-github/lib/github-cross-repo-sync.ts +16 -3
  232. package/plugins/specweave-github/lib/github-feature-sync-cli.js +20 -11
  233. package/plugins/specweave-github/lib/github-feature-sync-cli.ts +42 -20
  234. package/plugins/specweave-github/lib/github-feature-sync.js +32 -8
  235. package/plugins/specweave-github/lib/github-feature-sync.ts +41 -9
  236. package/plugins/specweave-github/lib/github-graphql-client.js +29 -20
  237. package/plugins/specweave-github/lib/github-graphql-client.ts +34 -22
  238. package/plugins/specweave-github/lib/github-hierarchical-sync.js +2 -2
  239. package/plugins/specweave-github/lib/github-hierarchical-sync.ts +2 -2
  240. package/plugins/specweave-github/lib/github-multi-project-sync.js +23 -7
  241. package/plugins/specweave-github/lib/github-multi-project-sync.ts +26 -8
  242. package/plugins/specweave-github/lib/github-spec-frontmatter-updater.js +110 -5
  243. package/plugins/specweave-github/lib/github-spec-frontmatter-updater.ts +135 -9
  244. package/plugins/specweave-github/lib/github-spec-sync.js +85 -24
  245. package/plugins/specweave-github/lib/github-spec-sync.ts +100 -26
  246. package/plugins/specweave-github/lib/github-sync-orchestrator.js +2 -1
  247. package/plugins/specweave-github/lib/github-sync-orchestrator.ts +3 -1
  248. package/plugins/specweave-github/lib/github-us-auto-closer.js +25 -0
  249. package/plugins/specweave-github/lib/github-us-auto-closer.ts +43 -0
  250. package/plugins/specweave-github/lib/per-us-sync.js +26 -11
  251. package/plugins/specweave-github/lib/per-us-sync.ts +29 -11
  252. package/plugins/specweave-jira/hooks/post-task-completion.sh +2 -1
  253. package/plugins/specweave-jira/lib/content-format-adapter.js +116 -0
  254. package/plugins/specweave-jira/lib/content-format-adapter.ts +189 -0
  255. package/plugins/specweave-jira/lib/enhanced-jira-sync.js +21 -5
  256. package/plugins/specweave-jira/lib/jira-deployment-detector.js +63 -0
  257. package/plugins/specweave-jira/lib/jira-deployment-detector.ts +113 -0
  258. package/plugins/specweave-jira/lib/jira-duplicate-detector.js +12 -29
  259. package/plugins/specweave-jira/lib/jira-duplicate-detector.ts +13 -27
  260. package/plugins/specweave-jira/lib/jira-epic-sync.js +15 -5
  261. package/plugins/specweave-jira/lib/jira-epic-sync.ts +22 -7
  262. package/plugins/specweave-jira/lib/jira-field-discovery.js +76 -0
  263. package/plugins/specweave-jira/lib/jira-field-discovery.ts +139 -0
  264. package/plugins/specweave-jira/lib/jira-hierarchical-sync.js +10 -0
  265. package/plugins/specweave-jira/lib/jira-hierarchical-sync.ts +11 -0
  266. package/plugins/specweave-jira/lib/jira-multi-project-sync.js +19 -9
  267. package/plugins/specweave-jira/lib/jira-multi-project-sync.ts +25 -14
  268. package/plugins/specweave-jira/lib/jira-paginated-search.js +55 -0
  269. package/plugins/specweave-jira/lib/jira-paginated-search.ts +108 -0
  270. package/plugins/specweave-jira/lib/jira-spec-commit-sync.js +5 -3
  271. package/plugins/specweave-jira/lib/jira-spec-commit-sync.ts +5 -3
  272. package/plugins/specweave-jira/lib/jira-spec-sync.js +102 -31
  273. package/plugins/specweave-jira/lib/jira-spec-sync.ts +123 -45
  274. package/plugins/specweave-jira/lib/jira-status-sync.js +18 -5
  275. package/plugins/specweave-jira/lib/jira-status-sync.ts +21 -6
  276. package/plugins/specweave-jira/lib/metadata-paths.js +38 -0
  277. package/plugins/specweave-jira/lib/metadata-paths.ts +73 -0
  278. package/plugins/specweave-jira/lib/reorganization-detector.js +101 -23
  279. package/plugins/specweave-jira/lib/reorganization-detector.ts +125 -35
  280. package/plugins/specweave-jira/scripts/refresh-cache.js +1 -1
  281. package/plugins/specweave-jira/scripts/refresh-cache.ts +2 -2
  282. package/plugins/specweave-jira/skills/jira-resource-validator/SKILL.md +3 -5
@@ -163,7 +163,8 @@ class AdoSpecSync {
163
163
  value: this.mapPriorityToAdo(spec.metadata.priority)
164
164
  }
165
165
  ];
166
- const response = await this.client.post("/wit/workitems/$Feature?api-version=7.0", payload);
166
+ const encodedType = encodeURIComponent(workItemType);
167
+ const response = await this.client.post(`/wit/workitems/$${encodedType}?api-version=7.0`, payload);
167
168
  const featureData = response.data;
168
169
  console.log(` \u2705 Created ADO Feature #${featureData.id}: ${featureData._links.html.href}`);
169
170
  return {
@@ -173,29 +174,41 @@ class AdoSpecSync {
173
174
  };
174
175
  }
175
176
  /**
176
- * Update existing ADO Feature
177
+ * Update existing ADO Feature (conditional — only writes changed fields)
177
178
  */
178
179
  async updateAdoFeature(featureId, spec) {
179
180
  const featureTitle = `[${spec.metadata.id.toUpperCase()}] ${spec.metadata.title}`;
180
181
  const featureDescription = this.generateFeatureDescription(spec);
181
- const payload = [
182
- {
182
+ const current = await this.fetchAdoFeature(featureId);
183
+ const payload = [];
184
+ if (current.fields["System.Title"] !== featureTitle) {
185
+ payload.push({
183
186
  op: "replace",
184
187
  path: "/fields/System.Title",
185
188
  value: featureTitle
186
- },
187
- {
189
+ });
190
+ } else {
191
+ console.log(` \u2139\uFE0F Title unchanged, skipping`);
192
+ }
193
+ if (current.fields["System.Description"] !== featureDescription) {
194
+ payload.push({
188
195
  op: "replace",
189
196
  path: "/fields/System.Description",
190
197
  value: featureDescription
191
- }
192
- ];
198
+ });
199
+ } else {
200
+ console.log(` \u2139\uFE0F Description unchanged, skipping`);
201
+ }
202
+ if (payload.length === 0) {
203
+ console.log(` \u2139\uFE0F No changes detected for ADO Feature #${featureId}`);
204
+ return current;
205
+ }
193
206
  const response = await this.client.patch(
194
207
  `/wit/workitems/${featureId}?api-version=7.0`,
195
208
  payload
196
209
  );
197
210
  const featureData = response.data;
198
- console.log(` \u2705 Updated ADO Feature #${featureId}`);
211
+ console.log(` \u2705 Updated ADO Feature #${featureId} (${payload.length} field(s) changed)`);
199
212
  return {
200
213
  id: featureData.id,
201
214
  url: featureData._links.html.href,
@@ -255,7 +255,8 @@ export class AdoSpecSync {
255
255
  }
256
256
  ];
257
257
 
258
- const response = await this.client.post('/wit/workitems/$Feature?api-version=7.0', payload);
258
+ const encodedType = encodeURIComponent(workItemType);
259
+ const response = await this.client.post(`/wit/workitems/$${encodedType}?api-version=7.0`, payload);
259
260
  const featureData = response.data;
260
261
 
261
262
  console.log(` ✅ Created ADO Feature #${featureData.id}: ${featureData._links.html.href}`);
@@ -268,24 +269,41 @@ export class AdoSpecSync {
268
269
  }
269
270
 
270
271
  /**
271
- * Update existing ADO Feature
272
+ * Update existing ADO Feature (conditional — only writes changed fields)
272
273
  */
273
274
  private async updateAdoFeature(featureId: number, spec: SpecContent): Promise<AdoFeature> {
274
275
  const featureTitle = `[${spec.metadata.id.toUpperCase()}] ${spec.metadata.title}`;
275
276
  const featureDescription = this.generateFeatureDescription(spec);
276
277
 
277
- const payload = [
278
- {
278
+ // Fetch current values to avoid overwriting ADO-side edits
279
+ const current = await this.fetchAdoFeature(featureId);
280
+
281
+ const payload: any[] = [];
282
+
283
+ if (current.fields['System.Title'] !== featureTitle) {
284
+ payload.push({
279
285
  op: 'replace',
280
286
  path: '/fields/System.Title',
281
287
  value: featureTitle
282
- },
283
- {
288
+ });
289
+ } else {
290
+ console.log(` ℹ️ Title unchanged, skipping`);
291
+ }
292
+
293
+ if (current.fields['System.Description'] !== featureDescription) {
294
+ payload.push({
284
295
  op: 'replace',
285
296
  path: '/fields/System.Description',
286
297
  value: featureDescription
287
- }
288
- ];
298
+ });
299
+ } else {
300
+ console.log(` ℹ️ Description unchanged, skipping`);
301
+ }
302
+
303
+ if (payload.length === 0) {
304
+ console.log(` ℹ️ No changes detected for ADO Feature #${featureId}`);
305
+ return current;
306
+ }
289
307
 
290
308
  const response = await this.client.patch(
291
309
  `/wit/workitems/${featureId}?api-version=7.0`,
@@ -293,7 +311,7 @@ export class AdoSpecSync {
293
311
  );
294
312
  const featureData = response.data;
295
313
 
296
- console.log(` ✅ Updated ADO Feature #${featureId}`);
314
+ console.log(` ✅ Updated ADO Feature #${featureId} (${payload.length} field(s) changed)`);
297
315
 
298
316
  return {
299
317
  id: featureData.id,
@@ -3,6 +3,7 @@ import * as path from "path";
3
3
  import * as yaml from "yaml";
4
4
  const STATUS_MAPPING = {
5
5
  ado: {
6
+ // Agile process template
6
7
  "New": "draft",
7
8
  "Active": "in-progress",
8
9
  "Resolved": "implemented",
@@ -10,7 +11,20 @@ const STATUS_MAPPING = {
10
11
  "In Review": "in-qa",
11
12
  "In QA": "in-qa",
12
13
  "Blocked": "blocked",
13
- "Removed": "cancelled"
14
+ "Removed": "cancelled",
15
+ // Scrum process template
16
+ "Approved": "draft",
17
+ "Committed": "in-progress",
18
+ "Done": "complete",
19
+ // CMMI process template
20
+ "Proposed": "draft",
21
+ // 'Active' already mapped above (shared with CMMI)
22
+ // 'Resolved' already mapped above (shared with CMMI)
23
+ // 'Closed' already mapped above (shared with CMMI)
24
+ // Basic process template
25
+ "To Do": "draft",
26
+ "Doing": "in-progress"
27
+ // 'Done' already mapped above (shared with Basic)
14
28
  },
15
29
  jira: {
16
30
  "To Do": "draft",
@@ -30,6 +44,8 @@ const STATUS_MAPPING = {
30
44
  }
31
45
  };
32
46
  const REVERSE_STATUS_MAPPING = {
47
+ // Default reverse mapping uses Agile states (most common)
48
+ // Callers should use process-template-aware mapping when template is known
33
49
  ado: {
34
50
  "draft": "New",
35
51
  "in-progress": "Active",
@@ -87,6 +87,7 @@ export interface ExternalStatus {
87
87
 
88
88
  const STATUS_MAPPING = {
89
89
  ado: {
90
+ // Agile process template
90
91
  'New': 'draft' as SpecStatus,
91
92
  'Active': 'in-progress' as SpecStatus,
92
93
  'Resolved': 'implemented' as SpecStatus,
@@ -94,7 +95,20 @@ const STATUS_MAPPING = {
94
95
  'In Review': 'in-qa' as SpecStatus,
95
96
  'In QA': 'in-qa' as SpecStatus,
96
97
  'Blocked': 'blocked' as SpecStatus,
97
- 'Removed': 'cancelled' as SpecStatus
98
+ 'Removed': 'cancelled' as SpecStatus,
99
+ // Scrum process template
100
+ 'Approved': 'draft' as SpecStatus,
101
+ 'Committed': 'in-progress' as SpecStatus,
102
+ 'Done': 'complete' as SpecStatus,
103
+ // CMMI process template
104
+ 'Proposed': 'draft' as SpecStatus,
105
+ // 'Active' already mapped above (shared with CMMI)
106
+ // 'Resolved' already mapped above (shared with CMMI)
107
+ // 'Closed' already mapped above (shared with CMMI)
108
+ // Basic process template
109
+ 'To Do': 'draft' as SpecStatus,
110
+ 'Doing': 'in-progress' as SpecStatus,
111
+ // 'Done' already mapped above (shared with Basic)
98
112
  },
99
113
  jira: {
100
114
  'To Do': 'draft' as SpecStatus,
@@ -115,6 +129,8 @@ const STATUS_MAPPING = {
115
129
  };
116
130
 
117
131
  const REVERSE_STATUS_MAPPING = {
132
+ // Default reverse mapping uses Agile states (most common)
133
+ // Callers should use process-template-aware mapping when template is known
118
134
  ado: {
119
135
  'draft': 'New',
120
136
  'in-progress': 'Active',
@@ -130,10 +130,20 @@ function buildTaskMapping(increments, organization, project) {
130
130
  title: task.title,
131
131
  userStories: task.userStories
132
132
  }));
133
+ // Derive repository name from git remote or fall back to project name
134
+ let repoName = project;
135
+ try {
136
+ const { execSync } = require("child_process");
137
+ const remoteUrl = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
138
+ const match = remoteUrl.match(/\/([^/]+?)(?:\.git)?$/);
139
+ if (match) repoName = match[1];
140
+ } catch {
141
+ // Fallback to project name if git is unavailable
142
+ }
133
143
  return {
134
144
  incrementId: firstIncrement.id,
135
145
  tasks,
136
- tasksUrl: `https://dev.azure.com/${organization}/${project}/_git/repo?path=/.specweave/increments/${firstIncrement.id}/tasks.md`
146
+ tasksUrl: `https://dev.azure.com/${organization}/${project}/_git/${repoName}?path=/.specweave/increments/${firstIncrement.id}/tasks.md`
137
147
  };
138
148
  }
139
149
  async function findArchitectureDocs(rootDir, specId) {
@@ -1,9 +1,16 @@
1
1
  import { consoleLogger } from "../../../src/utils/logger.js";
2
+ const WORK_ITEM_TYPE_BY_TEMPLATE = {
3
+ agile: "User Story",
4
+ scrum: "Product Backlog Item",
5
+ cmmi: "Requirement",
6
+ basic: "Issue"
7
+ };
2
8
  class PerUSAdoSync {
3
9
  constructor(adoClient, projectMappings, options = {}) {
4
10
  this.adoClient = adoClient;
5
11
  this.projectMappings = projectMappings;
6
12
  this.logger = options.logger ?? consoleLogger;
13
+ this.workItemType = options.workItemType ?? WORK_ITEM_TYPE_BY_TEMPLATE[options.processTemplate?.toLowerCase() ?? ""] ?? "User Story";
7
14
  }
8
15
  /**
9
16
  * Sync all user stories to their respective ADO projects
@@ -133,7 +140,7 @@ class PerUSAdoSync {
133
140
  } else {
134
141
  const newItem = await this.adoClient.createWorkItem(
135
142
  mapping.project,
136
- "User Story",
143
+ this.workItemType,
137
144
  title,
138
145
  description,
139
146
  areaPath
@@ -81,6 +81,16 @@ export interface AdoClient {
81
81
  getWorkItemUrl(project: string, workItemId: number): string;
82
82
  }
83
83
 
84
+ /**
85
+ * Default work item type by process template
86
+ */
87
+ const WORK_ITEM_TYPE_BY_TEMPLATE: Record<string, string> = {
88
+ agile: 'User Story',
89
+ scrum: 'Product Backlog Item',
90
+ cmmi: 'Requirement',
91
+ basic: 'Issue',
92
+ };
93
+
84
94
  /**
85
95
  * Per-US ADO Sync
86
96
  *
@@ -91,15 +101,20 @@ export class PerUSAdoSync {
91
101
  private projectMappings: ProjectMappings;
92
102
  private adoClient: AdoClient;
93
103
  private logger: Logger;
104
+ private workItemType: string;
94
105
 
95
106
  constructor(
96
107
  adoClient: AdoClient,
97
108
  projectMappings: ProjectMappings,
98
- options: { logger?: Logger } = {}
109
+ options: { logger?: Logger; workItemType?: string; processTemplate?: string } = {}
99
110
  ) {
100
111
  this.adoClient = adoClient;
101
112
  this.projectMappings = projectMappings;
102
113
  this.logger = options.logger ?? consoleLogger;
114
+ // Explicit workItemType takes priority, then process template lookup, then default
115
+ this.workItemType = options.workItemType
116
+ ?? WORK_ITEM_TYPE_BY_TEMPLATE[options.processTemplate?.toLowerCase() ?? '']
117
+ ?? 'User Story';
103
118
  }
104
119
 
105
120
  /**
@@ -268,7 +283,7 @@ export class PerUSAdoSync {
268
283
  // Create new work item
269
284
  const newItem = await this.adoClient.createWorkItem(
270
285
  mapping.project,
271
- 'User Story',
286
+ this.workItemType,
272
287
  title,
273
288
  description,
274
289
  areaPath
@@ -74,14 +74,38 @@ else
74
74
  [[ "$GH_ENABLED" != "true" ]] && { log "GitHub sync not enabled"; exit 0; }
75
75
  fi
76
76
 
77
- # Check auto-create enabled (either autoSync OR auto_create_github_issue)
77
+ # Check auto-create enabled
78
+ # Two flags control auto-creation behavior:
79
+ # - sync.autoSync (boolean): Global auto-sync toggle. When true, enables ALL auto-sync
80
+ # operations including issue creation, progress sync, and status updates.
81
+ # - hooks.post_increment_planning.auto_create_github_issue (boolean): Fine-grained toggle
82
+ # for ONLY the auto-create-issue behavior in post-increment-planning hooks.
83
+ #
84
+ # Precedence: auto_create_github_issue (specific) > autoSync (global)
85
+ # If BOTH are set, auto_create_github_issue takes precedence for issue creation.
86
+ # If ONLY autoSync is true, issue creation is enabled (as part of the global auto-sync).
78
87
  AUTO_SYNC=$(jq -r '.sync.autoSync // false' "$CONFIG_PATH" 2>/dev/null)
79
88
  AUTO_CREATE=$(jq -r '.hooks.post_increment_planning.auto_create_github_issue // false' "$CONFIG_PATH" 2>/dev/null)
89
+
90
+ # Warn if only one flag is set (likely misconfiguration)
91
+ if [[ "$AUTO_SYNC" == "true" ]] && [[ "$AUTO_CREATE" == "false" ]]; then
92
+ log "Warning: sync.autoSync=true but auto_create_github_issue=false. Issue creation enabled via autoSync. Set auto_create_github_issue=true to be explicit."
93
+ elif [[ "$AUTO_SYNC" == "false" ]] && [[ "$AUTO_CREATE" == "true" ]]; then
94
+ log "Warning: auto_create_github_issue=true but sync.autoSync=false. Only issue creation is enabled; other sync operations are disabled."
95
+ fi
96
+
80
97
  if [[ "$AUTO_SYNC" != "true" ]] && [[ "$AUTO_CREATE" != "true" ]]; then
81
98
  log "Auto-sync disabled (autoSync=$AUTO_SYNC, auto_create=$AUTO_CREATE)"
82
99
  exit 0
83
100
  fi
84
101
 
102
+ # Log which flag triggered activation
103
+ if [[ "$AUTO_CREATE" == "true" ]]; then
104
+ log "Auto-create enabled via hooks.post_increment_planning.auto_create_github_issue"
105
+ else
106
+ log "Auto-create enabled via sync.autoSync (global toggle)"
107
+ fi
108
+
85
109
  # Get owner/repo
86
110
  OWNER=$(jq -r '.sync.github.owner // ""' "$CONFIG_PATH" 2>/dev/null)
87
111
  REPO=$(jq -r '.sync.github.repo // ""' "$CONFIG_PATH" 2>/dev/null)
@@ -115,7 +139,9 @@ fi
115
139
  if [[ "${SPECWEAVE_SKIP_DEBOUNCE:-0}" != "1" ]]; then
116
140
  DEBOUNCE_FILE="$STATE_DIR/.github-auto-create-pending-$INC_ID"
117
141
  if [[ -f "$DEBOUNCE_FILE" ]]; then
118
- SIGNAL_AGE=$(($(date +%s) - $(stat -f "%m" "$DEBOUNCE_FILE" 2>/dev/null || echo 0)))
142
+ # POSIX-portable: use perl for mtime (works on macOS and Linux)
143
+ FILE_MTIME=$(perl -e 'print((stat($ARGV[0]))[9])' "$DEBOUNCE_FILE" 2>/dev/null || echo 0)
144
+ SIGNAL_AGE=$(($(date +%s) - FILE_MTIME))
119
145
  if (( SIGNAL_AGE < 30 )); then
120
146
  log "Debounce: signal age ${SIGNAL_AGE}s < 30s. Deferring."
121
147
  exit 0
@@ -80,10 +80,13 @@ for i in {1..15}; do
80
80
  break
81
81
  fi
82
82
 
83
- # Check for stale lock
83
+ # Check for stale lock (POSIX-portable: works on macOS and Linux)
84
84
  if [[ -d "$LOCK_FILE" ]]; then
85
- LOCK_AGE=$(($(date +%s) - $(stat -f "%m" "$LOCK_FILE" 2>/dev/null || echo 0)))
86
- if (( LOCK_AGE > LOCK_TIMEOUT )); then
85
+ # Use find -mmin which works on both macOS and Linux
86
+ LOCK_TIMEOUT_MIN=$(( (LOCK_TIMEOUT + 59) / 60 )) # Convert seconds to minutes (ceil)
87
+ [[ $LOCK_TIMEOUT_MIN -lt 1 ]] && LOCK_TIMEOUT_MIN=1
88
+ STALE=$(find "$LOCK_FILE" -maxdepth 0 -mmin +${LOCK_TIMEOUT_MIN} 2>/dev/null)
89
+ if [[ -n "$STALE" ]]; then
87
90
  rmdir "$LOCK_FILE" 2>/dev/null || true
88
91
  continue
89
92
  fi
@@ -27,9 +27,10 @@ async function syncSpecWithEnhancedContent(options) {
27
27
  console.log(`\u{1F517} Found ${mapping.increments.length} related increments`);
28
28
  console.log(`\u{1F4CB} Mapped ${Object.keys(mapping.userStoryMappings).length} user stories to tasks`);
29
29
  }
30
- const taskMapping = buildTaskMapping(mapping.increments, owner, repo);
30
+ const defaultBranch = (owner && repo) ? await getDefaultBranch(owner, repo) : null;
31
+ const taskMapping = buildTaskMapping(mapping.increments, owner, repo, defaultBranch);
31
32
  const architectureDocs = await findArchitectureDocs(rootDir, specId);
32
- const sourceLinks = buildSourceLinks(mapping.increments[0]?.id, owner, repo);
33
+ const sourceLinks = buildSourceLinks(mapping.increments[0]?.id, owner, repo, defaultBranch);
33
34
  const enhancedSpec = {
34
35
  ...baseSpec,
35
36
  summary: baseSpec.description,
@@ -153,7 +154,33 @@ async function findSpecWeaveRoot(specPath) {
153
154
  }
154
155
  }
155
156
  }
156
- function buildTaskMapping(increments, owner, repo) {
157
+ // Cache for default branch per repo per session
158
+ const defaultBranchCache = new Map();
159
+
160
+ async function getDefaultBranch(owner, repo) {
161
+ const cacheKey = `${owner}/${repo}`;
162
+ if (defaultBranchCache.has(cacheKey)) {
163
+ return defaultBranchCache.get(cacheKey);
164
+ }
165
+ try {
166
+ const { execFileSync } = await import("child_process");
167
+ const result = execFileSync("gh", [
168
+ "repo", "view", `${owner}/${repo}`,
169
+ "--json", "defaultBranchRef",
170
+ "--jq", ".defaultBranchRef.name"
171
+ ], { encoding: "utf-8", timeout: 10000 }).trim();
172
+ if (result) {
173
+ defaultBranchCache.set(cacheKey, result);
174
+ return result;
175
+ }
176
+ } catch {
177
+ // On failure, return null — callers omit branch segment
178
+ }
179
+ defaultBranchCache.set(cacheKey, null);
180
+ return null;
181
+ }
182
+
183
+ function buildTaskMapping(increments, owner, repo, defaultBranch) {
157
184
  if (increments.length === 0) return void 0;
158
185
  const firstIncrement = increments[0];
159
186
  const tasks = firstIncrement.tasks.map((task) => ({
@@ -162,10 +189,11 @@ function buildTaskMapping(increments, owner, repo) {
162
189
  userStories: task.userStories,
163
190
  githubIssue: task.githubIssue
164
191
  }));
192
+ const branchSegment = defaultBranch ? `/blob/${defaultBranch}` : "";
165
193
  return {
166
194
  incrementId: firstIncrement.id,
167
195
  tasks,
168
- tasksUrl: `https://github.com/${owner}/${repo}/blob/develop/.specweave/increments/${firstIncrement.id}/tasks.md`
196
+ tasksUrl: `https://github.com/${owner}/${repo}${branchSegment}/.specweave/increments/${firstIncrement.id}/tasks.md`
169
197
  };
170
198
  }
171
199
  async function findArchitectureDocs(rootDir, specId) {
@@ -198,9 +226,10 @@ async function findArchitectureDocs(rootDir, specId) {
198
226
  }
199
227
  return docs;
200
228
  }
201
- function buildSourceLinks(incrementId, owner, repo) {
229
+ function buildSourceLinks(incrementId, owner, repo, defaultBranch) {
202
230
  if (!incrementId) return void 0;
203
- const baseUrl = `https://github.com/${owner}/${repo}/blob/develop/.specweave`;
231
+ const branchSegment = defaultBranch ? `/blob/${defaultBranch}` : "";
232
+ const baseUrl = `https://github.com/${owner}/${repo}${branchSegment}/.specweave`;
204
233
  return {
205
234
  spec: `${baseUrl}/docs/internal/specs/default/spec-${incrementId.replace(/^\d+-/, "")}.md`,
206
235
  plan: `${baseUrl}/increments/${incrementId}/plan.md`,
@@ -15,9 +15,9 @@ async function fetchBoardsForRepo(owner, repo) {
15
15
  "-H",
16
16
  "Accept: application/vnd.github+json"
17
17
  ], { env: getGhEnv() });
18
- if (result.status !== 0) {
18
+ if (result.exitCode !== 0) {
19
19
  console.error(`\u274C Failed to fetch boards for ${owner}/${repo}:`, result.stderr);
20
- throw new Error(`GitHub API error: ${result.status} ${result.stderr}`);
20
+ throw new Error(`GitHub API error (exit code ${result.exitCode}): ${result.stderr}`);
21
21
  }
22
22
  const boards = result.stdout.trim().split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
23
23
  console.log(`\u2705 Found ${boards.length} board(s) for repo ${owner}/${repo}`);
@@ -38,9 +38,9 @@ async function fetchBoardsForOrg(org) {
38
38
  "-H",
39
39
  "Accept: application/vnd.github+json"
40
40
  ], { env: getGhEnv() });
41
- if (result.status !== 0) {
41
+ if (result.exitCode !== 0) {
42
42
  console.error(`\u274C Failed to fetch boards for org ${org}:`, result.stderr);
43
- throw new Error(`GitHub API error: ${result.status} ${result.stderr}`);
43
+ throw new Error(`GitHub API error (exit code ${result.exitCode}): ${result.stderr}`);
44
44
  }
45
45
  const boards = result.stdout.trim().split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
46
46
  console.log(`\u2705 Found ${boards.length} board(s) for org ${org}`);
@@ -54,9 +54,9 @@ export async function fetchBoardsForRepo(
54
54
  'Accept: application/vnd.github+json',
55
55
  ], { env: getGhEnv() });
56
56
 
57
- if (result.status !== 0) {
57
+ if (result.exitCode !== 0) {
58
58
  console.error(`❌ Failed to fetch boards for ${owner}/${repo}:`, result.stderr);
59
- throw new Error(`GitHub API error: ${result.status} ${result.stderr}`);
59
+ throw new Error(`GitHub API error (exit code ${result.exitCode}): ${result.stderr}`);
60
60
  }
61
61
 
62
62
  // Parse JSONL (one JSON object per line)
@@ -98,9 +98,9 @@ export async function fetchBoardsForOrg(
98
98
  'Accept: application/vnd.github+json',
99
99
  ], { env: getGhEnv() });
100
100
 
101
- if (result.status !== 0) {
101
+ if (result.exitCode !== 0) {
102
102
  console.error(`❌ Failed to fetch boards for org ${org}:`, result.stderr);
103
- throw new Error(`GitHub API error: ${result.status} ${result.stderr}`);
103
+ throw new Error(`GitHub API error (exit code ${result.exitCode}): ${result.stderr}`);
104
104
  }
105
105
 
106
106
  // Parse JSONL
@@ -103,13 +103,14 @@ class GitHubClientV2 {
103
103
  /**
104
104
  * Create or get existing milestone
105
105
  */
106
- async createOrGetMilestone(title, description, daysFromNow = 2) {
106
+ async createOrGetMilestone(title, description, daysFromNow) {
107
+ const dueDays = daysFromNow ?? 2;
107
108
  const existing = await this.getMilestoneByTitle(title);
108
109
  if (existing) {
109
110
  return existing;
110
111
  }
111
112
  const dueDate = /* @__PURE__ */ new Date();
112
- dueDate.setDate(dueDate.getDate() + daysFromNow);
113
+ dueDate.setDate(dueDate.getDate() + dueDays);
113
114
  const dueDateISO = dueDate.toISOString();
114
115
  const args = [
115
116
  "api",
@@ -136,7 +137,7 @@ class GitHubClientV2 {
136
137
  async getMilestoneByTitle(title) {
137
138
  const result = await execFileNoThrow("gh", [
138
139
  "api",
139
- `repos/${this.fullRepo}/milestones`,
140
+ `repos/${this.fullRepo}/milestones?per_page=100&state=all`,
140
141
  "--jq",
141
142
  `.[] | select(.title=="${title}") | {number: .number, title: .title, description: .description, state: .state}`
142
143
  ], { env: this.getGhEnv() });
@@ -477,10 +478,9 @@ ${body}`;
477
478
  async getLastComment(issueNumber) {
478
479
  const result = await execFileNoThrow("gh", [
479
480
  "api",
480
- `repos/${this.fullRepo}/issues/${issueNumber}/comments`,
481
+ `repos/${this.fullRepo}/issues/${issueNumber}/comments?sort=created&direction=desc&per_page=1`,
481
482
  "--jq",
482
- ".[-1] | {body: .body, author: .user.login}"
483
- // Get last comment only
483
+ ".[0] | {body: .body, author: .user.login}"
484
484
  ], { env: this.getGhEnv() });
485
485
  if (result.exitCode !== 0) {
486
486
  return null;
@@ -148,17 +148,20 @@ export class GitHubClientV2 {
148
148
  async createOrGetMilestone(
149
149
  title: string,
150
150
  description?: string,
151
- daysFromNow: number = 2
151
+ daysFromNow?: number
152
152
  ): Promise<GitHubMilestone> {
153
+ // Use configured value or default to 2 days
154
+ const dueDays = daysFromNow ?? 2;
155
+
153
156
  // Check if milestone already exists
154
157
  const existing = await this.getMilestoneByTitle(title);
155
158
  if (existing) {
156
159
  return existing;
157
160
  }
158
161
 
159
- // Calculate due date
162
+ // Calculate due date from creation timestamp
160
163
  const dueDate = new Date();
161
- dueDate.setDate(dueDate.getDate() + daysFromNow);
164
+ dueDate.setDate(dueDate.getDate() + dueDays);
162
165
  const dueDateISO = dueDate.toISOString();
163
166
 
164
167
  // Build API request
@@ -194,7 +197,7 @@ export class GitHubClientV2 {
194
197
  ): Promise<GitHubMilestone | null> {
195
198
  const result = await execFileNoThrow('gh', [
196
199
  'api',
197
- `repos/${this.fullRepo}/milestones`,
200
+ `repos/${this.fullRepo}/milestones?per_page=100&state=all`,
198
201
  '--jq',
199
202
  `.[] | select(.title=="${title}") | {number: .number, title: .title, description: .description, state: .state}`,
200
203
  ], { env: this.getGhEnv() });
@@ -607,12 +610,13 @@ export class GitHubClientV2 {
607
610
  * Returns the most recent comment body, or null if no comments exist
608
611
  */
609
612
  async getLastComment(issueNumber: number): Promise<{body: string; author: string} | null> {
610
- // Get all comments (sorted by creation date, newest last)
613
+ // Query the last comment directly using per_page=1 + page from last page
614
+ // sort=created&direction=desc gives newest first, per_page=1 returns just one
611
615
  const result = await execFileNoThrow('gh', [
612
616
  'api',
613
- `repos/${this.fullRepo}/issues/${issueNumber}/comments`,
617
+ `repos/${this.fullRepo}/issues/${issueNumber}/comments?sort=created&direction=desc&per_page=1`,
614
618
  '--jq',
615
- '.[-1] | {body: .body, author: .user.login}', // Get last comment only
619
+ '.[0] | {body: .body, author: .user.login}',
616
620
  ], { env: this.getGhEnv() });
617
621
 
618
622
  if (result.exitCode !== 0) {
@@ -40,15 +40,16 @@ class GitHubClient {
40
40
  *
41
41
  * @param title Milestone title
42
42
  * @param description Milestone description
43
- * @param daysFromNow Days until milestone due (default: 2 days - SpecWeave AI velocity)
43
+ * @param daysFromNow Days until milestone due (configurable via github.milestoneDueDays, default: 2)
44
44
  */
45
- async createOrGetMilestone(title, description, daysFromNow = 2) {
45
+ async createOrGetMilestone(title, description, daysFromNow) {
46
+ const dueDays = daysFromNow ?? 2;
46
47
  const existing = await this.getMilestoneByTitle(title);
47
48
  if (existing) {
48
49
  return existing;
49
50
  }
50
51
  const dueDate = /* @__PURE__ */ new Date();
51
- dueDate.setDate(dueDate.getDate() + daysFromNow);
52
+ dueDate.setDate(dueDate.getDate() + dueDays);
52
53
  const dueDateISO = dueDate.toISOString();
53
54
  const cmd = `gh api repos/${this.repo}/milestones -f title="${title}" ${description ? `-f description="${description}"` : ""} -f due_on="${dueDateISO}" --jq '{number: .number, title: .title, description: .description, state: .state, due_on: .due_on}'`;
54
55
  try {
@@ -63,7 +64,7 @@ class GitHubClient {
63
64
  */
64
65
  async getMilestoneByTitle(title) {
65
66
  try {
66
- const cmd = `gh api repos/${this.repo}/milestones --jq '.[] | select(.title=="${title}") | {number: .number, title: .title, description: .description, state: .state}'`;
67
+ const cmd = `gh api "repos/${this.repo}/milestones?per_page=100&state=all" --jq '.[] | select(.title=="${title}") | {number: .number, title: .title, description: .description, state: .state}'`;
67
68
  const output = execSync(cmd, { encoding: "utf-8" }).trim();
68
69
  return output ? JSON.parse(output) : null;
69
70
  } catch {
@@ -54,22 +54,24 @@ export class GitHubClient {
54
54
  *
55
55
  * @param title Milestone title
56
56
  * @param description Milestone description
57
- * @param daysFromNow Days until milestone due (default: 2 days - SpecWeave AI velocity)
57
+ * @param daysFromNow Days until milestone due (configurable via github.milestoneDueDays, default: 2)
58
58
  */
59
59
  async createOrGetMilestone(
60
60
  title: string,
61
61
  description?: string,
62
- daysFromNow: number = 2
62
+ daysFromNow?: number
63
63
  ): Promise<GitHubMilestone> {
64
+ // Use configured value or default to 2 days
65
+ const dueDays = daysFromNow ?? 2;
64
66
  // Check if milestone already exists
65
67
  const existing = await this.getMilestoneByTitle(title);
66
68
  if (existing) {
67
69
  return existing;
68
70
  }
69
71
 
70
- // Calculate due date (SpecWeave default: 1-2 days with AI assistance)
72
+ // Calculate due date from creation timestamp
71
73
  const dueDate = new Date();
72
- dueDate.setDate(dueDate.getDate() + daysFromNow);
74
+ dueDate.setDate(dueDate.getDate() + dueDays);
73
75
  const dueDateISO = dueDate.toISOString();
74
76
 
75
77
  // Create new milestone with due date
@@ -88,7 +90,7 @@ export class GitHubClient {
88
90
  */
89
91
  private async getMilestoneByTitle(title: string): Promise<GitHubMilestone | null> {
90
92
  try {
91
- const cmd = `gh api repos/${this.repo}/milestones --jq '.[] | select(.title=="${title}") | {number: .number, title: .title, description: .description, state: .state}'`;
93
+ const cmd = `gh api "repos/${this.repo}/milestones?per_page=100&state=all" --jq '.[] | select(.title=="${title}") | {number: .number, title: .title, description: .description, state: .state}'`;
92
94
  const output = execSync(cmd, { encoding: 'utf-8' }).trim();
93
95
  return output ? JSON.parse(output) : null;
94
96
  } catch {