specweave 0.32.2 → 0.32.5

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 (244) hide show
  1. package/CLAUDE.md +51 -9
  2. package/bin/specweave.js +34 -0
  3. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.d.ts +100 -0
  4. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.d.ts.map +1 -0
  5. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js +291 -0
  6. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js.map +1 -0
  7. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.d.ts +103 -0
  8. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.d.ts.map +1 -0
  9. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js +310 -0
  10. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js.map +1 -0
  11. package/dist/plugins/specweave-jira/lib/jira-permission-gate.d.ts +126 -0
  12. package/dist/plugins/specweave-jira/lib/jira-permission-gate.d.ts.map +1 -0
  13. package/dist/plugins/specweave-jira/lib/jira-permission-gate.js +207 -0
  14. package/dist/plugins/specweave-jira/lib/jira-permission-gate.js.map +1 -0
  15. package/dist/src/adapters/codex/README.md +1 -1
  16. package/dist/src/adapters/codex/adapter.js +1 -1
  17. package/dist/src/cli/commands/archive.d.ts +2 -0
  18. package/dist/src/cli/commands/archive.d.ts.map +1 -1
  19. package/dist/src/cli/commands/archive.js +33 -0
  20. package/dist/src/cli/commands/archive.js.map +1 -1
  21. package/dist/src/cli/commands/context.d.ts +92 -0
  22. package/dist/src/cli/commands/context.d.ts.map +1 -0
  23. package/dist/src/cli/commands/context.js +205 -0
  24. package/dist/src/cli/commands/context.js.map +1 -0
  25. package/dist/src/cli/commands/init-multiproject.js +1 -1
  26. package/dist/src/cli/commands/init-multiproject.js.map +1 -1
  27. package/dist/src/cli/commands/init.d.ts.map +1 -1
  28. package/dist/src/cli/commands/init.js +111 -69
  29. package/dist/src/cli/commands/init.js.map +1 -1
  30. package/dist/src/cli/commands/migrate-to-multiproject.js +2 -2
  31. package/dist/src/cli/commands/migrate-to-multiproject.js.map +1 -1
  32. package/dist/src/cli/helpers/init/external-import.d.ts +3 -0
  33. package/dist/src/cli/helpers/init/external-import.d.ts.map +1 -1
  34. package/dist/src/cli/helpers/init/external-import.js +17 -4
  35. package/dist/src/cli/helpers/init/external-import.js.map +1 -1
  36. package/dist/src/cli/helpers/init/index.d.ts +1 -0
  37. package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
  38. package/dist/src/cli/helpers/init/index.js +2 -0
  39. package/dist/src/cli/helpers/init/index.js.map +1 -1
  40. package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts +70 -0
  41. package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts.map +1 -1
  42. package/dist/src/cli/helpers/init/jira-ado-auto-detect.js +214 -4
  43. package/dist/src/cli/helpers/init/jira-ado-auto-detect.js.map +1 -1
  44. package/dist/src/cli/helpers/init/living-docs-preflight.d.ts +4 -0
  45. package/dist/src/cli/helpers/init/living-docs-preflight.d.ts.map +1 -1
  46. package/dist/src/cli/helpers/init/living-docs-preflight.js +34 -3
  47. package/dist/src/cli/helpers/init/living-docs-preflight.js.map +1 -1
  48. package/dist/src/cli/helpers/init/testing-config.d.ts +3 -0
  49. package/dist/src/cli/helpers/init/testing-config.d.ts.map +1 -1
  50. package/dist/src/cli/helpers/init/testing-config.js +9 -2
  51. package/dist/src/cli/helpers/init/testing-config.js.map +1 -1
  52. package/dist/src/cli/helpers/init/translation-config.d.ts +3 -0
  53. package/dist/src/cli/helpers/init/translation-config.d.ts.map +1 -1
  54. package/dist/src/cli/helpers/init/translation-config.js +21 -4
  55. package/dist/src/cli/helpers/init/translation-config.js.map +1 -1
  56. package/dist/src/cli/helpers/init/wizard-navigation.d.ts +45 -0
  57. package/dist/src/cli/helpers/init/wizard-navigation.d.ts.map +1 -0
  58. package/dist/src/cli/helpers/init/wizard-navigation.js +97 -0
  59. package/dist/src/cli/helpers/init/wizard-navigation.js.map +1 -0
  60. package/dist/src/core/increment/increment-archiver.d.ts +25 -4
  61. package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
  62. package/dist/src/core/increment/increment-archiver.js +64 -20
  63. package/dist/src/core/increment/increment-archiver.js.map +1 -1
  64. package/dist/src/core/increment/increment-utils.d.ts +65 -0
  65. package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
  66. package/dist/src/core/increment/increment-utils.js +114 -0
  67. package/dist/src/core/increment/increment-utils.js.map +1 -1
  68. package/dist/src/core/living-docs/cross-project-sync.d.ts +97 -0
  69. package/dist/src/core/living-docs/cross-project-sync.d.ts.map +1 -0
  70. package/dist/src/core/living-docs/cross-project-sync.js +135 -0
  71. package/dist/src/core/living-docs/cross-project-sync.js.map +1 -0
  72. package/dist/src/core/living-docs/external-sync-orchestrator.d.ts +106 -0
  73. package/dist/src/core/living-docs/external-sync-orchestrator.d.ts.map +1 -0
  74. package/dist/src/core/living-docs/external-sync-orchestrator.js +146 -0
  75. package/dist/src/core/living-docs/external-sync-orchestrator.js.map +1 -0
  76. package/dist/src/core/living-docs/feature-archiver.d.ts +4 -0
  77. package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -1
  78. package/dist/src/core/living-docs/feature-archiver.js +32 -10
  79. package/dist/src/core/living-docs/feature-archiver.js.map +1 -1
  80. package/dist/src/core/living-docs/feature-id-manager.d.ts.map +1 -1
  81. package/dist/src/core/living-docs/feature-id-manager.js +7 -3
  82. package/dist/src/core/living-docs/feature-id-manager.js.map +1 -1
  83. package/dist/src/core/living-docs/governance/ecosystem-detector.d.ts +38 -0
  84. package/dist/src/core/living-docs/governance/ecosystem-detector.d.ts.map +1 -0
  85. package/dist/src/core/living-docs/governance/ecosystem-detector.js +325 -0
  86. package/dist/src/core/living-docs/governance/ecosystem-detector.js.map +1 -0
  87. package/dist/src/core/living-docs/governance/frontend-standards-parser.d.ts +74 -0
  88. package/dist/src/core/living-docs/governance/frontend-standards-parser.d.ts.map +1 -0
  89. package/dist/src/core/living-docs/governance/frontend-standards-parser.js +366 -0
  90. package/dist/src/core/living-docs/governance/frontend-standards-parser.js.map +1 -0
  91. package/dist/src/core/living-docs/governance/go-standards-parser.d.ts +64 -0
  92. package/dist/src/core/living-docs/governance/go-standards-parser.d.ts.map +1 -0
  93. package/dist/src/core/living-docs/governance/go-standards-parser.js +229 -0
  94. package/dist/src/core/living-docs/governance/go-standards-parser.js.map +1 -0
  95. package/dist/src/core/living-docs/governance/index.d.ts +50 -0
  96. package/dist/src/core/living-docs/governance/index.d.ts.map +1 -0
  97. package/dist/src/core/living-docs/governance/index.js +56 -0
  98. package/dist/src/core/living-docs/governance/index.js.map +1 -0
  99. package/dist/src/core/living-docs/governance/java-standards-parser.d.ts +89 -0
  100. package/dist/src/core/living-docs/governance/java-standards-parser.d.ts.map +1 -0
  101. package/dist/src/core/living-docs/governance/java-standards-parser.js +356 -0
  102. package/dist/src/core/living-docs/governance/java-standards-parser.js.map +1 -0
  103. package/dist/src/core/living-docs/governance/python-standards-parser.d.ts +83 -0
  104. package/dist/src/core/living-docs/governance/python-standards-parser.d.ts.map +1 -0
  105. package/dist/src/core/living-docs/governance/python-standards-parser.js +347 -0
  106. package/dist/src/core/living-docs/governance/python-standards-parser.js.map +1 -0
  107. package/dist/src/core/living-docs/governance/standards-generator.d.ts +38 -0
  108. package/dist/src/core/living-docs/governance/standards-generator.d.ts.map +1 -0
  109. package/dist/src/core/living-docs/governance/standards-generator.js +476 -0
  110. package/dist/src/core/living-docs/governance/standards-generator.js.map +1 -0
  111. package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.d.ts.map +1 -1
  112. package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js +54 -2
  113. package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js.map +1 -1
  114. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts +5 -1
  115. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts.map +1 -1
  116. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js +358 -30
  117. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js.map +1 -1
  118. package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts +44 -0
  119. package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts.map +1 -1
  120. package/dist/src/core/living-docs/living-docs-sync.d.ts +7 -3
  121. package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
  122. package/dist/src/core/living-docs/living-docs-sync.js +94 -10
  123. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  124. package/dist/src/core/living-docs/module-analyzer.d.ts +22 -0
  125. package/dist/src/core/living-docs/module-analyzer.d.ts.map +1 -1
  126. package/dist/src/core/living-docs/module-analyzer.js +123 -19
  127. package/dist/src/core/living-docs/module-analyzer.js.map +1 -1
  128. package/dist/src/core/living-docs/sync-helpers/generators.d.ts +8 -1
  129. package/dist/src/core/living-docs/sync-helpers/generators.d.ts.map +1 -1
  130. package/dist/src/core/living-docs/sync-helpers/generators.js +18 -1
  131. package/dist/src/core/living-docs/sync-helpers/generators.js.map +1 -1
  132. package/dist/src/core/living-docs/sync-helpers/index.d.ts +1 -1
  133. package/dist/src/core/living-docs/sync-helpers/index.d.ts.map +1 -1
  134. package/dist/src/core/living-docs/sync-helpers/index.js.map +1 -1
  135. package/dist/src/core/living-docs/sync-helpers/parsers.d.ts +3 -1
  136. package/dist/src/core/living-docs/sync-helpers/parsers.d.ts.map +1 -1
  137. package/dist/src/core/living-docs/sync-helpers/parsers.js +24 -2
  138. package/dist/src/core/living-docs/sync-helpers/parsers.js.map +1 -1
  139. package/dist/src/core/living-docs/types.d.ts +6 -0
  140. package/dist/src/core/living-docs/types.d.ts.map +1 -1
  141. package/dist/src/core/living-docs/validators/index.d.ts +7 -0
  142. package/dist/src/core/living-docs/validators/index.d.ts.map +1 -0
  143. package/dist/src/core/living-docs/validators/index.js +7 -0
  144. package/dist/src/core/living-docs/validators/index.js.map +1 -0
  145. package/dist/src/core/living-docs/validators/project-validator.d.ts +92 -0
  146. package/dist/src/core/living-docs/validators/project-validator.d.ts.map +1 -0
  147. package/dist/src/core/living-docs/validators/project-validator.js +142 -0
  148. package/dist/src/core/living-docs/validators/project-validator.js.map +1 -0
  149. package/dist/src/core/llm/provider-factory.js +2 -2
  150. package/dist/src/core/llm/provider-factory.js.map +1 -1
  151. package/dist/src/core/llm/providers/anthropic-provider.js +1 -1
  152. package/dist/src/core/llm/providers/bedrock-provider.d.ts.map +1 -1
  153. package/dist/src/core/llm/providers/bedrock-provider.js +8 -4
  154. package/dist/src/core/llm/providers/bedrock-provider.js.map +1 -1
  155. package/dist/src/core/project/project-manager.d.ts.map +1 -1
  156. package/dist/src/core/project/project-manager.js +19 -17
  157. package/dist/src/core/project/project-manager.js.map +1 -1
  158. package/dist/src/core/types/config.d.ts +4 -2
  159. package/dist/src/core/types/config.d.ts.map +1 -1
  160. package/dist/src/core/types/config.js.map +1 -1
  161. package/dist/src/core/types/increment-metadata.d.ts +34 -0
  162. package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
  163. package/dist/src/importers/jira-importer.d.ts +14 -0
  164. package/dist/src/importers/jira-importer.d.ts.map +1 -1
  165. package/dist/src/importers/jira-importer.js +75 -0
  166. package/dist/src/importers/jira-importer.js.map +1 -1
  167. package/dist/src/integrations/jira/jira-token-provider.d.ts +93 -0
  168. package/dist/src/integrations/jira/jira-token-provider.d.ts.map +1 -0
  169. package/dist/src/integrations/jira/jira-token-provider.js +160 -0
  170. package/dist/src/integrations/jira/jira-token-provider.js.map +1 -0
  171. package/dist/src/sync/ado-reconciler.d.ts +92 -0
  172. package/dist/src/sync/ado-reconciler.d.ts.map +1 -0
  173. package/dist/src/sync/ado-reconciler.js +335 -0
  174. package/dist/src/sync/ado-reconciler.js.map +1 -0
  175. package/dist/src/sync/jira-reconciler.d.ts +106 -0
  176. package/dist/src/sync/jira-reconciler.d.ts.map +1 -0
  177. package/dist/src/sync/jira-reconciler.js +405 -0
  178. package/dist/src/sync/jira-reconciler.js.map +1 -0
  179. package/dist/src/types/model-selection.d.ts +6 -4
  180. package/dist/src/types/model-selection.d.ts.map +1 -1
  181. package/dist/src/types/model-selection.js +3 -1
  182. package/dist/src/types/model-selection.js.map +1 -1
  183. package/dist/src/utils/cross-cutting-detector.d.ts +66 -0
  184. package/dist/src/utils/cross-cutting-detector.d.ts.map +1 -0
  185. package/dist/src/utils/cross-cutting-detector.js +179 -0
  186. package/dist/src/utils/cross-cutting-detector.js.map +1 -0
  187. package/dist/src/utils/external-tool-drift-detector.d.ts +1 -1
  188. package/dist/src/utils/external-tool-drift-detector.d.ts.map +1 -1
  189. package/dist/src/utils/external-tool-drift-detector.js +5 -4
  190. package/dist/src/utils/external-tool-drift-detector.js.map +1 -1
  191. package/dist/src/utils/feature-id-derivation.d.ts +8 -3
  192. package/dist/src/utils/feature-id-derivation.d.ts.map +1 -1
  193. package/dist/src/utils/feature-id-derivation.js +14 -6
  194. package/dist/src/utils/feature-id-derivation.js.map +1 -1
  195. package/dist/src/utils/model-selection.d.ts +3 -4
  196. package/dist/src/utils/model-selection.d.ts.map +1 -1
  197. package/dist/src/utils/model-selection.js +3 -4
  198. package/dist/src/utils/model-selection.js.map +1 -1
  199. package/dist/src/utils/project-detection.d.ts +12 -8
  200. package/dist/src/utils/project-detection.d.ts.map +1 -1
  201. package/dist/src/utils/project-detection.js +13 -19
  202. package/dist/src/utils/project-detection.js.map +1 -1
  203. package/package.json +1 -1
  204. package/plugins/specweave/agents/code-standards-detective/AGENT.md +48 -0
  205. package/plugins/specweave/commands/specweave-costs.md +4 -4
  206. package/plugins/specweave/commands/specweave-do.md +9 -9
  207. package/plugins/specweave/commands/specweave-done.md +13 -0
  208. package/plugins/specweave/commands/specweave-status.md +64 -0
  209. package/plugins/specweave/commands/specweave-validate.md +27 -1
  210. package/plugins/specweave/hooks/hooks.json +11 -1
  211. package/plugins/specweave/hooks/spec-project-validator.sh +81 -25
  212. package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +135 -0
  213. package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +34 -0
  214. package/plugins/specweave/scripts/read-costs.sh +3 -3
  215. package/plugins/specweave/skills/code-standards-analyzer/SKILL.md +58 -6
  216. package/plugins/specweave/skills/increment-planner/SKILL.md +135 -29
  217. package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +4 -2
  218. package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +2 -1
  219. package/plugins/specweave/skills/increment-planner/templates/tasks-multi-project.md +1 -1
  220. package/plugins/specweave/skills/increment-planner/templates/tasks-single-project.md +1 -1
  221. package/plugins/specweave/skills/spec-generator/SKILL.md +78 -7
  222. package/plugins/specweave-ado/commands/cleanup-duplicates.md +212 -0
  223. package/plugins/specweave-ado/commands/reconcile.md +120 -0
  224. package/plugins/specweave-ado/lib/ado-duplicate-detector.js +279 -0
  225. package/plugins/specweave-ado/lib/ado-duplicate-detector.ts +407 -0
  226. package/plugins/specweave-github/agents/github-manager/AGENT.md +2 -2
  227. package/plugins/specweave-infrastructure/skills/hetzner-provisioner/README.md +1 -1
  228. package/plugins/specweave-jira/agents/jira-manager/AGENT.md +1 -1
  229. package/plugins/specweave-jira/agents/jira-multi-project-mapper/AGENT.md +530 -0
  230. package/plugins/specweave-jira/agents/jira-sync-judge/AGENT.md +438 -0
  231. package/plugins/specweave-jira/commands/cleanup-duplicates.md +219 -0
  232. package/plugins/specweave-jira/commands/close.md +297 -0
  233. package/plugins/specweave-jira/commands/create.md +198 -0
  234. package/plugins/specweave-jira/commands/reconcile.md +123 -0
  235. package/plugins/specweave-jira/commands/status.md +215 -0
  236. package/plugins/specweave-jira/lib/jira-duplicate-detector.js +296 -0
  237. package/plugins/specweave-jira/lib/jira-duplicate-detector.ts +434 -0
  238. package/plugins/specweave-jira/lib/jira-permission-gate.js +160 -0
  239. package/plugins/specweave-jira/lib/jira-permission-gate.ts +276 -0
  240. package/plugins/specweave-jira/lib/jira-profile-resolver.js +222 -0
  241. package/plugins/specweave-jira/lib/jira-profile-resolver.ts +427 -0
  242. package/plugins/specweave-jira/reference/jira-specweave-mapping.md +16 -11
  243. package/plugins/specweave-release/commands/specweave-release-npm.md +140 -14
  244. package/plugins/specweave/commands/specweave-switch-project.md +0 -168
@@ -0,0 +1,120 @@
1
+ ---
2
+ name: specweave-ado:reconcile
3
+ description: Reconcile Azure DevOps work item states with increment statuses. Fixes drift by closing work items for completed increments and reactivating work items for resumed increments.
4
+ ---
5
+
6
+ # Azure DevOps Status Reconciliation
7
+
8
+ Scan all increments and fix any drift between local metadata.json status and ADO work item states.
9
+
10
+ ## Usage
11
+
12
+ ```bash
13
+ /specweave-ado:reconcile [options]
14
+ ```
15
+
16
+ ## Options
17
+
18
+ - `--dry-run`: Preview changes without making them
19
+ - `--verbose`: Show detailed output for each work item checked
20
+
21
+ ## What It Does
22
+
23
+ 1. **Scans** all non-archived increments
24
+ 2. **Compares** metadata.json status with ADO work item state
25
+ 3. **Fixes** mismatches:
26
+ - `metadata=completed` + `ADO=Active` → **Close** the work item
27
+ - `metadata=in-progress` + `ADO=Closed` → **Reactivate** the work item
28
+
29
+ ## When to Use
30
+
31
+ - After manual metadata.json edits
32
+ - After git pulls that changed increment statuses
33
+ - When you notice active work items for completed work
34
+ - As a periodic health check
35
+
36
+ ## Implementation
37
+
38
+ Run the reconciliation using the AdoReconciler:
39
+
40
+ ```typescript
41
+ import { AdoReconciler } from '../../../src/sync/ado-reconciler.js';
42
+
43
+ const reconciler = new AdoReconciler({
44
+ projectRoot: process.cwd(),
45
+ dryRun: args.includes('--dry-run'),
46
+ });
47
+
48
+ const result = await reconciler.reconcile();
49
+
50
+ // Report results
51
+ console.log(`\nReconciliation complete:`);
52
+ console.log(` Scanned: ${result.scanned} increments`);
53
+ console.log(` Fixed: ${result.closed} closed, ${result.reopened} reopened`);
54
+ if (result.errors.length > 0) {
55
+ console.log(` Errors: ${result.errors.length}`);
56
+ }
57
+ ```
58
+
59
+ ## Example Output
60
+
61
+ ```
62
+ 📊 Scanning 5 increment(s) for ADO state drift...
63
+
64
+ ✅ Work Item #1234 (0056-plugin-fix/US-001): State matches (Active)
65
+ ❌ Work Item #1240 (0066-import-wizard/US-003): Active but should be CLOSED (status=completed)
66
+ ✅ Closed work item #1240
67
+ ❌ Work Item #1238 (0063-multi-repo/US-001): CLOSED but should be ACTIVE (status=in-progress)
68
+ ✅ Reactivated work item #1238
69
+
70
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
71
+ 📊 ADO RECONCILIATION SUMMARY
72
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
73
+ Increments scanned: 5
74
+ Mismatches found: 2
75
+ Work items closed: 1
76
+ Work items reopened: 1
77
+ Errors: 0
78
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
79
+ ```
80
+
81
+ ## Dry Run Mode
82
+
83
+ ```bash
84
+ /specweave-ado:reconcile --dry-run
85
+ ```
86
+
87
+ Shows what would be changed without making any modifications:
88
+
89
+ ```
90
+ ❌ Work Item #1240 (0066-import-wizard/US-003): Active but should be CLOSED
91
+ [DRY RUN] Would close work item #1240
92
+
93
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
94
+ 📊 ADO RECONCILIATION SUMMARY
95
+ ⚠️ DRY RUN - No changes were made
96
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
97
+ ```
98
+
99
+ ## Requirements
100
+
101
+ - Azure DevOps PAT configured (`AZURE_DEVOPS_PAT`)
102
+ - `sync.ado.enabled = true` in config.json
103
+ - `sync.settings.canUpdateExternalItems = true` in config.json
104
+
105
+ ## ADO Status Mapping
106
+
107
+ | SpecWeave Status | Expected ADO State |
108
+ |-----------------|-------------------|
109
+ | `completed` | Closed, Done, Resolved |
110
+ | `abandoned` | Closed, Removed |
111
+ | `in-progress` | Active, In Progress |
112
+ | `active` | Active, New |
113
+ | `planning` | New |
114
+
115
+ ## Related Commands
116
+
117
+ - `/specweave-ado:status`: View sync status for increments
118
+ - `/specweave-ado:sync`: Manual sync to ADO
119
+ - `/specweave:done`: Close increment (triggers auto-close)
120
+ - `/specweave:resume`: Resume increment (now triggers auto-reopen)
@@ -0,0 +1,279 @@
1
+ import { consoleLogger } from "../../../src/utils/logger.js";
2
+ class AdoDuplicateDetector {
3
+ constructor(options = {}) {
4
+ this.org = options.org || process.env.AZURE_DEVOPS_ORG || "";
5
+ this.project = options.project || process.env.AZURE_DEVOPS_PROJECT || "";
6
+ this.pat = options.pat || process.env.AZURE_DEVOPS_PAT || process.env.AZURE_DEVOPS_TOKEN || "";
7
+ this.logger = options.logger || consoleLogger;
8
+ }
9
+ /**
10
+ * Phase 1: Check if work item exists before creating
11
+ */
12
+ async checkBeforeCreate(titlePattern, incrementId) {
13
+ try {
14
+ const items = await this.searchWorkItems(titlePattern);
15
+ if (items.length > 0) {
16
+ return {
17
+ found: true,
18
+ existingItem: items[0],
19
+ count: items.length
20
+ };
21
+ }
22
+ return { found: false, count: 0 };
23
+ } catch (error) {
24
+ this.logger.log(`\u26A0\uFE0F Detection check failed: ${error.message}`);
25
+ return { found: false, count: 0 };
26
+ }
27
+ }
28
+ /**
29
+ * Phase 2: Verify count after creation
30
+ */
31
+ async verifyAfterCreate(titlePattern, expectedCount = 1) {
32
+ try {
33
+ const items = await this.searchWorkItems(titlePattern);
34
+ if (items.length > expectedCount) {
35
+ const sorted = items.sort(
36
+ (a, b) => a.id - b.id
37
+ // Sort by ID (lowest = oldest)
38
+ );
39
+ return {
40
+ success: false,
41
+ expectedCount,
42
+ actualCount: items.length,
43
+ duplicates: sorted.slice(expectedCount)
44
+ // All items after expected count
45
+ };
46
+ }
47
+ return {
48
+ success: true,
49
+ expectedCount,
50
+ actualCount: items.length,
51
+ duplicates: []
52
+ };
53
+ } catch (error) {
54
+ this.logger.log(`\u26A0\uFE0F Verification check failed: ${error.message}`);
55
+ return {
56
+ success: true,
57
+ // Assume success on error
58
+ expectedCount,
59
+ actualCount: expectedCount,
60
+ duplicates: []
61
+ };
62
+ }
63
+ }
64
+ /**
65
+ * Phase 3: Auto-close duplicates
66
+ */
67
+ async closeDuplicates(duplicates, keepItemId) {
68
+ const result = {
69
+ closedCount: 0,
70
+ keptCount: 1,
71
+ errors: []
72
+ };
73
+ for (const item of duplicates) {
74
+ try {
75
+ await this.closeWorkItem(item.id, keepItemId);
76
+ result.closedCount++;
77
+ this.logger.log(` \u2705 Closed #${item.id} (duplicate of #${keepItemId})`);
78
+ } catch (error) {
79
+ result.errors.push(`#${item.id}: ${error.message}`);
80
+ this.logger.log(` \u274C Failed to close #${item.id}: ${error.message}`);
81
+ }
82
+ }
83
+ return result;
84
+ }
85
+ /**
86
+ * Full cleanup: Find and close all duplicates for a feature
87
+ */
88
+ async cleanupFeatureDuplicates(featureId, dryRun = false) {
89
+ const searchPattern = `[${featureId}]`;
90
+ const items = await this.searchWorkItems(searchPattern);
91
+ this.logger.log(`
92
+ \u{1F50D} Scanning for duplicates in Feature ${featureId}...`);
93
+ this.logger.log(` Found ${items.length} total work items`);
94
+ const groups = this.groupByTitle(items);
95
+ const duplicateGroups = groups.filter((g) => g.duplicates.length > 0);
96
+ if (duplicateGroups.length === 0) {
97
+ this.logger.log(` \u2705 No duplicates found!`);
98
+ return {
99
+ groups: [],
100
+ totalItems: items.length,
101
+ duplicateCount: 0,
102
+ closedCount: 0
103
+ };
104
+ }
105
+ this.logger.log(` Detected ${duplicateGroups.length} duplicate groups:
106
+ `);
107
+ for (let i = 0; i < duplicateGroups.length; i++) {
108
+ const group = duplicateGroups[i];
109
+ this.logger.log(` \u{1F4CB} Group ${i + 1}: "${group.title.substring(0, 50)}..."`);
110
+ this.logger.log(` - #${group.keepItem.id} (KEEP) - Created ${group.keepItem.createdDate.split("T")[0]}`);
111
+ for (const dup of group.duplicates) {
112
+ this.logger.log(` - #${dup.id} (CLOSE) - Created ${dup.createdDate.split("T")[0]} - DUPLICATE`);
113
+ }
114
+ this.logger.log("");
115
+ }
116
+ const totalDuplicates = duplicateGroups.reduce((sum, g) => sum + g.duplicates.length, 0);
117
+ if (dryRun) {
118
+ this.logger.log(`
119
+ \u2705 Dry run complete!`);
120
+ this.logger.log(` Total work items: ${items.length}`);
121
+ this.logger.log(` Duplicate groups: ${duplicateGroups.length}`);
122
+ this.logger.log(` Work items to close: ${totalDuplicates}`);
123
+ this.logger.log(`
124
+ \u26A0\uFE0F This was a DRY RUN - no changes made.`);
125
+ return {
126
+ groups: duplicateGroups,
127
+ totalItems: items.length,
128
+ duplicateCount: totalDuplicates,
129
+ closedCount: 0
130
+ };
131
+ }
132
+ let closedCount = 0;
133
+ this.logger.log(`\u{1F5D1}\uFE0F Closing duplicates...`);
134
+ for (const group of duplicateGroups) {
135
+ const result = await this.closeDuplicates(group.duplicates, group.keepItem.id);
136
+ closedCount += result.closedCount;
137
+ }
138
+ this.logger.log(`
139
+ \u2705 Cleanup complete!`);
140
+ this.logger.log(` Closed: ${closedCount} duplicates`);
141
+ this.logger.log(` Kept: ${duplicateGroups.length} original work items`);
142
+ return {
143
+ groups: duplicateGroups,
144
+ totalItems: items.length,
145
+ duplicateCount: totalDuplicates,
146
+ closedCount
147
+ };
148
+ }
149
+ /**
150
+ * Group work items by title
151
+ */
152
+ groupByTitle(items) {
153
+ const titleMap = /* @__PURE__ */ new Map();
154
+ for (const item of items) {
155
+ const existing = titleMap.get(item.title) || [];
156
+ existing.push(item);
157
+ titleMap.set(item.title, existing);
158
+ }
159
+ const groups = [];
160
+ for (const [title, groupItems] of titleMap) {
161
+ const sorted = groupItems.sort((a, b) => a.id - b.id);
162
+ groups.push({
163
+ title,
164
+ items: sorted,
165
+ keepItem: sorted[0],
166
+ duplicates: sorted.slice(1)
167
+ });
168
+ }
169
+ return groups;
170
+ }
171
+ /**
172
+ * Search for work items using WIQL
173
+ */
174
+ async searchWorkItems(titlePattern) {
175
+ if (!this.org || !this.pat) {
176
+ throw new Error("ADO credentials not configured");
177
+ }
178
+ const wiql = {
179
+ query: `SELECT [System.Id], [System.Title], [System.State], [System.CreatedDate]
180
+ FROM WorkItems
181
+ WHERE [System.TeamProject] = '${this.project}'
182
+ AND [System.Title] CONTAINS '${titlePattern}'
183
+ ORDER BY [System.Id] ASC`
184
+ };
185
+ const auth = Buffer.from(`:${this.pat}`).toString("base64");
186
+ const url = `https://dev.azure.com/${this.org}/${this.project}/_apis/wit/wiql?api-version=7.1`;
187
+ const response = await fetch(url, {
188
+ method: "POST",
189
+ headers: {
190
+ Authorization: `Basic ${auth}`,
191
+ "Content-Type": "application/json"
192
+ },
193
+ body: JSON.stringify(wiql)
194
+ });
195
+ if (!response.ok) {
196
+ throw new Error(`WIQL query failed: ${response.status}`);
197
+ }
198
+ const data = await response.json();
199
+ const workItemIds = data.workItems?.map((wi) => wi.id) || [];
200
+ if (workItemIds.length === 0) {
201
+ return [];
202
+ }
203
+ return this.getWorkItemDetails(workItemIds);
204
+ }
205
+ /**
206
+ * Get work item details by IDs
207
+ */
208
+ async getWorkItemDetails(ids) {
209
+ if (ids.length === 0) return [];
210
+ const auth = Buffer.from(`:${this.pat}`).toString("base64");
211
+ const url = `https://dev.azure.com/${this.org}/_apis/wit/workitems?ids=${ids.join(",")}&api-version=7.1`;
212
+ const response = await fetch(url, {
213
+ headers: {
214
+ Authorization: `Basic ${auth}`,
215
+ "Content-Type": "application/json"
216
+ }
217
+ });
218
+ if (!response.ok) {
219
+ throw new Error(`Failed to get work items: ${response.status}`);
220
+ }
221
+ const data = await response.json();
222
+ return (data.value || []).map((wi) => ({
223
+ id: wi.id,
224
+ title: wi.fields["System.Title"],
225
+ state: wi.fields["System.State"],
226
+ createdDate: wi.fields["System.CreatedDate"],
227
+ url: wi._links?.html?.href
228
+ }));
229
+ }
230
+ /**
231
+ * Close a work item with duplicate comment
232
+ */
233
+ async closeWorkItem(workItemId, originalId) {
234
+ const auth = Buffer.from(`:${this.pat}`).toString("base64");
235
+ const url = `https://dev.azure.com/${this.org}/_apis/wit/workitems/${workItemId}?api-version=7.1`;
236
+ const comment = `## Duplicate of #${originalId}
237
+
238
+ This work item was automatically closed by SpecWeave cleanup because it is a duplicate.
239
+
240
+ The original work item (#${originalId}) contains the same content and should be used for tracking instead.
241
+
242
+ ---
243
+ \u{1F916} Auto-closed by SpecWeave Duplicate Cleanup`;
244
+ const operations = [
245
+ {
246
+ op: "add",
247
+ path: "/fields/System.State",
248
+ value: "Closed"
249
+ },
250
+ {
251
+ op: "add",
252
+ path: "/fields/System.History",
253
+ value: comment
254
+ }
255
+ ];
256
+ const response = await fetch(url, {
257
+ method: "PATCH",
258
+ headers: {
259
+ Authorization: `Basic ${auth}`,
260
+ "Content-Type": "application/json-patch+json"
261
+ },
262
+ body: JSON.stringify(operations)
263
+ });
264
+ if (!response.ok) {
265
+ const error = await response.text();
266
+ throw new Error(`Failed to close work item: ${response.status} - ${error}`);
267
+ }
268
+ }
269
+ }
270
+ async function cleanupAdoDuplicates(featureId, dryRun = false) {
271
+ const detector = new AdoDuplicateDetector();
272
+ return detector.cleanupFeatureDuplicates(featureId, dryRun);
273
+ }
274
+ var ado_duplicate_detector_default = AdoDuplicateDetector;
275
+ export {
276
+ AdoDuplicateDetector,
277
+ cleanupAdoDuplicates,
278
+ ado_duplicate_detector_default as default
279
+ };