specweave 0.32.2 → 0.32.3

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 (191) hide show
  1. package/CLAUDE.md +39 -0
  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.d.ts.map +1 -1
  26. package/dist/src/cli/commands/init.js +111 -69
  27. package/dist/src/cli/commands/init.js.map +1 -1
  28. package/dist/src/cli/helpers/init/external-import.d.ts +3 -0
  29. package/dist/src/cli/helpers/init/external-import.d.ts.map +1 -1
  30. package/dist/src/cli/helpers/init/external-import.js +17 -4
  31. package/dist/src/cli/helpers/init/external-import.js.map +1 -1
  32. package/dist/src/cli/helpers/init/index.d.ts +1 -0
  33. package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
  34. package/dist/src/cli/helpers/init/index.js +2 -0
  35. package/dist/src/cli/helpers/init/index.js.map +1 -1
  36. package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts +70 -0
  37. package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts.map +1 -1
  38. package/dist/src/cli/helpers/init/jira-ado-auto-detect.js +214 -4
  39. package/dist/src/cli/helpers/init/jira-ado-auto-detect.js.map +1 -1
  40. package/dist/src/cli/helpers/init/living-docs-preflight.d.ts +4 -0
  41. package/dist/src/cli/helpers/init/living-docs-preflight.d.ts.map +1 -1
  42. package/dist/src/cli/helpers/init/living-docs-preflight.js +34 -3
  43. package/dist/src/cli/helpers/init/living-docs-preflight.js.map +1 -1
  44. package/dist/src/cli/helpers/init/testing-config.d.ts +3 -0
  45. package/dist/src/cli/helpers/init/testing-config.d.ts.map +1 -1
  46. package/dist/src/cli/helpers/init/testing-config.js +9 -2
  47. package/dist/src/cli/helpers/init/testing-config.js.map +1 -1
  48. package/dist/src/cli/helpers/init/translation-config.d.ts +3 -0
  49. package/dist/src/cli/helpers/init/translation-config.d.ts.map +1 -1
  50. package/dist/src/cli/helpers/init/translation-config.js +21 -4
  51. package/dist/src/cli/helpers/init/translation-config.js.map +1 -1
  52. package/dist/src/cli/helpers/init/wizard-navigation.d.ts +45 -0
  53. package/dist/src/cli/helpers/init/wizard-navigation.d.ts.map +1 -0
  54. package/dist/src/cli/helpers/init/wizard-navigation.js +97 -0
  55. package/dist/src/cli/helpers/init/wizard-navigation.js.map +1 -0
  56. package/dist/src/core/increment/increment-archiver.d.ts +25 -4
  57. package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
  58. package/dist/src/core/increment/increment-archiver.js +64 -20
  59. package/dist/src/core/increment/increment-archiver.js.map +1 -1
  60. package/dist/src/core/increment/increment-utils.d.ts +65 -0
  61. package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
  62. package/dist/src/core/increment/increment-utils.js +114 -0
  63. package/dist/src/core/increment/increment-utils.js.map +1 -1
  64. package/dist/src/core/living-docs/feature-archiver.d.ts +4 -0
  65. package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -1
  66. package/dist/src/core/living-docs/feature-archiver.js +32 -10
  67. package/dist/src/core/living-docs/feature-archiver.js.map +1 -1
  68. package/dist/src/core/living-docs/feature-id-manager.d.ts.map +1 -1
  69. package/dist/src/core/living-docs/feature-id-manager.js +7 -3
  70. package/dist/src/core/living-docs/feature-id-manager.js.map +1 -1
  71. package/dist/src/core/living-docs/governance/ecosystem-detector.d.ts +38 -0
  72. package/dist/src/core/living-docs/governance/ecosystem-detector.d.ts.map +1 -0
  73. package/dist/src/core/living-docs/governance/ecosystem-detector.js +325 -0
  74. package/dist/src/core/living-docs/governance/ecosystem-detector.js.map +1 -0
  75. package/dist/src/core/living-docs/governance/frontend-standards-parser.d.ts +74 -0
  76. package/dist/src/core/living-docs/governance/frontend-standards-parser.d.ts.map +1 -0
  77. package/dist/src/core/living-docs/governance/frontend-standards-parser.js +366 -0
  78. package/dist/src/core/living-docs/governance/frontend-standards-parser.js.map +1 -0
  79. package/dist/src/core/living-docs/governance/go-standards-parser.d.ts +64 -0
  80. package/dist/src/core/living-docs/governance/go-standards-parser.d.ts.map +1 -0
  81. package/dist/src/core/living-docs/governance/go-standards-parser.js +229 -0
  82. package/dist/src/core/living-docs/governance/go-standards-parser.js.map +1 -0
  83. package/dist/src/core/living-docs/governance/index.d.ts +50 -0
  84. package/dist/src/core/living-docs/governance/index.d.ts.map +1 -0
  85. package/dist/src/core/living-docs/governance/index.js +56 -0
  86. package/dist/src/core/living-docs/governance/index.js.map +1 -0
  87. package/dist/src/core/living-docs/governance/java-standards-parser.d.ts +89 -0
  88. package/dist/src/core/living-docs/governance/java-standards-parser.d.ts.map +1 -0
  89. package/dist/src/core/living-docs/governance/java-standards-parser.js +356 -0
  90. package/dist/src/core/living-docs/governance/java-standards-parser.js.map +1 -0
  91. package/dist/src/core/living-docs/governance/python-standards-parser.d.ts +83 -0
  92. package/dist/src/core/living-docs/governance/python-standards-parser.d.ts.map +1 -0
  93. package/dist/src/core/living-docs/governance/python-standards-parser.js +347 -0
  94. package/dist/src/core/living-docs/governance/python-standards-parser.js.map +1 -0
  95. package/dist/src/core/living-docs/governance/standards-generator.d.ts +38 -0
  96. package/dist/src/core/living-docs/governance/standards-generator.d.ts.map +1 -0
  97. package/dist/src/core/living-docs/governance/standards-generator.js +476 -0
  98. package/dist/src/core/living-docs/governance/standards-generator.js.map +1 -0
  99. package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.d.ts.map +1 -1
  100. package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js +54 -2
  101. package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js.map +1 -1
  102. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts +5 -1
  103. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts.map +1 -1
  104. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js +358 -30
  105. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js.map +1 -1
  106. package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts +44 -0
  107. package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts.map +1 -1
  108. package/dist/src/core/living-docs/living-docs-sync.d.ts +6 -3
  109. package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
  110. package/dist/src/core/living-docs/living-docs-sync.js +17 -8
  111. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  112. package/dist/src/core/living-docs/module-analyzer.d.ts +22 -0
  113. package/dist/src/core/living-docs/module-analyzer.d.ts.map +1 -1
  114. package/dist/src/core/living-docs/module-analyzer.js +123 -19
  115. package/dist/src/core/living-docs/module-analyzer.js.map +1 -1
  116. package/dist/src/core/llm/provider-factory.js +2 -2
  117. package/dist/src/core/llm/provider-factory.js.map +1 -1
  118. package/dist/src/core/llm/providers/anthropic-provider.js +1 -1
  119. package/dist/src/core/llm/providers/bedrock-provider.d.ts.map +1 -1
  120. package/dist/src/core/llm/providers/bedrock-provider.js +8 -4
  121. package/dist/src/core/llm/providers/bedrock-provider.js.map +1 -1
  122. package/dist/src/importers/jira-importer.d.ts +14 -0
  123. package/dist/src/importers/jira-importer.d.ts.map +1 -1
  124. package/dist/src/importers/jira-importer.js +75 -0
  125. package/dist/src/importers/jira-importer.js.map +1 -1
  126. package/dist/src/integrations/jira/jira-token-provider.d.ts +93 -0
  127. package/dist/src/integrations/jira/jira-token-provider.d.ts.map +1 -0
  128. package/dist/src/integrations/jira/jira-token-provider.js +160 -0
  129. package/dist/src/integrations/jira/jira-token-provider.js.map +1 -0
  130. package/dist/src/sync/ado-reconciler.d.ts +92 -0
  131. package/dist/src/sync/ado-reconciler.d.ts.map +1 -0
  132. package/dist/src/sync/ado-reconciler.js +335 -0
  133. package/dist/src/sync/ado-reconciler.js.map +1 -0
  134. package/dist/src/sync/jira-reconciler.d.ts +106 -0
  135. package/dist/src/sync/jira-reconciler.d.ts.map +1 -0
  136. package/dist/src/sync/jira-reconciler.js +405 -0
  137. package/dist/src/sync/jira-reconciler.js.map +1 -0
  138. package/dist/src/types/model-selection.d.ts +6 -4
  139. package/dist/src/types/model-selection.d.ts.map +1 -1
  140. package/dist/src/types/model-selection.js +3 -1
  141. package/dist/src/types/model-selection.js.map +1 -1
  142. package/dist/src/utils/external-tool-drift-detector.d.ts +1 -1
  143. package/dist/src/utils/external-tool-drift-detector.d.ts.map +1 -1
  144. package/dist/src/utils/external-tool-drift-detector.js +5 -4
  145. package/dist/src/utils/external-tool-drift-detector.js.map +1 -1
  146. package/dist/src/utils/feature-id-derivation.d.ts +8 -3
  147. package/dist/src/utils/feature-id-derivation.d.ts.map +1 -1
  148. package/dist/src/utils/feature-id-derivation.js +14 -6
  149. package/dist/src/utils/feature-id-derivation.js.map +1 -1
  150. package/dist/src/utils/model-selection.d.ts +3 -4
  151. package/dist/src/utils/model-selection.d.ts.map +1 -1
  152. package/dist/src/utils/model-selection.js +3 -4
  153. package/dist/src/utils/model-selection.js.map +1 -1
  154. package/package.json +1 -1
  155. package/plugins/specweave/agents/code-standards-detective/AGENT.md +48 -0
  156. package/plugins/specweave/commands/specweave-costs.md +4 -4
  157. package/plugins/specweave/commands/specweave-do.md +9 -9
  158. package/plugins/specweave/commands/specweave-done.md +13 -0
  159. package/plugins/specweave/commands/specweave-validate.md +27 -1
  160. package/plugins/specweave/hooks/hooks.json +10 -0
  161. package/plugins/specweave/hooks/spec-project-validator.sh +80 -25
  162. package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +135 -0
  163. package/plugins/specweave/scripts/read-costs.sh +3 -3
  164. package/plugins/specweave/skills/code-standards-analyzer/SKILL.md +58 -6
  165. package/plugins/specweave/skills/increment-planner/SKILL.md +56 -25
  166. package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +4 -2
  167. package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +2 -1
  168. package/plugins/specweave/skills/increment-planner/templates/tasks-multi-project.md +1 -1
  169. package/plugins/specweave/skills/increment-planner/templates/tasks-single-project.md +1 -1
  170. package/plugins/specweave-ado/commands/cleanup-duplicates.md +212 -0
  171. package/plugins/specweave-ado/commands/reconcile.md +120 -0
  172. package/plugins/specweave-ado/lib/ado-duplicate-detector.js +279 -0
  173. package/plugins/specweave-ado/lib/ado-duplicate-detector.ts +407 -0
  174. package/plugins/specweave-github/agents/github-manager/AGENT.md +2 -2
  175. package/plugins/specweave-infrastructure/skills/hetzner-provisioner/README.md +1 -1
  176. package/plugins/specweave-jira/agents/jira-manager/AGENT.md +1 -1
  177. package/plugins/specweave-jira/agents/jira-multi-project-mapper/AGENT.md +530 -0
  178. package/plugins/specweave-jira/agents/jira-sync-judge/AGENT.md +438 -0
  179. package/plugins/specweave-jira/commands/cleanup-duplicates.md +219 -0
  180. package/plugins/specweave-jira/commands/close.md +297 -0
  181. package/plugins/specweave-jira/commands/create.md +198 -0
  182. package/plugins/specweave-jira/commands/reconcile.md +123 -0
  183. package/plugins/specweave-jira/commands/status.md +215 -0
  184. package/plugins/specweave-jira/lib/jira-duplicate-detector.js +296 -0
  185. package/plugins/specweave-jira/lib/jira-duplicate-detector.ts +434 -0
  186. package/plugins/specweave-jira/lib/jira-permission-gate.js +160 -0
  187. package/plugins/specweave-jira/lib/jira-permission-gate.ts +276 -0
  188. package/plugins/specweave-jira/lib/jira-profile-resolver.js +222 -0
  189. package/plugins/specweave-jira/lib/jira-profile-resolver.ts +427 -0
  190. package/plugins/specweave-jira/reference/jira-specweave-mapping.md +16 -11
  191. package/plugins/specweave-release/commands/specweave-release-npm.md +140 -14
@@ -0,0 +1,215 @@
1
+ ---
2
+ name: specweave-jira:status
3
+ description: Check JIRA sync status for SpecWeave increment
4
+ ---
5
+
6
+ # JIRA Status Command
7
+
8
+ **Usage**: `/specweave-jira:status <increment-id>`
9
+
10
+ **Purpose**: Display JIRA sync status and issue details for an increment
11
+
12
+ ---
13
+
14
+ ## Command Behavior
15
+
16
+ When user runs this command, Claude should:
17
+
18
+ ### 1. Read Increment Metadata
19
+
20
+ ```typescript
21
+ // Load increment metadata
22
+ const metadataPath = `.specweave/increments/${incrementId}/metadata.json`;
23
+ const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
24
+
25
+ // Check for linked JIRA issue (check both standard and legacy paths)
26
+ const jiraSync = metadata?.external_sync?.jira || metadata?.external_ids?.jira;
27
+ const issueKey = jiraSync?.issueKey || jiraSync?.epic;
28
+
29
+ if (!issueKey) {
30
+ console.log(`
31
+ ⚠️ No JIRA issue linked
32
+
33
+ This increment is not linked to a JIRA issue.
34
+ Create one with: /specweave-jira:create ${incrementId}
35
+ `);
36
+ return;
37
+ }
38
+ ```
39
+
40
+ ### 2. Resolve JIRA Profile
41
+
42
+ ```typescript
43
+ // Priority: increment profile > global defaultProfile > activeProfile
44
+ let profileName = jiraSync?.profile;
45
+ if (!profileName) {
46
+ const config = JSON.parse(await fs.readFile('.specweave/config.json', 'utf-8'));
47
+ profileName = config?.sync?.defaultProfile ?? config?.sync?.activeProfile;
48
+ }
49
+
50
+ const profileConfig = config?.sync?.profiles?.[profileName];
51
+ const { domain, projectKey } = profileConfig?.config || {};
52
+ ```
53
+
54
+ ### 3. Read Local Task Status
55
+
56
+ ```typescript
57
+ // Check tasks.md for completion
58
+ const tasksPath = `.specweave/increments/${incrementId}/tasks.md`;
59
+ const tasksContent = await fs.readFile(tasksPath, 'utf-8');
60
+
61
+ const totalTasks = (tasksContent.match(/### T-\d+/g) || []).length;
62
+ const completedTasks = (tasksContent.match(/\[x\] completed/gi) || []).length;
63
+ const completion = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
64
+ ```
65
+
66
+ ### 4. Invoke JIRA Manager Agent (Optional - For Live Status)
67
+
68
+ ```
69
+ Use Task tool with subagent_type: "specweave-jira:jira-manager:jira-manager"
70
+
71
+ Prompt: "Check JIRA sync status for increment {increment-id}.
72
+
73
+ Issue Key: {issueKey}
74
+ Profile: {profileName} (domain: {domain})
75
+
76
+ Steps:
77
+ 1. GET issue from JIRA API: /rest/api/3/issue/{issueKey}
78
+ 2. Extract: status, priority, assignee, sprint, story points
79
+ 3. Compare local vs JIRA status
80
+ 4. Detect any sync issues or drift
81
+ 5. Display comprehensive status"
82
+ ```
83
+
84
+ ### 5. Display Status
85
+
86
+ ```
87
+ JIRA Sync Status
88
+ ================
89
+ Increment: {increment-id}
90
+ Issue: {issueKey}
91
+ URL: https://{domain}/browse/{issueKey}
92
+ Status: {jiraStatus}
93
+ Completion: {completion}% ({completedTasks}/{totalTasks} tasks)
94
+ Last Synced: {lastSyncedAt} ({relativeTime})
95
+ Sync Enabled: ✅
96
+
97
+ Profile: {profileName}
98
+ Domain: {domain}
99
+ Project: {projectKey}
100
+
101
+ Next Sync: Automatic on task completion
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Example Output
107
+
108
+ ### Issue Linked
109
+
110
+ ```
111
+ User: /specweave-jira:status 0005-payment-integration
112
+
113
+ Claude:
114
+ JIRA Sync Status
115
+ ================
116
+ Increment: 0005-payment-integration
117
+ Issue: MYPROJ-123
118
+ URL: https://company.atlassian.net/browse/MYPROJ-123
119
+ Status: In Progress
120
+ Completion: 60% (6/10 tasks)
121
+ Last Synced: 2025-12-07 10:30:00 (5 minutes ago)
122
+ Sync Enabled: ✅
123
+
124
+ Profile: jira-my-project
125
+ Domain: company.atlassian.net
126
+ Project: MYPROJ
127
+
128
+ Priority: High
129
+ Assignee: john.doe
130
+ Sprint: Sprint 42
131
+
132
+ Next Sync: Automatic on task completion
133
+ ```
134
+
135
+ ### No Issue Linked
136
+
137
+ ```
138
+ User: /specweave-jira:status 0005
139
+
140
+ Claude:
141
+ JIRA Sync Status
142
+ ================
143
+ Increment: 0005-payment-integration
144
+
145
+ ⚠️ No JIRA issue linked
146
+
147
+ This increment is not linked to a JIRA issue.
148
+ Create one with: /specweave-jira:create 0005-payment-integration
149
+ ```
150
+
151
+ ### Sync Issues Detected
152
+
153
+ ```
154
+ User: /specweave-jira:status 0005
155
+
156
+ Claude:
157
+ JIRA Sync Status
158
+ ================
159
+ Increment: 0005-payment-integration
160
+ Issue: MYPROJ-123
161
+ URL: https://company.atlassian.net/browse/MYPROJ-123
162
+ Status: In Progress
163
+ Completion: 60% (6/10 tasks)
164
+ Last Synced: 2025-12-05 10:30:00 (2 days ago)
165
+ Sync Enabled: ✅
166
+
167
+ ⚠️ Sync Issues Detected:
168
+ - Local status: in_progress, JIRA status: Done
169
+ - Status drift detected - run /specweave-jira:pull to sync
170
+
171
+ Profile: jira-my-project
172
+ Domain: company.atlassian.net
173
+ Project: MYPROJ
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Status Information Displayed
179
+
180
+ | Field | Source | Description |
181
+ |-------|--------|-------------|
182
+ | Issue | metadata.json | JIRA issue key (e.g., PROJ-123) |
183
+ | URL | Constructed | Direct link to JIRA issue |
184
+ | Status | JIRA API | Current issue status |
185
+ | Completion | tasks.md | Local completion percentage |
186
+ | Last Synced | metadata.json | Timestamp of last sync |
187
+ | Profile | metadata/config | Which JIRA profile is used |
188
+ | Priority | JIRA API | Issue priority |
189
+ | Assignee | JIRA API | Current assignee |
190
+ | Sprint | JIRA API | Current sprint assignment |
191
+
192
+ ---
193
+
194
+ ## Drift Detection
195
+
196
+ The status command detects common sync issues:
197
+
198
+ | Issue | Detection | Resolution |
199
+ |-------|-----------|------------|
200
+ | Status drift | Local vs JIRA status mismatch | `/specweave-jira:pull` |
201
+ | Stale sync | Last synced > 24 hours ago | `/specweave-jira:sync` |
202
+ | Missing issue | Issue deleted in JIRA | `/specweave-jira:create` |
203
+ | Permission denied | canUpdateExternalItems=false | Read-only mode note |
204
+
205
+ ---
206
+
207
+ ## Related
208
+
209
+ | Command | Purpose |
210
+ |---------|---------|
211
+ | `/specweave-jira:pull` | Pull changes from JIRA |
212
+ | `/specweave-jira:push` | Push progress to JIRA |
213
+ | `/specweave-jira:sync` | Two-way sync |
214
+ | `/specweave-jira:create` | Create JIRA issue |
215
+ | `/specweave-jira:close` | Close issue when complete |
@@ -0,0 +1,296 @@
1
+ import { consoleLogger } from "../../../src/utils/logger.js";
2
+ class JiraDuplicateDetector {
3
+ constructor(options = {}) {
4
+ this.domain = options.domain || process.env.JIRA_DOMAIN || "";
5
+ const email = options.email || process.env.JIRA_EMAIL || "";
6
+ const token = options.token || process.env.JIRA_API_TOKEN || "";
7
+ this.auth = Buffer.from(`${email}:${token}`).toString("base64");
8
+ this.logger = options.logger || consoleLogger;
9
+ }
10
+ /**
11
+ * Phase 1: Check if issue exists before creating
12
+ */
13
+ async checkBeforeCreate(summaryPattern, incrementId) {
14
+ try {
15
+ const issues = await this.searchIssues(summaryPattern);
16
+ if (issues.length > 0) {
17
+ return {
18
+ found: true,
19
+ existingIssue: issues[0],
20
+ count: issues.length
21
+ };
22
+ }
23
+ return { found: false, count: 0 };
24
+ } catch (error) {
25
+ this.logger.log(`\u26A0\uFE0F Detection check failed: ${error.message}`);
26
+ return { found: false, count: 0 };
27
+ }
28
+ }
29
+ /**
30
+ * Phase 2: Verify count after creation
31
+ */
32
+ async verifyAfterCreate(summaryPattern, expectedCount = 1) {
33
+ try {
34
+ const issues = await this.searchIssues(summaryPattern);
35
+ if (issues.length > expectedCount) {
36
+ const sorted = issues.sort(
37
+ (a, b) => new Date(a.created).getTime() - new Date(b.created).getTime()
38
+ );
39
+ return {
40
+ success: false,
41
+ expectedCount,
42
+ actualCount: issues.length,
43
+ duplicates: sorted.slice(expectedCount)
44
+ // All issues after expected count
45
+ };
46
+ }
47
+ return {
48
+ success: true,
49
+ expectedCount,
50
+ actualCount: issues.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, keepIssueKey) {
68
+ const result = {
69
+ closedCount: 0,
70
+ keptCount: 1,
71
+ errors: []
72
+ };
73
+ for (const issue of duplicates) {
74
+ try {
75
+ await this.closeIssue(issue.key, keepIssueKey);
76
+ result.closedCount++;
77
+ this.logger.log(` \u2705 Closed ${issue.key} (duplicate of ${keepIssueKey})`);
78
+ } catch (error) {
79
+ result.errors.push(`${issue.key}: ${error.message}`);
80
+ this.logger.log(` \u274C Failed to close ${issue.key}: ${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 issues = await this.searchIssues(searchPattern);
91
+ this.logger.log(`
92
+ \u{1F50D} Scanning for duplicates in Feature ${featureId}...`);
93
+ this.logger.log(` Found ${issues.length} total issues`);
94
+ const groups = this.groupBySummary(issues);
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
+ totalIssues: issues.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.summary.substring(0, 50)}..."`);
110
+ this.logger.log(` - ${group.keepIssue.key} (KEEP) - Created ${group.keepIssue.created.split("T")[0]}`);
111
+ for (const dup of group.duplicates) {
112
+ this.logger.log(` - ${dup.key} (CLOSE) - Created ${dup.created.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 issues: ${issues.length}`);
121
+ this.logger.log(` Duplicate groups: ${duplicateGroups.length}`);
122
+ this.logger.log(` Issues to close: ${totalDuplicates}`);
123
+ this.logger.log(`
124
+ \u26A0\uFE0F This was a DRY RUN - no changes made.`);
125
+ return {
126
+ groups: duplicateGroups,
127
+ totalIssues: issues.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.keepIssue.key);
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 issues`);
142
+ return {
143
+ groups: duplicateGroups,
144
+ totalIssues: issues.length,
145
+ duplicateCount: totalDuplicates,
146
+ closedCount
147
+ };
148
+ }
149
+ /**
150
+ * Group issues by summary
151
+ */
152
+ groupBySummary(issues) {
153
+ const summaryMap = /* @__PURE__ */ new Map();
154
+ for (const issue of issues) {
155
+ const existing = summaryMap.get(issue.summary) || [];
156
+ existing.push(issue);
157
+ summaryMap.set(issue.summary, existing);
158
+ }
159
+ const groups = [];
160
+ for (const [summary, groupIssues] of summaryMap) {
161
+ const sorted = groupIssues.sort(
162
+ (a, b) => new Date(a.created).getTime() - new Date(b.created).getTime()
163
+ );
164
+ groups.push({
165
+ summary,
166
+ issues: sorted,
167
+ keepIssue: sorted[0],
168
+ duplicates: sorted.slice(1)
169
+ });
170
+ }
171
+ return groups;
172
+ }
173
+ /**
174
+ * Search for issues using JQL
175
+ */
176
+ async searchIssues(summaryPattern) {
177
+ if (!this.domain || !this.auth) {
178
+ throw new Error("JIRA credentials not configured");
179
+ }
180
+ const jql = encodeURIComponent(`summary ~ "${summaryPattern}" ORDER BY created ASC`);
181
+ const url = `https://${this.domain}/rest/api/3/search?jql=${jql}&fields=summary,status,created`;
182
+ const response = await fetch(url, {
183
+ headers: {
184
+ Authorization: `Basic ${this.auth}`,
185
+ Accept: "application/json"
186
+ }
187
+ });
188
+ if (!response.ok) {
189
+ throw new Error(`JQL search failed: ${response.status}`);
190
+ }
191
+ const data = await response.json();
192
+ return (data.issues || []).map((issue) => ({
193
+ key: issue.key,
194
+ summary: issue.fields.summary,
195
+ status: issue.fields.status?.name,
196
+ created: issue.fields.created,
197
+ url: `https://${this.domain}/browse/${issue.key}`
198
+ }));
199
+ }
200
+ /**
201
+ * Close an issue with duplicate comment
202
+ */
203
+ async closeIssue(issueKey, originalKey) {
204
+ await this.addComment(issueKey, originalKey);
205
+ const transitions = await this.getTransitions(issueKey);
206
+ const closeTransition = transitions.find(
207
+ (t) => t.name === "Won't Do" || t.name === "Done" || t.name === "Closed" || t.to?.name === "Won't Do" || t.to?.name === "Done"
208
+ );
209
+ if (!closeTransition) {
210
+ throw new Error(`No close transition found. Available: ${transitions.map((t) => t.name).join(", ")}`);
211
+ }
212
+ const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/transitions`;
213
+ const response = await fetch(url, {
214
+ method: "POST",
215
+ headers: {
216
+ Authorization: `Basic ${this.auth}`,
217
+ "Content-Type": "application/json"
218
+ },
219
+ body: JSON.stringify({
220
+ transition: { id: closeTransition.id }
221
+ })
222
+ });
223
+ if (!response.ok) {
224
+ const error = await response.text();
225
+ throw new Error(`Failed to transition issue: ${response.status} - ${error}`);
226
+ }
227
+ }
228
+ /**
229
+ * Get available transitions for an issue
230
+ */
231
+ async getTransitions(issueKey) {
232
+ const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/transitions`;
233
+ const response = await fetch(url, {
234
+ headers: {
235
+ Authorization: `Basic ${this.auth}`,
236
+ Accept: "application/json"
237
+ }
238
+ });
239
+ if (!response.ok) {
240
+ throw new Error(`Failed to get transitions: ${response.status}`);
241
+ }
242
+ const data = await response.json();
243
+ return data.transitions || [];
244
+ }
245
+ /**
246
+ * Add duplicate comment to issue
247
+ */
248
+ async addComment(issueKey, originalKey) {
249
+ const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/comment`;
250
+ const comment = `h2. Duplicate of ${originalKey}
251
+
252
+ This issue was automatically closed by SpecWeave cleanup because it is a duplicate.
253
+
254
+ The original issue (${originalKey}) contains the same content and should be used for tracking instead.
255
+
256
+ ----
257
+ \u{1F916} Auto-closed by SpecWeave Duplicate Cleanup`;
258
+ const response = await fetch(url, {
259
+ method: "POST",
260
+ headers: {
261
+ Authorization: `Basic ${this.auth}`,
262
+ "Content-Type": "application/json"
263
+ },
264
+ body: JSON.stringify({
265
+ body: {
266
+ type: "doc",
267
+ version: 1,
268
+ content: [
269
+ {
270
+ type: "paragraph",
271
+ content: [
272
+ {
273
+ type: "text",
274
+ text: comment
275
+ }
276
+ ]
277
+ }
278
+ ]
279
+ }
280
+ })
281
+ });
282
+ if (!response.ok) {
283
+ this.logger.log(` \u26A0\uFE0F Failed to add comment to ${issueKey}`);
284
+ }
285
+ }
286
+ }
287
+ async function cleanupJiraDuplicates(featureId, dryRun = false) {
288
+ const detector = new JiraDuplicateDetector();
289
+ return detector.cleanupFeatureDuplicates(featureId, dryRun);
290
+ }
291
+ var jira_duplicate_detector_default = JiraDuplicateDetector;
292
+ export {
293
+ JiraDuplicateDetector,
294
+ cleanupJiraDuplicates,
295
+ jira_duplicate_detector_default as default
296
+ };