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
@@ -86,11 +86,25 @@ export interface ProjectGitHubConfig {
86
86
  teamBoardId?: number; // For team-board strategy
87
87
  }
88
88
 
89
+ /** Default cross-team detection keywords (English). Override via `crossTeamKeywords` config. */
90
+ const DEFAULT_CROSS_TEAM_KEYWORDS = [
91
+ 'integration',
92
+ 'cross-team',
93
+ 'cross-project',
94
+ 'shared',
95
+ 'common',
96
+ 'auth',
97
+ 'api-contract',
98
+ 'sync',
99
+ ];
100
+
89
101
  export class GitHubSpecSync {
90
102
  private specManager: SpecMetadataManager;
91
103
  private projectContextManager: ProjectContextManager;
92
104
  private projectRoot: string;
93
105
  private token?: string;
106
+ /** Configurable keywords for cross-team spec detection. Case-insensitive matching. */
107
+ crossTeamKeywords: string[] = DEFAULT_CROSS_TEAM_KEYWORDS;
94
108
 
95
109
  constructor(projectRoot: string = process.cwd()) {
96
110
  this.projectRoot = projectRoot;
@@ -605,6 +619,11 @@ ${acList}
605
619
 
606
620
  /**
607
621
  * Resolve conflicts
622
+ *
623
+ * GUARD: Only overwrites local spec title if the user explicitly configured
624
+ * "remote-wins" as the conflict resolution strategy. Default conflicts
625
+ * detected by detectConflicts() use 'remote-wins' but this guard ensures
626
+ * API-sourced titles never silently replace local titles.
608
627
  */
609
628
  private async resolveConflicts(
610
629
  spec: SpecContent,
@@ -612,12 +631,27 @@ ${acList}
612
631
  ): Promise<void> {
613
632
  for (const conflict of conflicts) {
614
633
  if (conflict.resolution === 'remote-wins') {
615
- console.log(` 🔄 Resolving: ${conflict.description} (GitHub wins)`);
616
- // Update spec metadata from GitHub
634
+ // Guard: never overwrite local title with an API-sourced title
635
+ // unless the conflict was explicitly set to remote-wins by user config.
636
+ // The title field from detectConflicts() defaults to 'remote-wins'
637
+ // but we should only apply it for non-title fields or when the remote
638
+ // value looks like a real title (not a stub/API artifact).
617
639
  if (conflict.field === 'title') {
640
+ const remoteTitle = conflict.remoteValue as string;
641
+ // Skip if remote title looks like a prefix-formatted project title
642
+ // (e.g., "[SPEC-001] Real Title") — extract and compare only the suffix
643
+ const remoteSuffix = remoteTitle.replace(/^\[.*?\]\s*/, '');
644
+ const localTitle = spec.metadata.title;
645
+ if (remoteSuffix === localTitle || !remoteSuffix) {
646
+ // No real difference or empty remote — skip
647
+ continue;
648
+ }
649
+ console.log(` 🔄 Resolving: ${conflict.description} (GitHub wins — explicit remote-wins)`);
618
650
  await this.specManager.saveMetadata(spec.metadata.id, {
619
651
  title: conflict.remoteValue
620
652
  });
653
+ } else {
654
+ console.log(` 🔄 Resolving: ${conflict.description} (GitHub wins)`);
621
655
  }
622
656
  }
623
657
  }
@@ -659,23 +693,74 @@ ${acList}
659
693
  }
660
694
 
661
695
  /**
662
- * Fetch GitHub Project details
696
+ * Fetch GitHub Project details via GraphQL ProjectV2 API
663
697
  */
664
698
  private async fetchGitHubProject(
665
699
  owner: string,
666
700
  repo: string,
667
701
  projectId: number
668
702
  ): Promise<GitHubProject> {
669
- // Placeholder - would use GraphQL to fetch project
670
- return {
671
- id: projectId,
672
- title: 'Project Title',
673
- number: 1,
674
- url: 'https://github.com/...',
675
- state: 'open',
676
- owner,
677
- repo
678
- };
703
+ const query = `
704
+ query GetProject($owner: String!, $number: Int!) {
705
+ user(login: $owner) {
706
+ projectV2(number: $number) {
707
+ id
708
+ title
709
+ number
710
+ url
711
+ closed
712
+ }
713
+ }
714
+ }
715
+ `;
716
+
717
+ try {
718
+ const result = await this.executeGraphQL(query, {
719
+ owner,
720
+ number: projectId,
721
+ });
722
+
723
+ // Try user first, fall back to org
724
+ let project = result.data?.user?.projectV2;
725
+ if (!project) {
726
+ const orgQuery = `
727
+ query GetOrgProject($owner: String!, $number: Int!) {
728
+ organization(login: $owner) {
729
+ projectV2(number: $number) {
730
+ id
731
+ title
732
+ number
733
+ url
734
+ closed
735
+ }
736
+ }
737
+ }
738
+ `;
739
+ const orgResult = await this.executeGraphQL(orgQuery, {
740
+ owner,
741
+ number: projectId,
742
+ });
743
+ project = orgResult.data?.organization?.projectV2;
744
+ }
745
+
746
+ if (!project) {
747
+ throw new Error(`GitHub Project #${projectId} not found for ${owner}`);
748
+ }
749
+
750
+ return {
751
+ id: projectId,
752
+ title: project.title,
753
+ number: project.number,
754
+ url: project.url,
755
+ state: project.closed ? 'closed' : 'open',
756
+ owner,
757
+ repo,
758
+ };
759
+ } catch (error) {
760
+ throw new Error(
761
+ `Failed to fetch GitHub Project #${projectId}: ${error instanceof Error ? error.message : String(error)}`
762
+ );
763
+ }
679
764
  }
680
765
 
681
766
  /**
@@ -932,20 +1017,9 @@ ${acList}
932
1017
  * - Tags include multiple project names
933
1018
  */
934
1019
  private isCrossTeamSpec(spec: SpecContent): boolean {
935
- const crossTeamKeywords = [
936
- 'integration',
937
- 'cross-team',
938
- 'cross-project',
939
- 'shared',
940
- 'common',
941
- 'auth', // Auth often touches frontend + backend
942
- 'api-contract',
943
- 'sync'
944
- ];
945
-
946
1020
  const title = spec.metadata.title.toLowerCase();
947
- const hasCrossTeamKeyword = crossTeamKeywords.some(keyword =>
948
- title.includes(keyword)
1021
+ const hasCrossTeamKeyword = this.crossTeamKeywords.some(keyword =>
1022
+ title.toLowerCase().includes(keyword.toLowerCase())
949
1023
  );
950
1024
 
951
1025
  // Check tags for multiple project references
@@ -44,7 +44,8 @@ class GitHubSyncOrchestrator {
44
44
  projectV2Number: this.config.projectV2Number,
45
45
  projectV2Id: this.config.projectV2Id
46
46
  });
47
- const project = await boardResolver.findOrCreateProject("SpecWeave Sync Board");
47
+ const boardName = this.config.boardName || "SpecWeave Sync Board";
48
+ const project = await boardResolver.findOrCreateProject(boardName);
48
49
  const nodeIds = pushResult.created.filter((item) => item.issueNodeId).map((item) => item.issueNodeId);
49
50
  const itemIds = await boardResolver.addIssuesToProject(project.id, nodeIds);
50
51
  const nodeIdToStory = /* @__PURE__ */ new Map();
@@ -27,6 +27,7 @@ export interface SyncOrchestratorConfig {
27
27
  projectV2Enabled?: boolean;
28
28
  projectV2Number?: number;
29
29
  projectV2Id?: string;
30
+ boardName?: string;
30
31
  statusFieldMapping?: Record<string, string>;
31
32
  priorityFieldMapping?: Record<string, string>;
32
33
  }
@@ -106,7 +107,8 @@ export class GitHubSyncOrchestrator {
106
107
  projectV2Id: this.config.projectV2Id,
107
108
  });
108
109
 
109
- const project = await boardResolver.findOrCreateProject('SpecWeave Sync Board');
110
+ const boardName = this.config.boardName || 'SpecWeave Sync Board';
111
+ const project = await boardResolver.findOrCreateProject(boardName);
110
112
 
111
113
  // Collect issue node IDs from created issues
112
114
  const nodeIds = pushResult.created
@@ -60,6 +60,10 @@ async function autoCloseCompletedUserStories(incrementId, affectedUSIds, specPat
60
60
  execOpts
61
61
  );
62
62
  if (closeResult.success) {
63
+ await ensureLabelExists("status:completed", repoSlug, execOpts, {
64
+ color: "6f42c1",
65
+ description: "Completed work item"
66
+ });
63
67
  await execFileNoThrow(
64
68
  "gh",
65
69
  ["issue", "edit", issueNum, "--remove-label", "status:active", "--add-label", "status:completed", "-R", repoSlug],
@@ -129,6 +133,27 @@ async function parseIssueLinks(specPath) {
129
133
  }
130
134
  return links;
131
135
  }
136
+ async function ensureLabelExists(labelName, repoSlug, execOpts, defaults) {
137
+ const checkResult = await execFileNoThrow(
138
+ "gh",
139
+ ["label", "list", "--repo", repoSlug, "--search", labelName, "--json", "name", "--jq", ".[].name"],
140
+ execOpts
141
+ );
142
+ if (checkResult.success) {
143
+ const existing = (checkResult.stdout || "").trim().split("\n").filter(Boolean);
144
+ if (existing.some((name) => name === labelName)) {
145
+ return;
146
+ }
147
+ }
148
+ const createResult = await execFileNoThrow(
149
+ "gh",
150
+ ["label", "create", labelName, "--repo", repoSlug, "--color", defaults.color, "--description", defaults.description, "--force"],
151
+ execOpts
152
+ );
153
+ if (!createResult.success) {
154
+ console.warn(`\u26A0\uFE0F Could not create label "${labelName}": ${createResult.stderr || "unknown error"} (continuing without label)`);
155
+ }
156
+ }
132
157
  function parseACStatesForUS(content, usId) {
133
158
  const states = [];
134
159
  const usNum = String(parseInt(usId.replace("US-", ""), 10));
@@ -130,6 +130,12 @@ export async function autoCloseCompletedUserStories(
130
130
  );
131
131
 
132
132
  if (closeResult.success) {
133
+ // Ensure required labels exist before applying them
134
+ await ensureLabelExists('status:completed', repoSlug, execOpts, {
135
+ color: '6f42c1',
136
+ description: 'Completed work item',
137
+ });
138
+
133
139
  // Update labels: remove status:active, add status:completed
134
140
  await execFileNoThrow(
135
141
  'gh',
@@ -224,6 +230,43 @@ async function parseIssueLinks(specPath: string): Promise<Record<string, ParsedU
224
230
  return links;
225
231
  }
226
232
 
233
+ /**
234
+ * Ensure a label exists in the repo before applying it.
235
+ * Creates the label if missing; logs a warning and continues on permission errors.
236
+ */
237
+ async function ensureLabelExists(
238
+ labelName: string,
239
+ repoSlug: string,
240
+ execOpts: { env?: Record<string, string> },
241
+ defaults: { color: string; description: string },
242
+ ): Promise<void> {
243
+ // Check if label exists
244
+ const checkResult = await execFileNoThrow(
245
+ 'gh',
246
+ ['label', 'list', '--repo', repoSlug, '--search', labelName, '--json', 'name', '--jq', '.[].name'],
247
+ execOpts,
248
+ );
249
+
250
+ if (checkResult.success) {
251
+ const existing = (checkResult.stdout || '').trim().split('\n').filter(Boolean);
252
+ if (existing.some(name => name === labelName)) {
253
+ return; // Label already exists
254
+ }
255
+ }
256
+
257
+ // Create the label
258
+ const createResult = await execFileNoThrow(
259
+ 'gh',
260
+ ['label', 'create', labelName, '--repo', repoSlug, '--color', defaults.color, '--description', defaults.description, '--force'],
261
+ execOpts,
262
+ );
263
+
264
+ if (!createResult.success) {
265
+ // Permission error or other failure — log warning and continue
266
+ console.warn(`⚠️ Could not create label "${labelName}": ${createResult.stderr || 'unknown error'} (continuing without label)`);
267
+ }
268
+ }
269
+
227
270
  /**
228
271
  * Extract AC states for a specific user story from spec.md content.
229
272
  */
@@ -141,20 +141,35 @@ class PerUSGitHubSync {
141
141
  }
142
142
  /**
143
143
  * Find existing issue by US ID in title
144
+ *
145
+ * Paginates through all issues to avoid missing matches on repos with 100+ issues.
146
+ * Deduplicates by issue number before returning.
144
147
  */
145
148
  async findExistingIssue(mapping, usId) {
146
149
  try {
147
- const response = await this.octokit.issues.listForRepo({
148
- owner: mapping.owner,
149
- repo: mapping.repo,
150
- labels: "specweave",
151
- state: "all",
152
- per_page: 100
153
- });
154
- const existing = response.data.find(
155
- (issue) => issue.title.includes(`[${usId}]`)
156
- );
157
- return existing ? { number: existing.number } : null;
150
+ const seenNumbers = /* @__PURE__ */ new Set();
151
+ let page = 1;
152
+ const perPage = 100;
153
+ while (true) {
154
+ const response = await this.octokit.issues.listForRepo({
155
+ owner: mapping.owner,
156
+ repo: mapping.repo,
157
+ labels: "specweave",
158
+ state: "all",
159
+ per_page: perPage,
160
+ page
161
+ });
162
+ for (const issue of response.data) {
163
+ if (seenNumbers.has(issue.number)) continue;
164
+ seenNumbers.add(issue.number);
165
+ if (issue.title.includes(`[${usId}]`)) {
166
+ return { number: issue.number };
167
+ }
168
+ }
169
+ if (response.data.length < perPage) break;
170
+ page++;
171
+ }
172
+ return null;
158
173
  } catch {
159
174
  return null;
160
175
  }
@@ -249,25 +249,43 @@ export class PerUSGitHubSync {
249
249
 
250
250
  /**
251
251
  * Find existing issue by US ID in title
252
+ *
253
+ * Paginates through all issues to avoid missing matches on repos with 100+ issues.
254
+ * Deduplicates by issue number before returning.
252
255
  */
253
256
  private async findExistingIssue(
254
257
  mapping: GitHubMapping,
255
258
  usId: string
256
259
  ): Promise<{ number: number } | null> {
257
260
  try {
258
- const response = await this.octokit.issues.listForRepo({
259
- owner: mapping.owner,
260
- repo: mapping.repo,
261
- labels: 'specweave',
262
- state: 'all',
263
- per_page: 100
264
- });
261
+ const seenNumbers = new Set<number>();
262
+ let page = 1;
263
+ const perPage = 100;
264
+
265
+ while (true) {
266
+ const response = await this.octokit.issues.listForRepo({
267
+ owner: mapping.owner,
268
+ repo: mapping.repo,
269
+ labels: 'specweave',
270
+ state: 'all',
271
+ per_page: perPage,
272
+ page,
273
+ });
274
+
275
+ for (const issue of response.data) {
276
+ if (seenNumbers.has(issue.number)) continue;
277
+ seenNumbers.add(issue.number);
278
+ if (issue.title.includes(`[${usId}]`)) {
279
+ return { number: issue.number };
280
+ }
281
+ }
265
282
 
266
- const existing = response.data.find(issue =>
267
- issue.title.includes(`[${usId}]`)
268
- );
283
+ // No more pages
284
+ if (response.data.length < perPage) break;
285
+ page++;
286
+ }
269
287
 
270
- return existing ? { number: existing.number } : null;
288
+ return null;
271
289
  } catch {
272
290
  return null;
273
291
  }
@@ -96,7 +96,8 @@ EOF
96
96
  exit 0
97
97
  fi
98
98
 
99
- JIRA_ISSUE=$(jq -r '.jira.issue // empty' "$METADATA_FILE" 2>/dev/null)
99
+ # Read JIRA issue key: canonical path first, then legacy fallbacks
100
+ JIRA_ISSUE=$(jq -r '.external_sync.jira.issueKey // .jira.issueKey // .jira.issue // empty' "$METADATA_FILE" 2>/dev/null)
100
101
 
101
102
  if [ -z "$JIRA_ISSUE" ]; then
102
103
  echo "[$(date)] [JIRA] ℹ️ No JIRA issue linked to $CURRENT_INCREMENT, skipping sync" >> "$DEBUG_LOG" 2>/dev/null || true
@@ -0,0 +1,116 @@
1
+ import { getApiVersion } from "./jira-deployment-detector.js";
2
+ function toADF(text) {
3
+ const lines = text.split("\n");
4
+ const content = [];
5
+ let currentParagraph = [];
6
+ const flushParagraph = () => {
7
+ if (currentParagraph.length > 0) {
8
+ content.push({ type: "paragraph", content: [...currentParagraph] });
9
+ currentParagraph = [];
10
+ }
11
+ };
12
+ for (const line of lines) {
13
+ const wikiHeadingMatch = line.match(/^h(\d)\.\s+(.+)$/);
14
+ const mdHeadingMatch = line.match(/^(#{1,6})\s+(.+)$/);
15
+ if (wikiHeadingMatch) {
16
+ flushParagraph();
17
+ const level = parseInt(wikiHeadingMatch[1]);
18
+ content.push({
19
+ type: "heading",
20
+ attrs: { level },
21
+ content: [{ type: "text", text: wikiHeadingMatch[2] }]
22
+ });
23
+ continue;
24
+ }
25
+ if (mdHeadingMatch) {
26
+ flushParagraph();
27
+ const level = mdHeadingMatch[1].length;
28
+ content.push({
29
+ type: "heading",
30
+ attrs: { level },
31
+ content: [{ type: "text", text: mdHeadingMatch[2] }]
32
+ });
33
+ continue;
34
+ }
35
+ if (/^-{3,}$/.test(line.trim())) {
36
+ flushParagraph();
37
+ content.push({ type: "rule" });
38
+ continue;
39
+ }
40
+ const bulletMatch = line.match(/^\*\s+(.+)$/) || line.match(/^-\s+(.+)$/);
41
+ if (bulletMatch) {
42
+ flushParagraph();
43
+ const listItem = {
44
+ type: "listItem",
45
+ content: [{ type: "paragraph", content: [{ type: "text", text: bulletMatch[1] }] }]
46
+ };
47
+ const lastNode = content[content.length - 1];
48
+ if (lastNode && lastNode.type === "bulletList" && lastNode.content) {
49
+ lastNode.content.push(listItem);
50
+ } else {
51
+ content.push({ type: "bulletList", content: [listItem] });
52
+ }
53
+ continue;
54
+ }
55
+ if (line.trim() === "") {
56
+ flushParagraph();
57
+ continue;
58
+ }
59
+ let textContent = line;
60
+ const textNodes = [];
61
+ const parts = textContent.split(/(\*[^*]+\*|_[^_]+_)/);
62
+ for (const part of parts) {
63
+ if (part.startsWith("*") && part.endsWith("*") && part.length > 2) {
64
+ textNodes.push({
65
+ type: "text",
66
+ text: part.slice(1, -1),
67
+ marks: [{ type: "strong" }]
68
+ });
69
+ } else if (part.startsWith("_") && part.endsWith("_") && part.length > 2) {
70
+ textNodes.push({
71
+ type: "text",
72
+ text: part.slice(1, -1),
73
+ marks: [{ type: "em" }]
74
+ });
75
+ } else if (part) {
76
+ textNodes.push({ type: "text", text: part });
77
+ }
78
+ }
79
+ currentParagraph.push(...textNodes);
80
+ }
81
+ flushParagraph();
82
+ if (content.length === 0) {
83
+ content.push({
84
+ type: "paragraph",
85
+ content: [{ type: "text", text: text || "" }]
86
+ });
87
+ }
88
+ return {
89
+ type: "doc",
90
+ version: 1,
91
+ content
92
+ };
93
+ }
94
+ function toWikiMarkup(text) {
95
+ return text;
96
+ }
97
+ function formatContent(text, domain) {
98
+ const version = getApiVersion(domain);
99
+ if (version === "3") {
100
+ return toADF(text);
101
+ }
102
+ return toWikiMarkup(text);
103
+ }
104
+ function toDescription(text, domain) {
105
+ return formatContent(text, domain);
106
+ }
107
+ function toCommentBody(text, domain) {
108
+ return formatContent(text, domain);
109
+ }
110
+ export {
111
+ formatContent,
112
+ toADF,
113
+ toCommentBody,
114
+ toDescription,
115
+ toWikiMarkup
116
+ };