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
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Canonical Metadata Paths for JIRA Issue Keys
3
+ *
4
+ * Single source of truth for reading/writing JIRA issue keys in metadata.
5
+ * All plugin files MUST use these helpers instead of direct property access.
6
+ *
7
+ * Canonical path: external_sync.jira.issueKey
8
+ * Legacy paths (read-only fallback): jira.issue, jira.issueKey
9
+ *
10
+ * @module metadata-paths
11
+ */
12
+
13
+ /** Canonical dot-path for JIRA issue key in metadata */
14
+ export const CANONICAL_JIRA_KEY_PATH = 'external_sync.jira.issueKey';
15
+
16
+ /**
17
+ * Read JIRA issue key from metadata, checking canonical path first
18
+ * then falling back to legacy paths for backward compatibility.
19
+ */
20
+ export function readIssueKey(metadata: any): string | null {
21
+ if (!metadata) return null;
22
+
23
+ // Canonical path (preferred)
24
+ const canonical = metadata?.external_sync?.jira?.issueKey;
25
+ if (canonical) return canonical;
26
+
27
+ // Legacy path: .jira.issue
28
+ const legacyIssue = metadata?.jira?.issue;
29
+ if (legacyIssue) return legacyIssue;
30
+
31
+ // Legacy path: .jira.issueKey
32
+ const legacyKey = metadata?.jira?.issueKey;
33
+ if (legacyKey) return legacyKey;
34
+
35
+ return null;
36
+ }
37
+
38
+ /**
39
+ * Write JIRA issue key to the canonical metadata path.
40
+ * Creates intermediate objects if needed.
41
+ */
42
+ export function writeIssueKey(metadata: any, key: string): any {
43
+ if (!metadata) metadata = {};
44
+ if (!metadata.external_sync) metadata.external_sync = {};
45
+ if (!metadata.external_sync.jira) metadata.external_sync.jira = {};
46
+ metadata.external_sync.jira.issueKey = key;
47
+ return metadata;
48
+ }
49
+
50
+ /**
51
+ * Migrate legacy metadata paths to canonical path.
52
+ * Reads from legacy, writes to canonical, optionally cleans up legacy keys.
53
+ */
54
+ export function migrateToCanonical(metadata: any, cleanup: boolean = false): any {
55
+ if (!metadata) return metadata;
56
+
57
+ const existingKey = readIssueKey(metadata);
58
+ if (!existingKey) return metadata;
59
+
60
+ // Write to canonical
61
+ writeIssueKey(metadata, existingKey);
62
+
63
+ // Optionally remove legacy paths
64
+ if (cleanup) {
65
+ if (metadata.jira?.issue) delete metadata.jira.issue;
66
+ if (metadata.jira?.issueKey) delete metadata.jira.issueKey;
67
+ if (metadata.jira && Object.keys(metadata.jira).length === 0) {
68
+ delete metadata.jira;
69
+ }
70
+ }
71
+
72
+ return metadata;
73
+ }
@@ -1,6 +1,19 @@
1
+ import * as fs from "fs/promises";
2
+ import * as path from "path";
1
3
  class JiraReorganizationDetector {
2
4
  constructor(client) {
3
5
  this.client = client;
6
+ /** Track known parent keys for reparent detection */
7
+ this.previousParents = /* @__PURE__ */ new Map();
8
+ }
9
+ /**
10
+ * Set known parent keys from previous sync metadata.
11
+ * Call before detectReorganization() to enable accurate reparent detection.
12
+ */
13
+ setKnownParents(parents) {
14
+ for (const [key, parent] of Object.entries(parents)) {
15
+ this.previousParents.set(key, parent);
16
+ }
4
17
  }
5
18
  /**
6
19
  * Detect all reorganization events for tracked issues
@@ -117,22 +130,26 @@ class JiraReorganizationDetector {
117
130
  return null;
118
131
  }
119
132
  /**
120
- * Detect if issue was moved to different epic
133
+ * Detect if issue was moved to different epic.
134
+ *
135
+ * Requires previousParents map to track known parent keys.
136
+ * Only fires REPARENTED when the parent actually changed.
121
137
  */
122
138
  detectReparent(originalKey, issue, lastSyncTimestamp) {
123
- const currentParent = issue.fields.parent?.key;
124
- if (currentParent && lastSyncTimestamp) {
125
- const updatedAt = new Date(issue.fields.updated);
126
- const lastSync = new Date(lastSyncTimestamp);
127
- if (updatedAt > lastSync) {
128
- return {
129
- type: "REPARENTED",
130
- timestamp: issue.fields.updated,
131
- description: `Issue ${originalKey} may have been reparented to ${currentParent}`,
132
- originalKeys: [originalKey],
133
- toEpic: currentParent
134
- };
135
- }
139
+ const currentParent = issue.fields.parent?.key || null;
140
+ const previousParent = this.previousParents.get(originalKey) || null;
141
+ if (previousParent !== null && currentParent !== previousParent) {
142
+ return {
143
+ type: "REPARENTED",
144
+ timestamp: issue.fields.updated,
145
+ description: `Issue ${originalKey} reparented from ${previousParent} to ${currentParent || "none"}`,
146
+ originalKeys: [originalKey],
147
+ fromEpic: previousParent,
148
+ toEpic: currentParent || void 0
149
+ };
150
+ }
151
+ if (currentParent) {
152
+ this.previousParents.set(originalKey, currentParent);
136
153
  }
137
154
  return null;
138
155
  }
@@ -190,24 +207,85 @@ async function handleReorganization(events, incrementId, projectRoot = process.c
190
207
  console.log(`
191
208
  \u{1F527} Handling ${events.length} reorganization events...
192
209
  `);
210
+ const metadataPath = path.join(
211
+ projectRoot,
212
+ ".specweave",
213
+ "increments",
214
+ incrementId,
215
+ "metadata.json"
216
+ );
217
+ let metadata = {};
218
+ try {
219
+ metadata = JSON.parse(await fs.readFile(metadataPath, "utf-8"));
220
+ } catch {
221
+ console.warn(` \u26A0\uFE0F Could not read metadata.json for ${incrementId}`);
222
+ }
223
+ if (!metadata.reorganization) {
224
+ metadata.reorganization = { events: [], lastHandled: null };
225
+ }
193
226
  for (const event of events) {
194
227
  switch (event.type) {
195
- case "MOVED_PROJECT":
228
+ case "MOVED_PROJECT": {
229
+ if (event.newKeys?.[0]) {
230
+ if (!metadata.external_sync) metadata.external_sync = {};
231
+ if (!metadata.external_sync.jira) metadata.external_sync.jira = {};
232
+ metadata.external_sync.jira.issueKey = event.newKeys[0];
233
+ metadata.external_sync.jira.project = event.toProject;
234
+ }
196
235
  console.log(` \u2713 Updated project mapping: ${event.fromProject} \u2192 ${event.toProject}`);
197
236
  break;
198
- case "SPLIT":
199
- console.log(` \u2713 Added new story from split: ${event.newKeys?.join(", ")}`);
237
+ }
238
+ case "SPLIT": {
239
+ if (!metadata.external_sync?.jira?.relatedKeys) {
240
+ if (!metadata.external_sync) metadata.external_sync = {};
241
+ if (!metadata.external_sync.jira) metadata.external_sync.jira = {};
242
+ metadata.external_sync.jira.relatedKeys = [];
243
+ }
244
+ if (event.newKeys) {
245
+ metadata.external_sync.jira.relatedKeys.push(...event.newKeys);
246
+ }
247
+ console.log(` \u2713 Recorded split: ${event.newKeys?.join(", ")}`);
200
248
  break;
201
- case "MERGED":
202
- console.log(` \u2713 Marked story as merged: ${event.originalKeys[0]} \u2192 ${event.newKeys?.[0]}`);
249
+ }
250
+ case "MERGED": {
251
+ if (event.newKeys?.[0]) {
252
+ if (!metadata.external_sync) metadata.external_sync = {};
253
+ if (!metadata.external_sync.jira) metadata.external_sync.jira = {};
254
+ metadata.external_sync.jira.issueKey = event.newKeys[0];
255
+ metadata.external_sync.jira.mergedFrom = event.originalKeys[0];
256
+ }
257
+ console.log(` \u2713 Updated merged issue: ${event.originalKeys[0]} \u2192 ${event.newKeys?.[0]}`);
203
258
  break;
204
- case "REPARENTED":
205
- console.log(` \u2713 Updated epic link: ${event.toEpic}`);
259
+ }
260
+ case "REPARENTED": {
261
+ if (!metadata.external_sync) metadata.external_sync = {};
262
+ if (!metadata.external_sync.jira) metadata.external_sync.jira = {};
263
+ metadata.external_sync.jira.epicKey = event.toEpic || null;
264
+ metadata.external_sync.jira.previousEpicKey = event.fromEpic || null;
265
+ console.log(` \u2713 Updated epic link: ${event.fromEpic || "none"} \u2192 ${event.toEpic || "none"}`);
206
266
  break;
207
- case "DELETED":
208
- console.log(` \u26A0\uFE0F Story deleted from Jira: ${event.originalKeys[0]}`);
267
+ }
268
+ case "DELETED": {
269
+ if (!metadata.external_sync) metadata.external_sync = {};
270
+ if (!metadata.external_sync.jira) metadata.external_sync.jira = {};
271
+ metadata.external_sync.jira.deleted = true;
272
+ metadata.external_sync.jira.deletedAt = event.timestamp;
273
+ console.log(` \u26A0\uFE0F Marked as deleted: ${event.originalKeys[0]}`);
209
274
  break;
275
+ }
210
276
  }
277
+ metadata.reorganization.events.push({
278
+ type: event.type,
279
+ timestamp: event.timestamp,
280
+ description: event.description
281
+ });
282
+ }
283
+ metadata.reorganization.lastHandled = (/* @__PURE__ */ new Date()).toISOString();
284
+ try {
285
+ await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
286
+ console.log(` \u{1F4C4} Updated metadata.json for ${incrementId}`);
287
+ } catch (err) {
288
+ console.warn(` \u26A0\uFE0F Failed to write metadata: ${err.message}`);
211
289
  }
212
290
  console.log("\n\u2705 Reorganization handled\n");
213
291
  }
@@ -12,6 +12,8 @@
12
12
  */
13
13
 
14
14
  import { JiraClient, JiraIssue } from '../../../src/integrations/jira/jira-client.js';
15
+ import * as fs from 'fs/promises';
16
+ import * as path from 'path';
15
17
 
16
18
  // ============================================================================
17
19
  // Types
@@ -54,8 +56,21 @@ export interface ReorganizationDetectionResult {
54
56
  // ============================================================================
55
57
 
56
58
  export class JiraReorganizationDetector {
59
+ /** Track known parent keys for reparent detection */
60
+ private previousParents = new Map<string, string>();
61
+
57
62
  constructor(private client: JiraClient) {}
58
63
 
64
+ /**
65
+ * Set known parent keys from previous sync metadata.
66
+ * Call before detectReorganization() to enable accurate reparent detection.
67
+ */
68
+ setKnownParents(parents: Record<string, string>): void {
69
+ for (const [key, parent] of Object.entries(parents)) {
70
+ this.previousParents.set(key, parent);
71
+ }
72
+ }
73
+
59
74
  /**
60
75
  * Detect all reorganization events for tracked issues
61
76
  */
@@ -220,32 +235,34 @@ export class JiraReorganizationDetector {
220
235
  }
221
236
 
222
237
  /**
223
- * Detect if issue was moved to different epic
238
+ * Detect if issue was moved to different epic.
239
+ *
240
+ * Requires previousParents map to track known parent keys.
241
+ * Only fires REPARENTED when the parent actually changed.
224
242
  */
225
243
  private detectReparent(
226
244
  originalKey: string,
227
245
  issue: JiraIssue,
228
246
  lastSyncTimestamp?: string
229
247
  ): ReorganizationEvent | null {
230
- // Check if issue has parent (epic)
231
- const currentParent = issue.fields.parent?.key;
232
-
233
- // We need to track previous parent from metadata
234
- // For now, just detect if parent exists and was recently updated
235
- if (currentParent && lastSyncTimestamp) {
236
- const updatedAt = new Date(issue.fields.updated);
237
- const lastSync = new Date(lastSyncTimestamp);
238
-
239
- if (updatedAt > lastSync) {
240
- // Parent might have changed (we'd need to store previous parent to be sure)
241
- return {
242
- type: 'REPARENTED',
243
- timestamp: issue.fields.updated,
244
- description: `Issue ${originalKey} may have been reparented to ${currentParent}`,
245
- originalKeys: [originalKey],
246
- toEpic: currentParent,
247
- };
248
- }
248
+ const currentParent = issue.fields.parent?.key || null;
249
+ const previousParent = this.previousParents.get(originalKey) || null;
250
+
251
+ // Only fire REPARENTED if we know the previous parent AND it differs
252
+ if (previousParent !== null && currentParent !== previousParent) {
253
+ return {
254
+ type: 'REPARENTED',
255
+ timestamp: issue.fields.updated,
256
+ description: `Issue ${originalKey} reparented from ${previousParent} to ${currentParent || 'none'}`,
257
+ originalKeys: [originalKey],
258
+ fromEpic: previousParent,
259
+ toEpic: currentParent || undefined,
260
+ };
261
+ }
262
+
263
+ // Update tracked parent for future comparisons
264
+ if (currentParent) {
265
+ this.previousParents.set(originalKey, currentParent);
249
266
  }
250
267
 
251
268
  return null;
@@ -313,7 +330,10 @@ export class JiraReorganizationDetector {
313
330
  // ============================================================================
314
331
 
315
332
  /**
316
- * Handle reorganization events by updating SpecWeave increment
333
+ * Handle reorganization events by updating SpecWeave increment metadata.
334
+ *
335
+ * For each event type, updates local metadata.json to reflect
336
+ * the reorganization that happened in JIRA.
317
337
  */
318
338
  export async function handleReorganization(
319
339
  events: ReorganizationEvent[],
@@ -326,33 +346,103 @@ export async function handleReorganization(
326
346
 
327
347
  console.log(`\n🔧 Handling ${events.length} reorganization events...\n`);
328
348
 
349
+ const metadataPath = path.join(
350
+ projectRoot,
351
+ '.specweave',
352
+ 'increments',
353
+ incrementId,
354
+ 'metadata.json'
355
+ );
356
+
357
+ let metadata: any = {};
358
+ try {
359
+ metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
360
+ } catch {
361
+ console.warn(` ⚠️ Could not read metadata.json for ${incrementId}`);
362
+ }
363
+
364
+ // Ensure reorganization log exists
365
+ if (!metadata.reorganization) {
366
+ metadata.reorganization = { events: [], lastHandled: null };
367
+ }
368
+
329
369
  for (const event of events) {
330
370
  switch (event.type) {
331
- case 'MOVED_PROJECT':
371
+ case 'MOVED_PROJECT': {
372
+ // Update tracked project key in metadata
373
+ if (event.newKeys?.[0]) {
374
+ if (!metadata.external_sync) metadata.external_sync = {};
375
+ if (!metadata.external_sync.jira) metadata.external_sync.jira = {};
376
+ metadata.external_sync.jira.issueKey = event.newKeys[0];
377
+ metadata.external_sync.jira.project = event.toProject;
378
+ }
332
379
  console.log(` ✓ Updated project mapping: ${event.fromProject} → ${event.toProject}`);
333
- // Update metadata with new project/key
334
380
  break;
381
+ }
335
382
 
336
- case 'SPLIT':
337
- console.log(` ✓ Added new story from split: ${event.newKeys?.join(', ')}`);
338
- // Add new user story to spec.md
383
+ case 'SPLIT': {
384
+ // Record split — new keys added as related issues
385
+ if (!metadata.external_sync?.jira?.relatedKeys) {
386
+ if (!metadata.external_sync) metadata.external_sync = {};
387
+ if (!metadata.external_sync.jira) metadata.external_sync.jira = {};
388
+ metadata.external_sync.jira.relatedKeys = [];
389
+ }
390
+ if (event.newKeys) {
391
+ metadata.external_sync.jira.relatedKeys.push(...event.newKeys);
392
+ }
393
+ console.log(` ✓ Recorded split: ${event.newKeys?.join(', ')}`);
339
394
  break;
395
+ }
340
396
 
341
- case 'MERGED':
342
- console.log(` ✓ Marked story as merged: ${event.originalKeys[0]} ${event.newKeys?.[0]}`);
343
- // Update spec.md to mark as merged
397
+ case 'MERGED': {
398
+ // Update issue key to the merge target
399
+ if (event.newKeys?.[0]) {
400
+ if (!metadata.external_sync) metadata.external_sync = {};
401
+ if (!metadata.external_sync.jira) metadata.external_sync.jira = {};
402
+ metadata.external_sync.jira.issueKey = event.newKeys[0];
403
+ metadata.external_sync.jira.mergedFrom = event.originalKeys[0];
404
+ }
405
+ console.log(` ✓ Updated merged issue: ${event.originalKeys[0]} → ${event.newKeys?.[0]}`);
344
406
  break;
407
+ }
345
408
 
346
- case 'REPARENTED':
347
- console.log(` ✓ Updated epic link: ${event.toEpic}`);
348
- // Update metadata
409
+ case 'REPARENTED': {
410
+ // Update parent epic key in metadata
411
+ if (!metadata.external_sync) metadata.external_sync = {};
412
+ if (!metadata.external_sync.jira) metadata.external_sync.jira = {};
413
+ metadata.external_sync.jira.epicKey = event.toEpic || null;
414
+ metadata.external_sync.jira.previousEpicKey = event.fromEpic || null;
415
+ console.log(` ✓ Updated epic link: ${event.fromEpic || 'none'} → ${event.toEpic || 'none'}`);
349
416
  break;
417
+ }
350
418
 
351
- case 'DELETED':
352
- console.log(` ⚠️ Story deleted from Jira: ${event.originalKeys[0]}`);
353
- // Mark as deleted in spec.md (don't remove, just mark)
419
+ case 'DELETED': {
420
+ // Mark issue as deleted (don't remove metadata, just flag)
421
+ if (!metadata.external_sync) metadata.external_sync = {};
422
+ if (!metadata.external_sync.jira) metadata.external_sync.jira = {};
423
+ metadata.external_sync.jira.deleted = true;
424
+ metadata.external_sync.jira.deletedAt = event.timestamp;
425
+ console.log(` ⚠️ Marked as deleted: ${event.originalKeys[0]}`);
354
426
  break;
427
+ }
355
428
  }
429
+
430
+ // Log event for audit trail
431
+ metadata.reorganization.events.push({
432
+ type: event.type,
433
+ timestamp: event.timestamp,
434
+ description: event.description,
435
+ });
436
+ }
437
+
438
+ metadata.reorganization.lastHandled = new Date().toISOString();
439
+
440
+ // Write updated metadata
441
+ try {
442
+ await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
443
+ console.log(` 📄 Updated metadata.json for ${incrementId}`);
444
+ } catch (err) {
445
+ console.warn(` ⚠️ Failed to write metadata: ${(err as Error).message}`);
356
446
  }
357
447
 
358
448
  console.log('\n✅ Reorganization handled\n');
@@ -17,7 +17,7 @@ async function refreshJiraCache(projectRoot = process.cwd()) {
17
17
  }
18
18
  console.log(`\u2705 Cleared ${cleared} cache files`);
19
19
  }
20
- if (require.main === module) {
20
+ if (import.meta.url === `file://${process.argv[1]}`) {
21
21
  refreshJiraCache().catch(console.error);
22
22
  }
23
23
  export {
@@ -34,7 +34,7 @@ export async function refreshJiraCache(projectRoot: string = process.cwd()): Pro
34
34
  console.log(`✅ Cleared ${cleared} cache files`);
35
35
  }
36
36
 
37
- // CLI entry
38
- if (require.main === module) {
37
+ // CLI entry (ESM-compatible)
38
+ if (import.meta.url === `file://${process.argv[1]}`) {
39
39
  refreshJiraCache().catch(console.error);
40
40
  }
@@ -59,11 +59,9 @@ if [[ ! "$JIRA_DOMAIN" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9](
59
59
  exit 1
60
60
  fi
61
61
 
62
- # Cloud JIRA: must match <subdomain>.atlassian.net
63
- if [[ ! "$JIRA_DOMAIN" =~ ^[a-zA-Z0-9-]+\.atlassian\.net$ ]]; then
64
- echo "Error: Domain does not match <subdomain>.atlassian.net pattern"
65
- exit 1
66
- fi
62
+ # JIRA Cloud and Server/DC are both supported.
63
+ # Cloud domains match <subdomain>.atlassian.net; self-hosted use custom domains.
64
+ # No hard-block deployment type is auto-detected at runtime.
67
65
  ```
68
66
 
69
67
  ### API Call Pattern (HTTPS only, quoted variables)