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
@@ -31,6 +31,8 @@ import {
31
31
  formatForJira,
32
32
  CommentContent,
33
33
  } from '../../../src/core/comment-builder.js';
34
+ import { readIssueKey } from './metadata-paths.js';
35
+ import { getApiBaseUrl } from './jira-deployment-detector.js';
34
36
  import path from 'path';
35
37
  import fs from 'fs/promises';
36
38
 
@@ -84,7 +86,7 @@ export async function syncSpecCommitsToJira(
84
86
  return result;
85
87
  }
86
88
 
87
- const jiraIssueKey = metadata.jira?.issueKey;
89
+ const jiraIssueKey = readIssueKey(metadata);
88
90
  if (!jiraIssueKey) {
89
91
  if (verbose) {
90
92
  console.log('No JIRA issue linked to increment');
@@ -94,7 +96,7 @@ export async function syncSpecCommitsToJira(
94
96
 
95
97
  // 2. Create JIRA client
96
98
  const client = axios.create({
97
- baseURL: `https://${config.domain}/rest/api/3`,
99
+ baseURL: getApiBaseUrl(config.domain),
98
100
  auth: {
99
101
  username: config.email,
100
102
  password: config.apiToken,
@@ -285,7 +287,7 @@ export async function postCommitBatchUpdate(
285
287
  ): Promise<boolean> {
286
288
  try {
287
289
  const client = axios.create({
288
- baseURL: `https://${config.domain}/rest/api/3`,
290
+ baseURL: getApiBaseUrl(config.domain),
289
291
  auth: {
290
292
  username: config.email,
291
293
  password: config.apiToken,
@@ -1,12 +1,18 @@
1
1
  import { SpecMetadataManager } from "../../../src/core/specs/spec-metadata-manager.js";
2
2
  import { SpecParser } from "../../../src/core/specs/spec-parser.js";
3
+ import * as fs from "fs/promises";
4
+ import * as path from "path";
5
+ import { detectDeploymentType, getApiBaseUrl } from "./jira-deployment-detector.js";
6
+ import { toDescription } from "./content-format-adapter.js";
7
+ import { getEpicLinkFieldForProject } from "./jira-field-discovery.js";
8
+ import { searchAllIssues } from "./jira-paginated-search.js";
3
9
  import axios from "axios";
4
10
  class JiraSpecSync {
5
11
  constructor(config, projectRoot = process.cwd()) {
6
12
  this.specManager = new SpecMetadataManager(projectRoot);
7
13
  this.config = config;
8
14
  this.client = axios.create({
9
- baseURL: `https://${config.domain}/rest/api/3`,
15
+ baseURL: getApiBaseUrl(config.domain),
10
16
  auth: {
11
17
  username: config.email,
12
18
  password: config.apiToken
@@ -17,6 +23,16 @@ class JiraSpecSync {
17
23
  }
18
24
  });
19
25
  }
26
+ /**
27
+ * Initialize: detect deployment type and update client baseURL
28
+ */
29
+ async init() {
30
+ const deployment = await detectDeploymentType(this.config.domain, {
31
+ email: this.config.email,
32
+ apiToken: this.config.apiToken
33
+ });
34
+ this.client.defaults.baseURL = deployment.baseUrl;
35
+ }
20
36
  /**
21
37
  * Sync spec to Jira Epic (CREATE or UPDATE)
22
38
  */
@@ -106,6 +122,7 @@ class JiraSpecSync {
106
122
  };
107
123
  }
108
124
  console.log(`\u26A0\uFE0F Detected ${conflicts.length} conflict(s)`);
125
+ await this.writeConflictReport(specId, conflicts);
109
126
  await this.resolveConflicts(spec, conflicts);
110
127
  console.log("\u2705 Sync FROM Jira complete!");
111
128
  return {
@@ -131,7 +148,7 @@ class JiraSpecSync {
131
148
  */
132
149
  async createJiraEpic(spec) {
133
150
  const epicSummary = `[${spec.metadata.id.toUpperCase()}] ${spec.metadata.title}`;
134
- const epicDescription = this.generateEpicDescription(spec);
151
+ const epicDescription = toDescription(this.generateEpicDescription(spec), this.config.domain);
135
152
  const issueType = this.mapTypeToJira(spec.metadata.type, "Epic");
136
153
  const payload = {
137
154
  fields: {
@@ -169,7 +186,7 @@ class JiraSpecSync {
169
186
  */
170
187
  async updateJiraEpic(epicKey, spec) {
171
188
  const epicSummary = `[${spec.metadata.id.toUpperCase()}] ${spec.metadata.title}`;
172
- const epicDescription = this.generateEpicDescription(spec);
189
+ const epicDescription = toDescription(this.generateEpicDescription(spec), this.config.domain);
173
190
  const payload = {
174
191
  fields: {
175
192
  summary: epicSummary,
@@ -295,20 +312,72 @@ ${acList}
295
312
  return conflicts;
296
313
  }
297
314
  /**
298
- * Resolve conflicts
315
+ * Resolve conflicts based on configurable strategy.
316
+ *
317
+ * Strategies:
318
+ * - 'manual' (default): Halt sync, report conflicts to user, no auto-resolve
319
+ * - 'remote-wins': Auto-resolve in favor of JIRA (remote)
320
+ * - 'local-wins': Auto-resolve in favor of spec (local)
321
+ * - 'report-only': Log conflicts, continue without resolving
299
322
  */
300
- async resolveConflicts(spec, conflicts) {
323
+ async resolveConflicts(spec, conflicts, strategy = "manual") {
324
+ if (strategy === "manual") {
325
+ console.log(` \u26A0\uFE0F ${conflicts.length} conflict(s) require manual resolution.`);
326
+ for (const conflict of conflicts) {
327
+ console.log(` - ${conflict.field}: local="${conflict.localValue}" vs remote="${conflict.remoteValue}"`);
328
+ }
329
+ console.log(` Sync halted. Review conflicts and resolve manually.`);
330
+ return;
331
+ }
332
+ if (strategy === "report-only") {
333
+ console.log(` \u2139\uFE0F ${conflicts.length} conflict(s) detected (report-only mode):`);
334
+ for (const conflict of conflicts) {
335
+ console.log(` - ${conflict.field}: local="${conflict.localValue}" vs remote="${conflict.remoteValue}"`);
336
+ }
337
+ return;
338
+ }
301
339
  for (const conflict of conflicts) {
302
- if (conflict.resolution === "remote-wins") {
340
+ if (strategy === "remote-wins") {
303
341
  console.log(` \u{1F504} Resolving: ${conflict.description} (Jira wins)`);
304
342
  if (conflict.field === "title") {
305
343
  await this.specManager.saveMetadata(spec.metadata.id, {
306
344
  title: conflict.remoteValue
307
345
  });
308
346
  }
347
+ } else if (strategy === "local-wins") {
348
+ console.log(` \u{1F504} Resolving: ${conflict.description} (local wins \u2014 no remote update)`);
309
349
  }
310
350
  }
311
351
  }
352
+ /**
353
+ * Write conflict report JSON file for detected conflicts.
354
+ */
355
+ async writeConflictReport(specId, conflicts) {
356
+ try {
357
+ const report = {
358
+ specId,
359
+ provider: "jira",
360
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
361
+ conflicts: conflicts.map((c) => ({
362
+ field: c.field,
363
+ localValue: c.localValue,
364
+ remoteValue: c.remoteValue,
365
+ description: c.description
366
+ }))
367
+ };
368
+ const reportsDir = path.join(
369
+ this.specManager.projectRoot || process.cwd(),
370
+ ".specweave",
371
+ "reports"
372
+ );
373
+ await fs.mkdir(reportsDir, { recursive: true });
374
+ const reportPath = path.join(reportsDir, "conflict-report.json");
375
+ await fs.writeFile(reportPath, JSON.stringify(report, null, 2));
376
+ console.log(` \u{1F4C4} Conflict report written to ${reportPath}`);
377
+ } catch (err) {
378
+ console.warn(" \u26A0\uFE0F Failed to write conflict report:", err.message);
379
+ }
380
+ }
312
381
  /**
313
382
  * Fetch Jira Epic details
314
383
  */
@@ -329,14 +398,11 @@ ${acList}
329
398
  */
330
399
  async findStoryByTitle(usId) {
331
400
  const jql = `project = ${this.config.projectKey} AND summary ~ "[${usId}]" AND issuetype = Story`;
332
- const response = await this.client.get("/search", {
333
- params: {
334
- jql,
335
- maxResults: 1,
336
- fields: "summary,description,status,labels"
337
- }
401
+ const issues = await searchAllIssues(this.client, {
402
+ jql,
403
+ fields: "summary,description,status,labels",
404
+ maxResults: 1
338
405
  });
339
- const issues = response.data.issues;
340
406
  return issues.length > 0 ? {
341
407
  id: issues[0].id,
342
408
  key: issues[0].key,
@@ -351,26 +417,31 @@ ${acList}
351
417
  */
352
418
  async createStory(story) {
353
419
  const issueType = this.mapTypeToJira(story.type, "Story");
354
- const payload = {
355
- fields: {
356
- project: {
357
- key: this.config.projectKey
358
- },
359
- summary: story.summary,
360
- description: story.description,
361
- issuetype: {
362
- name: issueType
363
- },
364
- labels: story.labels,
365
- // Link to epic (field name may vary by Jira configuration)
366
- customfield_10014: story.epicLink,
367
- // Epic Link field (adjust if needed)
368
- // Set native JIRA priority field
369
- priority: {
370
- name: this.mapPriorityToJira(story.priority)
371
- }
420
+ const { field: epicField, style } = await getEpicLinkFieldForProject(
421
+ this.config.domain,
422
+ this.config.projectKey,
423
+ { email: this.config.email, apiToken: this.config.apiToken }
424
+ );
425
+ const fields = {
426
+ project: {
427
+ key: this.config.projectKey
428
+ },
429
+ summary: story.summary,
430
+ description: story.description,
431
+ issuetype: {
432
+ name: issueType
433
+ },
434
+ labels: story.labels,
435
+ priority: {
436
+ name: this.mapPriorityToJira(story.priority)
372
437
  }
373
438
  };
439
+ if (style === "next-gen") {
440
+ fields.parent = { key: story.epicLink };
441
+ } else {
442
+ fields[epicField] = story.epicLink;
443
+ }
444
+ const payload = { fields };
374
445
  const response = await this.client.post("/issue", payload);
375
446
  const storyData = response.data;
376
447
  return {
@@ -15,6 +15,8 @@
15
15
 
16
16
  import { SpecMetadataManager } from '../../../src/core/specs/spec-metadata-manager.js';
17
17
  import { SpecParser } from '../../../src/core/specs/spec-parser.js';
18
+ import * as fs from 'fs/promises';
19
+ import * as path from 'path';
18
20
  import {
19
21
  SpecContent,
20
22
  UserStory,
@@ -22,13 +24,17 @@ import {
22
24
  SpecSyncConflict
23
25
  } from '../../../src/core/types/spec-metadata.js';
24
26
  import { execFileNoThrow } from '../../../src/utils/execFileNoThrow.js';
27
+ import { detectDeploymentType, getApiBaseUrl } from './jira-deployment-detector.js';
28
+ import { toDescription, AdfDocument } from './content-format-adapter.js';
29
+ import { getEpicLinkFieldForProject } from './jira-field-discovery.js';
30
+ import { searchAllIssues } from './jira-paginated-search.js';
25
31
  import axios, { AxiosInstance } from 'axios';
26
32
 
27
33
  export interface JiraEpic {
28
34
  id: string;
29
35
  key: string; // e.g., SPEC-1
30
36
  summary: string;
31
- description: string;
37
+ description: string | AdfDocument;
32
38
  status: {
33
39
  name: string; // To Do, In Progress, Done
34
40
  };
@@ -63,9 +69,9 @@ export class JiraSpecSync {
63
69
  this.specManager = new SpecMetadataManager(projectRoot);
64
70
  this.config = config;
65
71
 
66
- // Create Jira API client
72
+ // Create Jira API client — baseURL set dynamically via init()
67
73
  this.client = axios.create({
68
- baseURL: `https://${config.domain}/rest/api/3`,
74
+ baseURL: getApiBaseUrl(config.domain),
69
75
  auth: {
70
76
  username: config.email,
71
77
  password: config.apiToken
@@ -77,6 +83,17 @@ export class JiraSpecSync {
77
83
  });
78
84
  }
79
85
 
86
+ /**
87
+ * Initialize: detect deployment type and update client baseURL
88
+ */
89
+ async init(): Promise<void> {
90
+ const deployment = await detectDeploymentType(this.config.domain, {
91
+ email: this.config.email,
92
+ apiToken: this.config.apiToken,
93
+ });
94
+ this.client.defaults.baseURL = deployment.baseUrl;
95
+ }
96
+
80
97
  /**
81
98
  * Sync spec to Jira Epic (CREATE or UPDATE)
82
99
  */
@@ -191,7 +208,10 @@ export class JiraSpecSync {
191
208
 
192
209
  console.log(`⚠️ Detected ${conflicts.length} conflict(s)`);
193
210
 
194
- // 5. Resolve conflicts (Jira wins by default for now)
211
+ // 5. Write conflict report
212
+ await this.writeConflictReport(specId, conflicts);
213
+
214
+ // 6. Resolve conflicts using configurable strategy (default: manual)
195
215
  await this.resolveConflicts(spec, conflicts);
196
216
 
197
217
  console.log('✅ Sync FROM Jira complete!');
@@ -221,7 +241,7 @@ export class JiraSpecSync {
221
241
  */
222
242
  private async createJiraEpic(spec: SpecContent): Promise<JiraEpic> {
223
243
  const epicSummary = `[${spec.metadata.id.toUpperCase()}] ${spec.metadata.title}`;
224
- const epicDescription = this.generateEpicDescription(spec);
244
+ const epicDescription = toDescription(this.generateEpicDescription(spec), this.config.domain);
225
245
 
226
246
  // Determine issue type based on spec type (supports Bug for bug-type specs)
227
247
  const issueType = this.mapTypeToJira(spec.metadata.type, 'Epic');
@@ -230,7 +250,7 @@ export class JiraSpecSync {
230
250
  fields: {
231
251
  project: { key: string };
232
252
  summary: string;
233
- description: string;
253
+ description: any;
234
254
  issuetype: { name: string };
235
255
  labels: string[];
236
256
  priority?: { name: string };
@@ -276,7 +296,7 @@ export class JiraSpecSync {
276
296
  */
277
297
  private async updateJiraEpic(epicKey: string, spec: SpecContent): Promise<JiraEpic> {
278
298
  const epicSummary = `[${spec.metadata.id.toUpperCase()}] ${spec.metadata.title}`;
279
- const epicDescription = this.generateEpicDescription(spec);
299
+ const epicDescription = toDescription(this.generateEpicDescription(spec), this.config.domain);
280
300
 
281
301
  const payload = {
282
302
  fields: {
@@ -442,25 +462,85 @@ ${acList}
442
462
  }
443
463
 
444
464
  /**
445
- * Resolve conflicts
465
+ * Resolve conflicts based on configurable strategy.
466
+ *
467
+ * Strategies:
468
+ * - 'manual' (default): Halt sync, report conflicts to user, no auto-resolve
469
+ * - 'remote-wins': Auto-resolve in favor of JIRA (remote)
470
+ * - 'local-wins': Auto-resolve in favor of spec (local)
471
+ * - 'report-only': Log conflicts, continue without resolving
446
472
  */
447
473
  private async resolveConflicts(
448
474
  spec: SpecContent,
449
- conflicts: SpecSyncConflict[]
475
+ conflicts: SpecSyncConflict[],
476
+ strategy: 'manual' | 'remote-wins' | 'local-wins' | 'report-only' = 'manual'
450
477
  ): Promise<void> {
478
+ if (strategy === 'manual') {
479
+ console.log(` ⚠️ ${conflicts.length} conflict(s) require manual resolution.`);
480
+ for (const conflict of conflicts) {
481
+ console.log(` - ${conflict.field}: local="${conflict.localValue}" vs remote="${conflict.remoteValue}"`);
482
+ }
483
+ console.log(` Sync halted. Review conflicts and resolve manually.`);
484
+ return;
485
+ }
486
+
487
+ if (strategy === 'report-only') {
488
+ console.log(` ℹ️ ${conflicts.length} conflict(s) detected (report-only mode):`);
489
+ for (const conflict of conflicts) {
490
+ console.log(` - ${conflict.field}: local="${conflict.localValue}" vs remote="${conflict.remoteValue}"`);
491
+ }
492
+ return;
493
+ }
494
+
451
495
  for (const conflict of conflicts) {
452
- if (conflict.resolution === 'remote-wins') {
496
+ if (strategy === 'remote-wins') {
453
497
  console.log(` 🔄 Resolving: ${conflict.description} (Jira wins)`);
454
- // Update spec metadata from Jira
455
498
  if (conflict.field === 'title') {
456
499
  await this.specManager.saveMetadata(spec.metadata.id, {
457
500
  title: conflict.remoteValue
458
501
  });
459
502
  }
503
+ } else if (strategy === 'local-wins') {
504
+ console.log(` 🔄 Resolving: ${conflict.description} (local wins — no remote update)`);
505
+ // Local wins: keep spec as-is, no action needed
460
506
  }
461
507
  }
462
508
  }
463
509
 
510
+ /**
511
+ * Write conflict report JSON file for detected conflicts.
512
+ */
513
+ private async writeConflictReport(
514
+ specId: string,
515
+ conflicts: SpecSyncConflict[]
516
+ ): Promise<void> {
517
+ try {
518
+ const report = {
519
+ specId,
520
+ provider: 'jira',
521
+ timestamp: new Date().toISOString(),
522
+ conflicts: conflicts.map((c) => ({
523
+ field: c.field,
524
+ localValue: c.localValue,
525
+ remoteValue: c.remoteValue,
526
+ description: c.description,
527
+ })),
528
+ };
529
+
530
+ const reportsDir = path.join(
531
+ (this.specManager as any).projectRoot || process.cwd(),
532
+ '.specweave',
533
+ 'reports'
534
+ );
535
+ await fs.mkdir(reportsDir, { recursive: true });
536
+ const reportPath = path.join(reportsDir, 'conflict-report.json');
537
+ await fs.writeFile(reportPath, JSON.stringify(report, null, 2));
538
+ console.log(` 📄 Conflict report written to ${reportPath}`);
539
+ } catch (err) {
540
+ console.warn(' ⚠️ Failed to write conflict report:', (err as Error).message);
541
+ }
542
+ }
543
+
464
544
  /**
465
545
  * Fetch Jira Epic details
466
546
  */
@@ -484,15 +564,12 @@ ${acList}
484
564
  private async findStoryByTitle(usId: string): Promise<JiraStory | null> {
485
565
  const jql = `project = ${this.config.projectKey} AND summary ~ "[${usId}]" AND issuetype = Story`;
486
566
 
487
- const response = await this.client.get('/search', {
488
- params: {
489
- jql,
490
- maxResults: 1,
491
- fields: 'summary,description,status,labels'
492
- }
567
+ const issues = await searchAllIssues(this.client, {
568
+ jql,
569
+ fields: 'summary,description,status,labels',
570
+ maxResults: 1,
493
571
  });
494
572
 
495
- const issues = response.data.issues;
496
573
  return issues.length > 0 ? {
497
574
  id: issues[0].id,
498
575
  key: issues[0].key,
@@ -517,36 +594,37 @@ ${acList}
517
594
  // Determine issue type (supports Bug for bug-type stories)
518
595
  const issueType = this.mapTypeToJira(story.type, 'Story');
519
596
 
520
- const payload: {
521
- fields: {
522
- project: { key: string };
523
- summary: string;
524
- description: string;
525
- issuetype: { name: string };
526
- labels: string[];
527
- customfield_10014: string;
528
- priority?: { name: string };
529
- };
530
- } = {
531
- fields: {
532
- project: {
533
- key: this.config.projectKey
534
- },
535
- summary: story.summary,
536
- description: story.description,
537
- issuetype: {
538
- name: issueType
539
- },
540
- labels: story.labels,
541
- // Link to epic (field name may vary by Jira configuration)
542
- customfield_10014: story.epicLink, // Epic Link field (adjust if needed)
543
- // Set native JIRA priority field
544
- priority: {
545
- name: this.mapPriorityToJira(story.priority)
546
- }
597
+ // Discover epic link field dynamically based on project style
598
+ const { field: epicField, style } = await getEpicLinkFieldForProject(
599
+ this.config.domain,
600
+ this.config.projectKey,
601
+ { email: this.config.email, apiToken: this.config.apiToken }
602
+ );
603
+
604
+ const fields: any = {
605
+ project: {
606
+ key: this.config.projectKey
607
+ },
608
+ summary: story.summary,
609
+ description: story.description,
610
+ issuetype: {
611
+ name: issueType
612
+ },
613
+ labels: story.labels,
614
+ priority: {
615
+ name: this.mapPriorityToJira(story.priority)
547
616
  }
548
617
  };
549
618
 
619
+ // Link to epic using the correct field for project style
620
+ if (style === 'next-gen') {
621
+ fields.parent = { key: story.epicLink };
622
+ } else {
623
+ fields[epicField] = story.epicLink;
624
+ }
625
+
626
+ const payload = { fields };
627
+
550
628
  const response = await this.client.post('/issue', payload);
551
629
  const storyData = response.data;
552
630
 
@@ -1,10 +1,12 @@
1
1
  import axios from "axios";
2
+ import { detectDeploymentType, getApiBaseUrl } from "./jira-deployment-detector.js";
3
+ import { toCommentBody } from "./content-format-adapter.js";
2
4
  class JiraStatusSync {
3
5
  constructor(domain, email, apiToken, projectKey) {
4
6
  this.domain = domain;
5
7
  this.projectKey = projectKey;
6
8
  this.client = axios.create({
7
- baseURL: `https://${domain}/rest/api/3`,
9
+ baseURL: getApiBaseUrl(domain),
8
10
  auth: {
9
11
  username: email,
10
12
  password: apiToken
@@ -15,6 +17,16 @@ class JiraStatusSync {
15
17
  }
16
18
  });
17
19
  }
20
+ /**
21
+ * Initialize: detect deployment type and update client baseURL
22
+ */
23
+ async init() {
24
+ const deployment = await detectDeploymentType(this.domain, {
25
+ email: this.client.defaults.auth?.username || "",
26
+ apiToken: this.client.defaults.auth?.password || ""
27
+ });
28
+ this.client.defaults.baseURL = deployment.baseUrl;
29
+ }
18
30
  /**
19
31
  * Get current status from JIRA issue
20
32
  *
@@ -67,14 +79,15 @@ class JiraStatusSync {
67
79
  * @param newStatus - New SpecWeave status
68
80
  */
69
81
  async postStatusComment(issueKey, oldStatus, newStatus) {
70
- const body = `\u{1F504} *Status Update*
82
+ const rawBody = `*Status Update*
71
83
 
72
84
  SpecWeave status changed:
73
- \u2022 *From*: ${oldStatus}
74
- \u2022 *To*: ${newStatus}
75
- \u2022 *When*: ${(/* @__PURE__ */ new Date()).toISOString()}
85
+ * *From*: ${oldStatus}
86
+ * *To*: ${newStatus}
87
+ * *When*: ${(/* @__PURE__ */ new Date()).toISOString()}
76
88
 
77
89
  _Synced from SpecWeave_`;
90
+ const body = toCommentBody(rawBody, this.domain);
78
91
  await this.client.post(`/issue/${issueKey}/comment`, {
79
92
  body
80
93
  });
@@ -12,6 +12,8 @@
12
12
  */
13
13
 
14
14
  import axios, { AxiosInstance } from 'axios';
15
+ import { detectDeploymentType, getApiBaseUrl } from './jira-deployment-detector.js';
16
+ import { toCommentBody } from './content-format-adapter.js';
15
17
 
16
18
  /**
17
19
  * External status representation (JIRA-specific)
@@ -51,9 +53,9 @@ export class JiraStatusSync {
51
53
  this.domain = domain;
52
54
  this.projectKey = projectKey;
53
55
 
54
- // Create JIRA API client
56
+ // Create JIRA API client — baseURL set dynamically via init()
55
57
  this.client = axios.create({
56
- baseURL: `https://${domain}/rest/api/3`,
58
+ baseURL: getApiBaseUrl(domain),
57
59
  auth: {
58
60
  username: email,
59
61
  password: apiToken
@@ -65,6 +67,17 @@ export class JiraStatusSync {
65
67
  });
66
68
  }
67
69
 
70
+ /**
71
+ * Initialize: detect deployment type and update client baseURL
72
+ */
73
+ async init(): Promise<void> {
74
+ const deployment = await detectDeploymentType(this.domain, {
75
+ email: this.client.defaults.auth?.username || '',
76
+ apiToken: this.client.defaults.auth?.password || '',
77
+ });
78
+ this.client.defaults.baseURL = deployment.baseUrl;
79
+ }
80
+
68
81
  /**
69
82
  * Get current status from JIRA issue
70
83
  *
@@ -134,13 +147,15 @@ export class JiraStatusSync {
134
147
  oldStatus: string,
135
148
  newStatus: string
136
149
  ): Promise<void> {
137
- const body = `🔄 *Status Update*\n\n` +
150
+ const rawBody = `*Status Update*\n\n` +
138
151
  `SpecWeave status changed:\n` +
139
- `• *From*: ${oldStatus}\n` +
140
- `• *To*: ${newStatus}\n` +
141
- `• *When*: ${new Date().toISOString()}\n\n` +
152
+ `* *From*: ${oldStatus}\n` +
153
+ `* *To*: ${newStatus}\n` +
154
+ `* *When*: ${new Date().toISOString()}\n\n` +
142
155
  `_Synced from SpecWeave_`;
143
156
 
157
+ const body = toCommentBody(rawBody, this.domain);
158
+
144
159
  await this.client.post(`/issue/${issueKey}/comment`, {
145
160
  body
146
161
  });
@@ -0,0 +1,38 @@
1
+ const CANONICAL_JIRA_KEY_PATH = "external_sync.jira.issueKey";
2
+ function readIssueKey(metadata) {
3
+ if (!metadata) return null;
4
+ const canonical = metadata?.external_sync?.jira?.issueKey;
5
+ if (canonical) return canonical;
6
+ const legacyIssue = metadata?.jira?.issue;
7
+ if (legacyIssue) return legacyIssue;
8
+ const legacyKey = metadata?.jira?.issueKey;
9
+ if (legacyKey) return legacyKey;
10
+ return null;
11
+ }
12
+ function writeIssueKey(metadata, key) {
13
+ if (!metadata) metadata = {};
14
+ if (!metadata.external_sync) metadata.external_sync = {};
15
+ if (!metadata.external_sync.jira) metadata.external_sync.jira = {};
16
+ metadata.external_sync.jira.issueKey = key;
17
+ return metadata;
18
+ }
19
+ function migrateToCanonical(metadata, cleanup = false) {
20
+ if (!metadata) return metadata;
21
+ const existingKey = readIssueKey(metadata);
22
+ if (!existingKey) return metadata;
23
+ writeIssueKey(metadata, existingKey);
24
+ if (cleanup) {
25
+ if (metadata.jira?.issue) delete metadata.jira.issue;
26
+ if (metadata.jira?.issueKey) delete metadata.jira.issueKey;
27
+ if (metadata.jira && Object.keys(metadata.jira).length === 0) {
28
+ delete metadata.jira;
29
+ }
30
+ }
31
+ return metadata;
32
+ }
33
+ export {
34
+ CANONICAL_JIRA_KEY_PATH,
35
+ migrateToCanonical,
36
+ readIssueKey,
37
+ writeIssueKey
38
+ };