specweave 0.17.15 → 0.17.17

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 (200) hide show
  1. package/CLAUDE.md +405 -2495
  2. package/README.md +92 -2
  3. package/dist/locales/de/.gitkeep +0 -0
  4. package/dist/locales/de/cli.json +108 -0
  5. package/dist/locales/en/cli.json +287 -0
  6. package/dist/locales/en/errors.json +7 -0
  7. package/dist/locales/en/templates.json +6 -0
  8. package/dist/locales/es/.gitkeep +0 -0
  9. package/dist/locales/es/cli.json +41 -0
  10. package/dist/locales/fr/.gitkeep +0 -0
  11. package/dist/locales/fr/cli.json +108 -0
  12. package/dist/locales/ja/.gitkeep +0 -0
  13. package/dist/locales/ja/cli.json +108 -0
  14. package/dist/locales/ko/.gitkeep +0 -0
  15. package/dist/locales/ko/cli.json +108 -0
  16. package/dist/locales/pt/.gitkeep +0 -0
  17. package/dist/locales/pt/cli.json +108 -0
  18. package/dist/locales/ru/.gitkeep +0 -0
  19. package/dist/locales/ru/cli.json +269 -0
  20. package/dist/locales/zh/.gitkeep +0 -0
  21. package/dist/locales/zh/cli.json +108 -0
  22. package/dist/plugins/specweave/lib/hooks/sync-living-docs.d.ts.map +1 -1
  23. package/dist/plugins/specweave/lib/hooks/sync-living-docs.js +188 -36
  24. package/dist/plugins/specweave/lib/hooks/sync-living-docs.js.map +1 -1
  25. package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts +54 -0
  26. package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts.map +1 -0
  27. package/dist/plugins/specweave-ado/lib/ado-status-sync.js +86 -0
  28. package/dist/plugins/specweave-ado/lib/ado-status-sync.js.map +1 -0
  29. package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts +25 -0
  30. package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts.map +1 -0
  31. package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js +191 -0
  32. package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js.map +1 -0
  33. package/dist/plugins/specweave-github/lib/duplicate-detector.d.ts +139 -0
  34. package/dist/plugins/specweave-github/lib/duplicate-detector.d.ts.map +1 -0
  35. package/dist/plugins/specweave-github/lib/duplicate-detector.js +389 -0
  36. package/dist/plugins/specweave-github/lib/duplicate-detector.js.map +1 -0
  37. package/dist/plugins/specweave-github/lib/enhanced-github-sync.d.ts +26 -0
  38. package/dist/plugins/specweave-github/lib/enhanced-github-sync.d.ts.map +1 -0
  39. package/dist/plugins/specweave-github/lib/enhanced-github-sync.js +249 -0
  40. package/dist/plugins/specweave-github/lib/enhanced-github-sync.js.map +1 -0
  41. package/dist/plugins/specweave-github/lib/github-client.d.ts +1 -1
  42. package/dist/plugins/specweave-github/lib/github-client.d.ts.map +1 -1
  43. package/dist/plugins/specweave-github/lib/github-client.js +25 -13
  44. package/dist/plugins/specweave-github/lib/github-client.js.map +1 -1
  45. package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts +83 -0
  46. package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts.map +1 -0
  47. package/dist/plugins/specweave-github/lib/github-epic-sync.js +451 -0
  48. package/dist/plugins/specweave-github/lib/github-epic-sync.js.map +1 -0
  49. package/dist/plugins/specweave-github/lib/github-status-sync.d.ts +43 -0
  50. package/dist/plugins/specweave-github/lib/github-status-sync.d.ts.map +1 -0
  51. package/dist/plugins/specweave-github/lib/github-status-sync.js +82 -0
  52. package/dist/plugins/specweave-github/lib/github-status-sync.js.map +1 -0
  53. package/dist/plugins/specweave-github/lib/task-sync.d.ts +5 -0
  54. package/dist/plugins/specweave-github/lib/task-sync.d.ts.map +1 -1
  55. package/dist/plugins/specweave-github/lib/task-sync.js +38 -2
  56. package/dist/plugins/specweave-github/lib/task-sync.js.map +1 -1
  57. package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts +26 -0
  58. package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts.map +1 -0
  59. package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js +195 -0
  60. package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js.map +1 -0
  61. package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts +66 -0
  62. package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts.map +1 -0
  63. package/dist/plugins/specweave-jira/lib/jira-epic-sync.js +274 -0
  64. package/dist/plugins/specweave-jira/lib/jira-epic-sync.js.map +1 -0
  65. package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +56 -0
  66. package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -0
  67. package/dist/plugins/specweave-jira/lib/jira-status-sync.js +93 -0
  68. package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -0
  69. package/dist/spec-parser.js +629 -0
  70. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  71. package/dist/src/cli/helpers/issue-tracker/index.js +48 -3
  72. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  73. package/dist/src/core/living-docs/hierarchy-mapper.d.ts +142 -0
  74. package/dist/src/core/living-docs/hierarchy-mapper.d.ts.map +1 -0
  75. package/dist/src/core/living-docs/hierarchy-mapper.js +453 -0
  76. package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -0
  77. package/dist/src/core/living-docs/index.d.ts +10 -84
  78. package/dist/src/core/living-docs/index.d.ts.map +1 -1
  79. package/dist/src/core/living-docs/index.js +10 -164
  80. package/dist/src/core/living-docs/index.js.map +1 -1
  81. package/dist/src/core/living-docs/spec-distributor.d.ts +106 -0
  82. package/dist/src/core/living-docs/spec-distributor.d.ts.map +1 -0
  83. package/dist/src/core/living-docs/spec-distributor.js +823 -0
  84. package/dist/src/core/living-docs/spec-distributor.js.map +1 -0
  85. package/dist/src/core/living-docs/types.d.ts +201 -0
  86. package/dist/src/core/living-docs/types.d.ts.map +1 -0
  87. package/dist/src/core/living-docs/types.js +15 -0
  88. package/dist/src/core/living-docs/types.js.map +1 -0
  89. package/dist/src/core/logging/prompt-logger.d.ts +70 -0
  90. package/dist/src/core/logging/prompt-logger.d.ts.map +1 -0
  91. package/dist/src/core/logging/prompt-logger.js +247 -0
  92. package/dist/src/core/logging/prompt-logger.js.map +1 -0
  93. package/dist/src/core/status-line/status-line-manager.d.ts +15 -24
  94. package/dist/src/core/status-line/status-line-manager.d.ts.map +1 -1
  95. package/dist/src/core/status-line/status-line-manager.js +33 -70
  96. package/dist/src/core/status-line/status-line-manager.js.map +1 -1
  97. package/dist/src/core/status-line/types.d.ts +19 -31
  98. package/dist/src/core/status-line/types.d.ts.map +1 -1
  99. package/dist/src/core/status-line/types.js +5 -9
  100. package/dist/src/core/status-line/types.js.map +1 -1
  101. package/dist/src/core/sync/conflict-resolver.d.ts +66 -0
  102. package/dist/src/core/sync/conflict-resolver.d.ts.map +1 -0
  103. package/dist/src/core/sync/conflict-resolver.js +108 -0
  104. package/dist/src/core/sync/conflict-resolver.js.map +1 -0
  105. package/dist/src/core/sync/enhanced-content-builder.d.ts +77 -0
  106. package/dist/src/core/sync/enhanced-content-builder.d.ts.map +1 -0
  107. package/dist/src/core/sync/enhanced-content-builder.js +199 -0
  108. package/dist/src/core/sync/enhanced-content-builder.js.map +1 -0
  109. package/dist/src/core/sync/label-detector.d.ts +66 -0
  110. package/dist/src/core/sync/label-detector.d.ts.map +1 -0
  111. package/dist/src/core/sync/label-detector.js +211 -0
  112. package/dist/src/core/sync/label-detector.js.map +1 -0
  113. package/dist/src/core/sync/retry-logic.d.ts +64 -0
  114. package/dist/src/core/sync/retry-logic.d.ts.map +1 -0
  115. package/dist/src/core/sync/retry-logic.js +165 -0
  116. package/dist/src/core/sync/retry-logic.js.map +1 -0
  117. package/dist/src/core/sync/spec-content-sync.d.ts +88 -0
  118. package/dist/src/core/sync/spec-content-sync.d.ts.map +1 -0
  119. package/dist/src/core/sync/spec-content-sync.js +5 -0
  120. package/dist/src/core/sync/spec-content-sync.js.map +1 -0
  121. package/dist/src/core/sync/spec-increment-mapper.d.ts +100 -0
  122. package/dist/src/core/sync/spec-increment-mapper.d.ts.map +1 -0
  123. package/dist/src/core/sync/spec-increment-mapper.js +424 -0
  124. package/dist/src/core/sync/spec-increment-mapper.js.map +1 -0
  125. package/dist/src/core/sync/status-cache.d.ts +91 -0
  126. package/dist/src/core/sync/status-cache.d.ts.map +1 -0
  127. package/dist/src/core/sync/status-cache.js +140 -0
  128. package/dist/src/core/sync/status-cache.js.map +1 -0
  129. package/dist/src/core/sync/status-mapper.d.ts +69 -0
  130. package/dist/src/core/sync/status-mapper.d.ts.map +1 -0
  131. package/dist/src/core/sync/status-mapper.js +90 -0
  132. package/dist/src/core/sync/status-mapper.js.map +1 -0
  133. package/dist/src/core/sync/status-sync-engine.d.ts +162 -0
  134. package/dist/src/core/sync/status-sync-engine.d.ts.map +1 -0
  135. package/dist/src/core/sync/status-sync-engine.js +347 -0
  136. package/dist/src/core/sync/status-sync-engine.js.map +1 -0
  137. package/dist/src/core/sync/sync-event-logger.d.ts +99 -0
  138. package/dist/src/core/sync/sync-event-logger.d.ts.map +1 -0
  139. package/dist/src/core/sync/sync-event-logger.js +103 -0
  140. package/dist/src/core/sync/sync-event-logger.js.map +1 -0
  141. package/dist/src/core/sync/workflow-detector.d.ts +95 -0
  142. package/dist/src/core/sync/workflow-detector.d.ts.map +1 -0
  143. package/dist/src/core/sync/workflow-detector.js +175 -0
  144. package/dist/src/core/sync/workflow-detector.js.map +1 -0
  145. package/dist/src/core/types/config.d.ts.map +1 -1
  146. package/dist/src/core/types/config.js +31 -0
  147. package/dist/src/core/types/config.js.map +1 -1
  148. package/dist/src/utils/github-url.d.ts +53 -0
  149. package/dist/src/utils/github-url.d.ts.map +1 -0
  150. package/dist/src/utils/github-url.js +90 -0
  151. package/dist/src/utils/github-url.js.map +1 -0
  152. package/dist/src/utils/plugin-validator.d.ts +9 -0
  153. package/dist/src/utils/plugin-validator.d.ts.map +1 -1
  154. package/dist/src/utils/plugin-validator.js +86 -19
  155. package/dist/src/utils/plugin-validator.js.map +1 -1
  156. package/dist/src/utils/spec-parser.d.ts +145 -0
  157. package/dist/src/utils/spec-parser.d.ts.map +1 -0
  158. package/dist/src/utils/spec-parser.js +640 -0
  159. package/dist/src/utils/spec-parser.js.map +1 -0
  160. package/dist/tsconfig.tsbuildinfo +1 -0
  161. package/package.json +1 -1
  162. package/plugins/specweave/agents/pm/AGENT.md +1 -1
  163. package/plugins/specweave/agents/pm/templates/increment-spec.md +158 -0
  164. package/plugins/specweave/agents/pm/templates/living-docs-spec.md +113 -0
  165. package/plugins/specweave/commands/specweave-done.md +163 -0
  166. package/plugins/specweave/hooks/lib/update-status-line.sh +79 -111
  167. package/plugins/specweave/hooks/post-increment-planning.sh +107 -35
  168. package/plugins/specweave/lib/hooks/sync-living-docs.js +139 -34
  169. package/plugins/specweave/lib/hooks/sync-living-docs.ts +234 -38
  170. package/plugins/specweave/skills/SKILLS-INDEX.md +4 -24
  171. package/plugins/specweave/skills/increment-planner/SKILL.md +94 -0
  172. package/plugins/specweave/skills/increment-work-router/SKILL.md +466 -0
  173. package/plugins/specweave/skills/plugin-validator/SKILL.md +16 -13
  174. package/plugins/specweave-ado/lib/ado-status-sync.js +80 -0
  175. package/plugins/specweave-ado/lib/ado-status-sync.ts +121 -0
  176. package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
  177. package/plugins/specweave-github/commands/specweave-github-cleanup-duplicates.md +205 -0
  178. package/plugins/specweave-github/commands/specweave-github-sync-epic.md +248 -0
  179. package/plugins/specweave-github/lib/duplicate-detector.js +370 -0
  180. package/plugins/specweave-github/lib/duplicate-detector.ts +525 -0
  181. package/plugins/specweave-github/lib/enhanced-github-sync.js +220 -0
  182. package/plugins/specweave-github/lib/enhanced-github-sync.ts +322 -0
  183. package/plugins/specweave-github/lib/github-client.js +21 -10
  184. package/plugins/specweave-github/lib/github-client.ts +27 -16
  185. package/plugins/specweave-github/lib/github-epic-sync.js +489 -0
  186. package/plugins/specweave-github/lib/github-epic-sync.ts +690 -0
  187. package/plugins/specweave-github/lib/github-status-sync.js +71 -0
  188. package/plugins/specweave-github/lib/github-status-sync.ts +107 -0
  189. package/plugins/specweave-github/lib/task-sync.js +33 -2
  190. package/plugins/specweave-github/lib/task-sync.ts +44 -2
  191. package/plugins/specweave-jira/commands/specweave-jira-sync-epic.md +267 -0
  192. package/plugins/specweave-jira/lib/enhanced-jira-sync.ts.disabled +222 -0
  193. package/plugins/specweave-jira/lib/jira-epic-sync.js +304 -0
  194. package/plugins/specweave-jira/lib/jira-epic-sync.ts +459 -0
  195. package/plugins/specweave-jira/lib/jira-status-sync.js +79 -0
  196. package/plugins/specweave-jira/lib/jira-status-sync.ts +139 -0
  197. package/src/templates/AGENTS.md.template +88 -1
  198. package/src/templates/CLAUDE.md.template +49 -0
  199. package/plugins/specweave/skills/increment-quality-judge/SKILL.md +0 -524
  200. package/plugins/specweave/skills/plugin-installer/SKILL.md +0 -353
@@ -0,0 +1,525 @@
1
+ /**
2
+ * Global Duplicate Detection System for GitHub Issues
3
+ *
4
+ * CRITICAL ARCHITECTURE:
5
+ * ALL GitHub issue creation in SpecWeave MUST use this module.
6
+ * This is the SINGLE SOURCE OF TRUTH for duplicate prevention.
7
+ *
8
+ * THREE-PHASE PROTECTION:
9
+ * 1. Detection (Before Create): Search GitHub for existing issues
10
+ * 2. Verification (After Create): Count check to detect duplicates
11
+ * 3. Reflection (Auto-Correct): Close duplicate issues automatically
12
+ *
13
+ * USAGE:
14
+ * ```typescript
15
+ * // Simple: Create with full protection
16
+ * const result = await DuplicateDetector.createWithProtection({
17
+ * title: '[FS-031] Feature Title',
18
+ * body: 'Description...',
19
+ * titlePattern: '[FS-031]',
20
+ * labels: ['specweave', 'feature']
21
+ * });
22
+ *
23
+ * // Manual: Check before creating
24
+ * const existing = await DuplicateDetector.checkBeforeCreate('[FS-031]');
25
+ * if (!existing) {
26
+ * // Create issue...
27
+ * }
28
+ * ```
29
+ *
30
+ * @module duplicate-detector
31
+ */
32
+
33
+ import { execFileSync } from 'child_process';
34
+
35
+ // ============================================================================
36
+ // TYPE DEFINITIONS
37
+ // ============================================================================
38
+
39
+ export interface GitHubIssue {
40
+ number: number;
41
+ title: string;
42
+ url: string;
43
+ body?: string;
44
+ state?: 'open' | 'closed';
45
+ createdAt?: string;
46
+ }
47
+
48
+ export interface VerificationResult {
49
+ success: boolean;
50
+ expectedCount: number;
51
+ actualCount: number;
52
+ duplicates: GitHubIssue[];
53
+ message: string;
54
+ }
55
+
56
+ export interface CorrectionResult {
57
+ success: boolean;
58
+ duplicatesClosed: number;
59
+ keptIssue: number;
60
+ errors: string[];
61
+ }
62
+
63
+ export interface CreateOptions {
64
+ title: string;
65
+ body: string;
66
+ titlePattern: string; // e.g., "[FS-031]" or "[INC-0031]"
67
+ incrementId?: string; // e.g., "0031-feature-name"
68
+ labels?: string[];
69
+ milestone?: string;
70
+ assignees?: string[];
71
+ repo?: string; // Format: "owner/repo", defaults to current repo
72
+ }
73
+
74
+ export interface CreateResult {
75
+ issue: GitHubIssue;
76
+ duplicatesFound: number;
77
+ duplicatesClosed: number;
78
+ wasReused: boolean; // true if existing issue was reused instead of creating new
79
+ }
80
+
81
+ // ============================================================================
82
+ // MAIN CLASS
83
+ // ============================================================================
84
+
85
+ export class DuplicateDetector {
86
+ /**
87
+ * PHASE 1: Detection (Before Creating Issue)
88
+ *
89
+ * Searches GitHub for existing issues matching the title pattern.
90
+ * This is the PRIMARY defense against duplicates.
91
+ *
92
+ * @param titlePattern - Pattern to search (e.g., "[FS-031]" or "[INC-0031]")
93
+ * @param incrementId - Optional increment ID for more precise matching
94
+ * @param repo - Optional repo (format: "owner/repo")
95
+ * @returns Existing issue if found, null otherwise
96
+ */
97
+ static async checkBeforeCreate(
98
+ titlePattern: string,
99
+ incrementId?: string,
100
+ repo?: string
101
+ ): Promise<GitHubIssue | null> {
102
+ console.log(`šŸ” DETECTION: Checking for existing issue with pattern: ${titlePattern}`);
103
+
104
+ try {
105
+ const args = [
106
+ 'issue',
107
+ 'list',
108
+ '--search', `"${titlePattern}" in:title`,
109
+ '--json', 'number,title,url,body,createdAt',
110
+ '--limit', '20',
111
+ '--state', 'all' // Check both open and closed
112
+ ];
113
+
114
+ if (repo) {
115
+ args.push('--repo', repo);
116
+ }
117
+
118
+ const output = execFileSync('gh', args, { encoding: 'utf-8' });
119
+ const issues = JSON.parse(output) as GitHubIssue[];
120
+
121
+ if (issues.length === 0) {
122
+ console.log(' āœ… No existing issues found (safe to create)');
123
+ return null;
124
+ }
125
+
126
+ console.log(` šŸ“‹ Found ${issues.length} issue(s) matching pattern`);
127
+
128
+ // Strategy 1: Exact title match
129
+ const exactMatch = issues.find(issue =>
130
+ issue.title.includes(titlePattern)
131
+ );
132
+
133
+ if (exactMatch) {
134
+ console.log(` āš ļø DUPLICATE DETECTED: Issue #${exactMatch.number}`);
135
+ console.log(` šŸ“Ž URL: ${exactMatch.url}`);
136
+ return exactMatch;
137
+ }
138
+
139
+ // Strategy 2: Body match (if incrementId provided)
140
+ if (incrementId) {
141
+ const bodyMatch = issues.find(issue =>
142
+ issue.body && (
143
+ issue.body.includes(`**Increment**: ${incrementId}`) ||
144
+ issue.body.includes(incrementId)
145
+ )
146
+ );
147
+
148
+ if (bodyMatch) {
149
+ console.log(` āš ļø DUPLICATE DETECTED (body match): Issue #${bodyMatch.number}`);
150
+ console.log(` šŸ“Ž URL: ${bodyMatch.url}`);
151
+ return bodyMatch;
152
+ }
153
+ }
154
+
155
+ console.log(' āœ… No exact match found (safe to create)');
156
+ return null;
157
+
158
+ } catch (error: any) {
159
+ console.warn(` āš ļø Detection failed (continuing anyway): ${error.message}`);
160
+ return null; // Fail gracefully - allow creation to proceed
161
+ }
162
+ }
163
+
164
+ /**
165
+ * PHASE 2: Verification (After Creating Issue)
166
+ *
167
+ * Counts issues matching the pattern and identifies duplicates.
168
+ * This is the SECONDARY defense - catches duplicates that slipped through.
169
+ *
170
+ * @param titlePattern - Pattern to search (e.g., "[FS-031]")
171
+ * @param expectedCount - Expected number of issues (usually 1)
172
+ * @param repo - Optional repo (format: "owner/repo")
173
+ * @returns Verification result with duplicate list
174
+ */
175
+ static async verifyAfterCreate(
176
+ titlePattern: string,
177
+ expectedCount: number = 1,
178
+ repo?: string
179
+ ): Promise<VerificationResult> {
180
+ console.log(`\nšŸ” VERIFICATION: Checking issue count for pattern: ${titlePattern}`);
181
+
182
+ try {
183
+ const args = [
184
+ 'issue',
185
+ 'list',
186
+ '--search', `"${titlePattern}" in:title`,
187
+ '--json', 'number,title,url,createdAt',
188
+ '--limit', '50',
189
+ '--state', 'all'
190
+ ];
191
+
192
+ if (repo) {
193
+ args.push('--repo', repo);
194
+ }
195
+
196
+ const output = execFileSync('gh', args, { encoding: 'utf-8' });
197
+ const issues = JSON.parse(output) as GitHubIssue[];
198
+
199
+ // Filter exact matches only
200
+ const exactMatches = issues.filter(issue =>
201
+ issue.title.includes(titlePattern)
202
+ );
203
+
204
+ const actualCount = exactMatches.length;
205
+
206
+ console.log(` Expected: ${expectedCount} issue(s)`);
207
+ console.log(` Actual: ${actualCount} issue(s)`);
208
+
209
+ if (actualCount === expectedCount) {
210
+ console.log(` āœ… VERIFICATION PASSED: Count matches!`);
211
+ return {
212
+ success: true,
213
+ expectedCount,
214
+ actualCount,
215
+ duplicates: [],
216
+ message: 'Verification passed'
217
+ };
218
+ } else if (actualCount > expectedCount) {
219
+ // DUPLICATES DETECTED!
220
+ console.warn(` āš ļø VERIFICATION FAILED: ${actualCount - expectedCount} duplicate(s) detected!`);
221
+
222
+ // Sort by creation date (oldest first)
223
+ const sorted = exactMatches.sort((a, b) => {
224
+ const dateA = new Date(a.createdAt || 0).getTime();
225
+ const dateB = new Date(b.createdAt || 0).getTime();
226
+ return dateA - dateB;
227
+ });
228
+
229
+ // Keep first (oldest), mark rest as duplicates
230
+ const duplicates = sorted.slice(1);
231
+
232
+ console.warn(` šŸ“‹ Duplicate issues:`);
233
+ duplicates.forEach(dup => {
234
+ console.warn(` - #${dup.number}: ${dup.title}`);
235
+ });
236
+
237
+ return {
238
+ success: false,
239
+ expectedCount,
240
+ actualCount,
241
+ duplicates,
242
+ message: `${duplicates.length} duplicate(s) found`
243
+ };
244
+ } else {
245
+ console.warn(` āš ļø VERIFICATION WARNING: Expected ${expectedCount} but found ${actualCount}`);
246
+ return {
247
+ success: false,
248
+ expectedCount,
249
+ actualCount,
250
+ duplicates: [],
251
+ message: 'Count mismatch (fewer than expected)'
252
+ };
253
+ }
254
+
255
+ } catch (error: any) {
256
+ console.error(` āŒ Verification failed: ${error.message}`);
257
+ return {
258
+ success: false,
259
+ expectedCount,
260
+ actualCount: -1,
261
+ duplicates: [],
262
+ message: `Verification error: ${error.message}`
263
+ };
264
+ }
265
+ }
266
+
267
+ /**
268
+ * PHASE 3: Reflection (Auto-Correct Duplicates)
269
+ *
270
+ * Automatically closes duplicate issues and keeps the oldest one.
271
+ * This is the CLEANUP phase - fixes problems that occurred.
272
+ *
273
+ * @param duplicates - List of duplicate issues to close
274
+ * @param keepIssueNumber - Issue number to keep (usually the oldest)
275
+ * @param repo - Optional repo (format: "owner/repo")
276
+ * @returns Correction result with count of closed issues
277
+ */
278
+ static async correctDuplicates(
279
+ duplicates: GitHubIssue[],
280
+ keepIssueNumber: number,
281
+ repo?: string
282
+ ): Promise<CorrectionResult> {
283
+ if (duplicates.length === 0) {
284
+ return {
285
+ success: true,
286
+ duplicatesClosed: 0,
287
+ keptIssue: keepIssueNumber,
288
+ errors: []
289
+ };
290
+ }
291
+
292
+ console.log(`\nšŸ”§ REFLECTION: Auto-correcting ${duplicates.length} duplicate(s)...`);
293
+
294
+ const errors: string[] = [];
295
+ let closed = 0;
296
+
297
+ for (const duplicate of duplicates) {
298
+ try {
299
+ console.log(` šŸ—‘ļø Closing duplicate #${duplicate.number}...`);
300
+
301
+ const comment = `Duplicate of #${keepIssueNumber}
302
+
303
+ This issue was automatically closed by SpecWeave's Global Duplicate Detection System.
304
+
305
+ The original issue (#${keepIssueNumber}) should be used for tracking instead.
306
+
307
+ šŸ¤– Auto-closed by SpecWeave`;
308
+
309
+ const commentArgs = [
310
+ 'issue',
311
+ 'comment',
312
+ duplicate.number.toString(),
313
+ '--body', comment
314
+ ];
315
+
316
+ if (repo) {
317
+ commentArgs.push('--repo', repo);
318
+ }
319
+
320
+ // Add comment explaining closure
321
+ execFileSync('gh', commentArgs, { encoding: 'utf-8' });
322
+
323
+ const closeArgs = [
324
+ 'issue',
325
+ 'close',
326
+ duplicate.number.toString()
327
+ ];
328
+
329
+ if (repo) {
330
+ closeArgs.push('--repo', repo);
331
+ }
332
+
333
+ // Close the issue
334
+ execFileSync('gh', closeArgs, { encoding: 'utf-8' });
335
+
336
+ console.log(` āœ… Closed #${duplicate.number}`);
337
+ closed++;
338
+
339
+ } catch (error: any) {
340
+ const errorMsg = `Failed to close #${duplicate.number}: ${error.message}`;
341
+ console.error(` āŒ ${errorMsg}`);
342
+ errors.push(errorMsg);
343
+ }
344
+ }
345
+
346
+ console.log(`\n āœ… REFLECTION COMPLETE: Kept #${keepIssueNumber}, closed ${closed}/${duplicates.length} duplicate(s)`);
347
+
348
+ return {
349
+ success: errors.length === 0,
350
+ duplicatesClosed: closed,
351
+ keptIssue: keepIssueNumber,
352
+ errors
353
+ };
354
+ }
355
+
356
+ /**
357
+ * ALL-IN-ONE: Create Issue with Full Protection
358
+ *
359
+ * This is the RECOMMENDED way to create GitHub issues in SpecWeave.
360
+ * Combines all 3 phases: Detection → Creation → Verification → Reflection
361
+ *
362
+ * GUARANTEES:
363
+ * - No duplicates will be created
364
+ * - Existing duplicates will be detected and closed
365
+ * - Idempotent: can run multiple times safely
366
+ *
367
+ * @param options - Create options (title, body, labels, etc.)
368
+ * @returns Create result with issue details and duplicate stats
369
+ */
370
+ static async createWithProtection(options: CreateOptions): Promise<CreateResult> {
371
+ const {
372
+ title,
373
+ body,
374
+ titlePattern,
375
+ incrementId,
376
+ labels = ['specweave'],
377
+ milestone,
378
+ assignees = [],
379
+ repo
380
+ } = options;
381
+
382
+ console.log(`\nšŸ›”ļø Creating GitHub issue with FULL PROTECTION...`);
383
+ console.log(` Title: ${title}`);
384
+ console.log(` Pattern: ${titlePattern}`);
385
+
386
+ // PHASE 1: Detection (Check if issue already exists)
387
+ console.log(`\n━━━ PHASE 1: DETECTION ━━━`);
388
+ const existing = await this.checkBeforeCreate(titlePattern, incrementId, repo);
389
+
390
+ let issueNumber: number;
391
+ let issueUrl: string;
392
+ let wasReused = false;
393
+
394
+ if (existing) {
395
+ // Reuse existing issue instead of creating duplicate
396
+ console.log(`\nā™»ļø Using existing issue #${existing.number} (skipping creation)`);
397
+ issueNumber = existing.number;
398
+ issueUrl = existing.url;
399
+ wasReused = true;
400
+ } else {
401
+ // PHASE 2: Creation (No duplicate found, safe to create)
402
+ console.log(`\n━━━ PHASE 2: CREATION ━━━`);
403
+ console.log(` Creating new GitHub issue...`);
404
+
405
+ try {
406
+ const args = [
407
+ 'issue',
408
+ 'create',
409
+ '--title', title,
410
+ '--body', body
411
+ ];
412
+
413
+ if (repo) {
414
+ args.push('--repo', repo);
415
+ }
416
+
417
+ // Add labels
418
+ labels.forEach(label => {
419
+ args.push('--label', label);
420
+ });
421
+
422
+ // Add milestone if provided
423
+ if (milestone) {
424
+ args.push('--milestone', milestone);
425
+ }
426
+
427
+ // Add assignees if provided
428
+ assignees.forEach(assignee => {
429
+ args.push('--assignee', assignee);
430
+ });
431
+
432
+ const output = execFileSync('gh', args, { encoding: 'utf-8' });
433
+
434
+ // Extract issue number from output (format: "https://github.com/owner/repo/issues/123")
435
+ const match = output.match(/\/issues\/(\d+)/);
436
+ if (!match) {
437
+ throw new Error('Could not extract issue number from gh CLI output');
438
+ }
439
+
440
+ issueNumber = parseInt(match[1], 10);
441
+ issueUrl = output.trim();
442
+
443
+ console.log(` āœ… Created issue #${issueNumber}`);
444
+ console.log(` šŸ“Ž ${issueUrl}`);
445
+
446
+ } catch (error: any) {
447
+ throw new Error(`Failed to create GitHub issue: ${error.message}`);
448
+ }
449
+ }
450
+
451
+ // PHASE 3: Verification (Count check for duplicates)
452
+ console.log(`\n━━━ PHASE 3: VERIFICATION ━━━`);
453
+ const verification = await this.verifyAfterCreate(titlePattern, 1, repo);
454
+
455
+ let duplicatesClosed = 0;
456
+
457
+ if (!verification.success && verification.duplicates.length > 0) {
458
+ // PHASE 4: Reflection (Auto-correct duplicates)
459
+ console.log(`\n━━━ PHASE 4: REFLECTION ━━━`);
460
+ console.warn(` āš ļø ${verification.duplicates.length} duplicate(s) detected!`);
461
+
462
+ const correction = await this.correctDuplicates(
463
+ verification.duplicates,
464
+ issueNumber,
465
+ repo
466
+ );
467
+
468
+ duplicatesClosed = correction.duplicatesClosed;
469
+
470
+ if (correction.errors.length > 0) {
471
+ console.warn(` āš ļø Some duplicates could not be closed:`);
472
+ correction.errors.forEach(err => console.warn(` - ${err}`));
473
+ }
474
+ } else if (verification.success) {
475
+ console.log(` āœ… No duplicates detected!`);
476
+ }
477
+
478
+ // Final Summary
479
+ console.log(`\nāœ… Issue creation complete!`);
480
+ console.log(` Issue: #${issueNumber}`);
481
+ console.log(` Duplicates found: ${verification.duplicates.length}`);
482
+ console.log(` Duplicates closed: ${duplicatesClosed}`);
483
+ console.log(` Reused existing: ${wasReused ? 'Yes' : 'No'}`);
484
+
485
+ return {
486
+ issue: {
487
+ number: issueNumber,
488
+ title,
489
+ url: issueUrl
490
+ },
491
+ duplicatesFound: verification.duplicates.length,
492
+ duplicatesClosed,
493
+ wasReused
494
+ };
495
+ }
496
+
497
+ /**
498
+ * Utility: Extract title pattern from full title
499
+ *
500
+ * Examples:
501
+ * - "[FS-031] Feature Title" → "[FS-031]"
502
+ * - "[INC-0031] Increment Title" → "[INC-0031]"
503
+ *
504
+ * @param title - Full issue title
505
+ * @returns Extracted pattern or null
506
+ */
507
+ static extractTitlePattern(title: string): string | null {
508
+ const match = title.match(/^(\[[^\]]+\])/);
509
+ return match ? match[1] : null;
510
+ }
511
+
512
+ /**
513
+ * Utility: Check if GitHub CLI is available and authenticated
514
+ *
515
+ * @returns true if gh CLI is ready, false otherwise
516
+ */
517
+ static checkGitHubCLI(): boolean {
518
+ try {
519
+ execFileSync('gh', ['auth', 'status'], { encoding: 'utf-8' });
520
+ return true;
521
+ } catch {
522
+ return false;
523
+ }
524
+ }
525
+ }