specweave 0.17.16 → 0.18.0

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 (161) hide show
  1. package/CLAUDE.md +405 -2495
  2. package/README.md +92 -2
  3. package/dist/plugins/specweave/lib/hooks/sync-living-docs.d.ts.map +1 -1
  4. package/dist/plugins/specweave/lib/hooks/sync-living-docs.js +188 -36
  5. package/dist/plugins/specweave/lib/hooks/sync-living-docs.js.map +1 -1
  6. package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts +54 -0
  7. package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts.map +1 -0
  8. package/dist/plugins/specweave-ado/lib/ado-status-sync.js +86 -0
  9. package/dist/plugins/specweave-ado/lib/ado-status-sync.js.map +1 -0
  10. package/dist/plugins/specweave-github/lib/duplicate-detector.d.ts +139 -0
  11. package/dist/plugins/specweave-github/lib/duplicate-detector.d.ts.map +1 -0
  12. package/dist/plugins/specweave-github/lib/duplicate-detector.js +389 -0
  13. package/dist/plugins/specweave-github/lib/duplicate-detector.js.map +1 -0
  14. package/dist/plugins/specweave-github/lib/enhanced-github-sync.d.ts +26 -0
  15. package/dist/plugins/specweave-github/lib/enhanced-github-sync.d.ts.map +1 -0
  16. package/dist/plugins/specweave-github/lib/enhanced-github-sync.js +249 -0
  17. package/dist/plugins/specweave-github/lib/enhanced-github-sync.js.map +1 -0
  18. package/dist/plugins/specweave-github/lib/github-client.d.ts +1 -1
  19. package/dist/plugins/specweave-github/lib/github-client.d.ts.map +1 -1
  20. package/dist/plugins/specweave-github/lib/github-client.js +25 -13
  21. package/dist/plugins/specweave-github/lib/github-client.js.map +1 -1
  22. package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts +83 -0
  23. package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts.map +1 -0
  24. package/dist/plugins/specweave-github/lib/github-epic-sync.js +451 -0
  25. package/dist/plugins/specweave-github/lib/github-epic-sync.js.map +1 -0
  26. package/dist/plugins/specweave-github/lib/github-status-sync.d.ts +43 -0
  27. package/dist/plugins/specweave-github/lib/github-status-sync.d.ts.map +1 -0
  28. package/dist/plugins/specweave-github/lib/github-status-sync.js +82 -0
  29. package/dist/plugins/specweave-github/lib/github-status-sync.js.map +1 -0
  30. package/dist/plugins/specweave-github/lib/task-sync.d.ts +5 -0
  31. package/dist/plugins/specweave-github/lib/task-sync.d.ts.map +1 -1
  32. package/dist/plugins/specweave-github/lib/task-sync.js +38 -2
  33. package/dist/plugins/specweave-github/lib/task-sync.js.map +1 -1
  34. package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts +66 -0
  35. package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts.map +1 -0
  36. package/dist/plugins/specweave-jira/lib/jira-epic-sync.js +274 -0
  37. package/dist/plugins/specweave-jira/lib/jira-epic-sync.js.map +1 -0
  38. package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +56 -0
  39. package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -0
  40. package/dist/plugins/specweave-jira/lib/jira-status-sync.js +93 -0
  41. package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -0
  42. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  43. package/dist/src/cli/helpers/issue-tracker/index.js +48 -3
  44. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  45. package/dist/src/core/living-docs/hierarchy-mapper.d.ts +142 -0
  46. package/dist/src/core/living-docs/hierarchy-mapper.d.ts.map +1 -0
  47. package/dist/src/core/living-docs/hierarchy-mapper.js +453 -0
  48. package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -0
  49. package/dist/src/core/living-docs/index.d.ts +10 -84
  50. package/dist/src/core/living-docs/index.d.ts.map +1 -1
  51. package/dist/src/core/living-docs/index.js +10 -164
  52. package/dist/src/core/living-docs/index.js.map +1 -1
  53. package/dist/src/core/living-docs/spec-distributor.d.ts +106 -0
  54. package/dist/src/core/living-docs/spec-distributor.d.ts.map +1 -0
  55. package/dist/src/core/living-docs/spec-distributor.js +823 -0
  56. package/dist/src/core/living-docs/spec-distributor.js.map +1 -0
  57. package/dist/src/core/living-docs/types.d.ts +201 -0
  58. package/dist/src/core/living-docs/types.d.ts.map +1 -0
  59. package/dist/src/core/living-docs/types.js +15 -0
  60. package/dist/src/core/living-docs/types.js.map +1 -0
  61. package/dist/src/core/logging/prompt-logger.d.ts +70 -0
  62. package/dist/src/core/logging/prompt-logger.d.ts.map +1 -0
  63. package/dist/src/core/logging/prompt-logger.js +247 -0
  64. package/dist/src/core/logging/prompt-logger.js.map +1 -0
  65. package/dist/src/core/status-line/status-line-manager.d.ts +15 -24
  66. package/dist/src/core/status-line/status-line-manager.d.ts.map +1 -1
  67. package/dist/src/core/status-line/status-line-manager.js +33 -70
  68. package/dist/src/core/status-line/status-line-manager.js.map +1 -1
  69. package/dist/src/core/status-line/types.d.ts +19 -31
  70. package/dist/src/core/status-line/types.d.ts.map +1 -1
  71. package/dist/src/core/status-line/types.js +5 -9
  72. package/dist/src/core/status-line/types.js.map +1 -1
  73. package/dist/src/core/sync/conflict-resolver.d.ts +66 -0
  74. package/dist/src/core/sync/conflict-resolver.d.ts.map +1 -0
  75. package/dist/src/core/sync/conflict-resolver.js +108 -0
  76. package/dist/src/core/sync/conflict-resolver.js.map +1 -0
  77. package/dist/src/core/sync/enhanced-content-builder.d.ts +77 -0
  78. package/dist/src/core/sync/enhanced-content-builder.d.ts.map +1 -0
  79. package/dist/src/core/sync/enhanced-content-builder.js +199 -0
  80. package/dist/src/core/sync/enhanced-content-builder.js.map +1 -0
  81. package/dist/src/core/sync/label-detector.d.ts +66 -0
  82. package/dist/src/core/sync/label-detector.d.ts.map +1 -0
  83. package/dist/src/core/sync/label-detector.js +211 -0
  84. package/dist/src/core/sync/label-detector.js.map +1 -0
  85. package/dist/src/core/sync/retry-logic.d.ts +64 -0
  86. package/dist/src/core/sync/retry-logic.d.ts.map +1 -0
  87. package/dist/src/core/sync/retry-logic.js +165 -0
  88. package/dist/src/core/sync/retry-logic.js.map +1 -0
  89. package/dist/src/core/sync/spec-increment-mapper.d.ts +100 -0
  90. package/dist/src/core/sync/spec-increment-mapper.d.ts.map +1 -0
  91. package/dist/src/core/sync/spec-increment-mapper.js +424 -0
  92. package/dist/src/core/sync/spec-increment-mapper.js.map +1 -0
  93. package/dist/src/core/sync/status-cache.d.ts +91 -0
  94. package/dist/src/core/sync/status-cache.d.ts.map +1 -0
  95. package/dist/src/core/sync/status-cache.js +140 -0
  96. package/dist/src/core/sync/status-cache.js.map +1 -0
  97. package/dist/src/core/sync/status-mapper.d.ts +69 -0
  98. package/dist/src/core/sync/status-mapper.d.ts.map +1 -0
  99. package/dist/src/core/sync/status-mapper.js +90 -0
  100. package/dist/src/core/sync/status-mapper.js.map +1 -0
  101. package/dist/src/core/sync/status-sync-engine.d.ts +162 -0
  102. package/dist/src/core/sync/status-sync-engine.d.ts.map +1 -0
  103. package/dist/src/core/sync/status-sync-engine.js +347 -0
  104. package/dist/src/core/sync/status-sync-engine.js.map +1 -0
  105. package/dist/src/core/sync/sync-event-logger.d.ts +99 -0
  106. package/dist/src/core/sync/sync-event-logger.d.ts.map +1 -0
  107. package/dist/src/core/sync/sync-event-logger.js +103 -0
  108. package/dist/src/core/sync/sync-event-logger.js.map +1 -0
  109. package/dist/src/core/sync/workflow-detector.d.ts +95 -0
  110. package/dist/src/core/sync/workflow-detector.d.ts.map +1 -0
  111. package/dist/src/core/sync/workflow-detector.js +175 -0
  112. package/dist/src/core/sync/workflow-detector.js.map +1 -0
  113. package/dist/src/core/types/config.d.ts.map +1 -1
  114. package/dist/src/core/types/config.js +31 -0
  115. package/dist/src/core/types/config.js.map +1 -1
  116. package/dist/src/utils/github-url.d.ts +53 -0
  117. package/dist/src/utils/github-url.d.ts.map +1 -0
  118. package/dist/src/utils/github-url.js +90 -0
  119. package/dist/src/utils/github-url.js.map +1 -0
  120. package/dist/src/utils/spec-parser.d.ts +145 -0
  121. package/dist/src/utils/spec-parser.d.ts.map +1 -0
  122. package/dist/src/utils/spec-parser.js +640 -0
  123. package/dist/src/utils/spec-parser.js.map +1 -0
  124. package/package.json +1 -1
  125. package/plugins/specweave/agents/pm/AGENT.md +1 -1
  126. package/plugins/specweave/agents/pm/templates/increment-spec.md +158 -0
  127. package/plugins/specweave/agents/pm/templates/living-docs-spec.md +113 -0
  128. package/plugins/specweave/commands/specweave-done.md +163 -0
  129. package/plugins/specweave/hooks/lib/update-status-line.sh +79 -111
  130. package/plugins/specweave/hooks/post-increment-planning.sh +107 -35
  131. package/plugins/specweave/lib/hooks/sync-living-docs.js +139 -34
  132. package/plugins/specweave/lib/hooks/sync-living-docs.ts +234 -38
  133. package/plugins/specweave/skills/SKILLS-INDEX.md +4 -24
  134. package/plugins/specweave/skills/increment-planner/SKILL.md +94 -0
  135. package/plugins/specweave/skills/increment-work-router/SKILL.md +466 -0
  136. package/plugins/specweave-ado/lib/ado-status-sync.js +80 -0
  137. package/plugins/specweave-ado/lib/ado-status-sync.ts +121 -0
  138. package/plugins/specweave-github/commands/specweave-github-cleanup-duplicates.md +205 -0
  139. package/plugins/specweave-github/commands/specweave-github-sync-epic.md +248 -0
  140. package/plugins/specweave-github/lib/duplicate-detector.js +370 -0
  141. package/plugins/specweave-github/lib/duplicate-detector.ts +525 -0
  142. package/plugins/specweave-github/lib/enhanced-github-sync.js +220 -0
  143. package/plugins/specweave-github/lib/enhanced-github-sync.ts +322 -0
  144. package/plugins/specweave-github/lib/github-client.js +21 -10
  145. package/plugins/specweave-github/lib/github-client.ts +27 -16
  146. package/plugins/specweave-github/lib/github-epic-sync.js +489 -0
  147. package/plugins/specweave-github/lib/github-epic-sync.ts +690 -0
  148. package/plugins/specweave-github/lib/github-status-sync.js +71 -0
  149. package/plugins/specweave-github/lib/github-status-sync.ts +107 -0
  150. package/plugins/specweave-github/lib/task-sync.js +33 -2
  151. package/plugins/specweave-github/lib/task-sync.ts +44 -2
  152. package/plugins/specweave-jira/commands/specweave-jira-sync-epic.md +267 -0
  153. package/plugins/specweave-jira/lib/enhanced-jira-sync.ts.disabled +222 -0
  154. package/plugins/specweave-jira/lib/jira-epic-sync.js +304 -0
  155. package/plugins/specweave-jira/lib/jira-epic-sync.ts +459 -0
  156. package/plugins/specweave-jira/lib/jira-status-sync.js +79 -0
  157. package/plugins/specweave-jira/lib/jira-status-sync.ts +139 -0
  158. package/src/templates/AGENTS.md.template +88 -1
  159. package/src/templates/CLAUDE.md.template +49 -0
  160. package/plugins/specweave/skills/increment-quality-judge/SKILL.md +0 -524
  161. package/plugins/specweave/skills/plugin-installer/SKILL.md +0 -353
@@ -0,0 +1,370 @@
1
+ import { execFileSync } from "child_process";
2
+ class DuplicateDetector {
3
+ /**
4
+ * PHASE 1: Detection (Before Creating Issue)
5
+ *
6
+ * Searches GitHub for existing issues matching the title pattern.
7
+ * This is the PRIMARY defense against duplicates.
8
+ *
9
+ * @param titlePattern - Pattern to search (e.g., "[FS-031]" or "[INC-0031]")
10
+ * @param incrementId - Optional increment ID for more precise matching
11
+ * @param repo - Optional repo (format: "owner/repo")
12
+ * @returns Existing issue if found, null otherwise
13
+ */
14
+ static async checkBeforeCreate(titlePattern, incrementId, repo) {
15
+ console.log(`\u{1F50D} DETECTION: Checking for existing issue with pattern: ${titlePattern}`);
16
+ try {
17
+ const args = [
18
+ "issue",
19
+ "list",
20
+ "--search",
21
+ `"${titlePattern}" in:title`,
22
+ "--json",
23
+ "number,title,url,body,createdAt",
24
+ "--limit",
25
+ "20",
26
+ "--state",
27
+ "all"
28
+ // Check both open and closed
29
+ ];
30
+ if (repo) {
31
+ args.push("--repo", repo);
32
+ }
33
+ const output = execFileSync("gh", args, { encoding: "utf-8" });
34
+ const issues = JSON.parse(output);
35
+ if (issues.length === 0) {
36
+ console.log(" \u2705 No existing issues found (safe to create)");
37
+ return null;
38
+ }
39
+ console.log(` \u{1F4CB} Found ${issues.length} issue(s) matching pattern`);
40
+ const exactMatch = issues.find(
41
+ (issue) => issue.title.includes(titlePattern)
42
+ );
43
+ if (exactMatch) {
44
+ console.log(` \u26A0\uFE0F DUPLICATE DETECTED: Issue #${exactMatch.number}`);
45
+ console.log(` \u{1F4CE} URL: ${exactMatch.url}`);
46
+ return exactMatch;
47
+ }
48
+ if (incrementId) {
49
+ const bodyMatch = issues.find(
50
+ (issue) => issue.body && (issue.body.includes(`**Increment**: ${incrementId}`) || issue.body.includes(incrementId))
51
+ );
52
+ if (bodyMatch) {
53
+ console.log(` \u26A0\uFE0F DUPLICATE DETECTED (body match): Issue #${bodyMatch.number}`);
54
+ console.log(` \u{1F4CE} URL: ${bodyMatch.url}`);
55
+ return bodyMatch;
56
+ }
57
+ }
58
+ console.log(" \u2705 No exact match found (safe to create)");
59
+ return null;
60
+ } catch (error) {
61
+ console.warn(` \u26A0\uFE0F Detection failed (continuing anyway): ${error.message}`);
62
+ return null;
63
+ }
64
+ }
65
+ /**
66
+ * PHASE 2: Verification (After Creating Issue)
67
+ *
68
+ * Counts issues matching the pattern and identifies duplicates.
69
+ * This is the SECONDARY defense - catches duplicates that slipped through.
70
+ *
71
+ * @param titlePattern - Pattern to search (e.g., "[FS-031]")
72
+ * @param expectedCount - Expected number of issues (usually 1)
73
+ * @param repo - Optional repo (format: "owner/repo")
74
+ * @returns Verification result with duplicate list
75
+ */
76
+ static async verifyAfterCreate(titlePattern, expectedCount = 1, repo) {
77
+ console.log(`
78
+ \u{1F50D} VERIFICATION: Checking issue count for pattern: ${titlePattern}`);
79
+ try {
80
+ const args = [
81
+ "issue",
82
+ "list",
83
+ "--search",
84
+ `"${titlePattern}" in:title`,
85
+ "--json",
86
+ "number,title,url,createdAt",
87
+ "--limit",
88
+ "50",
89
+ "--state",
90
+ "all"
91
+ ];
92
+ if (repo) {
93
+ args.push("--repo", repo);
94
+ }
95
+ const output = execFileSync("gh", args, { encoding: "utf-8" });
96
+ const issues = JSON.parse(output);
97
+ const exactMatches = issues.filter(
98
+ (issue) => issue.title.includes(titlePattern)
99
+ );
100
+ const actualCount = exactMatches.length;
101
+ console.log(` Expected: ${expectedCount} issue(s)`);
102
+ console.log(` Actual: ${actualCount} issue(s)`);
103
+ if (actualCount === expectedCount) {
104
+ console.log(` \u2705 VERIFICATION PASSED: Count matches!`);
105
+ return {
106
+ success: true,
107
+ expectedCount,
108
+ actualCount,
109
+ duplicates: [],
110
+ message: "Verification passed"
111
+ };
112
+ } else if (actualCount > expectedCount) {
113
+ console.warn(` \u26A0\uFE0F VERIFICATION FAILED: ${actualCount - expectedCount} duplicate(s) detected!`);
114
+ const sorted = exactMatches.sort((a, b) => {
115
+ const dateA = new Date(a.createdAt || 0).getTime();
116
+ const dateB = new Date(b.createdAt || 0).getTime();
117
+ return dateA - dateB;
118
+ });
119
+ const duplicates = sorted.slice(1);
120
+ console.warn(` \u{1F4CB} Duplicate issues:`);
121
+ duplicates.forEach((dup) => {
122
+ console.warn(` - #${dup.number}: ${dup.title}`);
123
+ });
124
+ return {
125
+ success: false,
126
+ expectedCount,
127
+ actualCount,
128
+ duplicates,
129
+ message: `${duplicates.length} duplicate(s) found`
130
+ };
131
+ } else {
132
+ console.warn(` \u26A0\uFE0F VERIFICATION WARNING: Expected ${expectedCount} but found ${actualCount}`);
133
+ return {
134
+ success: false,
135
+ expectedCount,
136
+ actualCount,
137
+ duplicates: [],
138
+ message: "Count mismatch (fewer than expected)"
139
+ };
140
+ }
141
+ } catch (error) {
142
+ console.error(` \u274C Verification failed: ${error.message}`);
143
+ return {
144
+ success: false,
145
+ expectedCount,
146
+ actualCount: -1,
147
+ duplicates: [],
148
+ message: `Verification error: ${error.message}`
149
+ };
150
+ }
151
+ }
152
+ /**
153
+ * PHASE 3: Reflection (Auto-Correct Duplicates)
154
+ *
155
+ * Automatically closes duplicate issues and keeps the oldest one.
156
+ * This is the CLEANUP phase - fixes problems that occurred.
157
+ *
158
+ * @param duplicates - List of duplicate issues to close
159
+ * @param keepIssueNumber - Issue number to keep (usually the oldest)
160
+ * @param repo - Optional repo (format: "owner/repo")
161
+ * @returns Correction result with count of closed issues
162
+ */
163
+ static async correctDuplicates(duplicates, keepIssueNumber, repo) {
164
+ if (duplicates.length === 0) {
165
+ return {
166
+ success: true,
167
+ duplicatesClosed: 0,
168
+ keptIssue: keepIssueNumber,
169
+ errors: []
170
+ };
171
+ }
172
+ console.log(`
173
+ \u{1F527} REFLECTION: Auto-correcting ${duplicates.length} duplicate(s)...`);
174
+ const errors = [];
175
+ let closed = 0;
176
+ for (const duplicate of duplicates) {
177
+ try {
178
+ console.log(` \u{1F5D1}\uFE0F Closing duplicate #${duplicate.number}...`);
179
+ const comment = `Duplicate of #${keepIssueNumber}
180
+
181
+ This issue was automatically closed by SpecWeave's Global Duplicate Detection System.
182
+
183
+ The original issue (#${keepIssueNumber}) should be used for tracking instead.
184
+
185
+ \u{1F916} Auto-closed by SpecWeave`;
186
+ const commentArgs = [
187
+ "issue",
188
+ "comment",
189
+ duplicate.number.toString(),
190
+ "--body",
191
+ comment
192
+ ];
193
+ if (repo) {
194
+ commentArgs.push("--repo", repo);
195
+ }
196
+ execFileSync("gh", commentArgs, { encoding: "utf-8" });
197
+ const closeArgs = [
198
+ "issue",
199
+ "close",
200
+ duplicate.number.toString()
201
+ ];
202
+ if (repo) {
203
+ closeArgs.push("--repo", repo);
204
+ }
205
+ execFileSync("gh", closeArgs, { encoding: "utf-8" });
206
+ console.log(` \u2705 Closed #${duplicate.number}`);
207
+ closed++;
208
+ } catch (error) {
209
+ const errorMsg = `Failed to close #${duplicate.number}: ${error.message}`;
210
+ console.error(` \u274C ${errorMsg}`);
211
+ errors.push(errorMsg);
212
+ }
213
+ }
214
+ console.log(`
215
+ \u2705 REFLECTION COMPLETE: Kept #${keepIssueNumber}, closed ${closed}/${duplicates.length} duplicate(s)`);
216
+ return {
217
+ success: errors.length === 0,
218
+ duplicatesClosed: closed,
219
+ keptIssue: keepIssueNumber,
220
+ errors
221
+ };
222
+ }
223
+ /**
224
+ * ALL-IN-ONE: Create Issue with Full Protection
225
+ *
226
+ * This is the RECOMMENDED way to create GitHub issues in SpecWeave.
227
+ * Combines all 3 phases: Detection → Creation → Verification → Reflection
228
+ *
229
+ * GUARANTEES:
230
+ * - No duplicates will be created
231
+ * - Existing duplicates will be detected and closed
232
+ * - Idempotent: can run multiple times safely
233
+ *
234
+ * @param options - Create options (title, body, labels, etc.)
235
+ * @returns Create result with issue details and duplicate stats
236
+ */
237
+ static async createWithProtection(options) {
238
+ const {
239
+ title,
240
+ body,
241
+ titlePattern,
242
+ incrementId,
243
+ labels = ["specweave"],
244
+ milestone,
245
+ assignees = [],
246
+ repo
247
+ } = options;
248
+ console.log(`
249
+ \u{1F6E1}\uFE0F Creating GitHub issue with FULL PROTECTION...`);
250
+ console.log(` Title: ${title}`);
251
+ console.log(` Pattern: ${titlePattern}`);
252
+ console.log(`
253
+ \u2501\u2501\u2501 PHASE 1: DETECTION \u2501\u2501\u2501`);
254
+ const existing = await this.checkBeforeCreate(titlePattern, incrementId, repo);
255
+ let issueNumber;
256
+ let issueUrl;
257
+ let wasReused = false;
258
+ if (existing) {
259
+ console.log(`
260
+ \u267B\uFE0F Using existing issue #${existing.number} (skipping creation)`);
261
+ issueNumber = existing.number;
262
+ issueUrl = existing.url;
263
+ wasReused = true;
264
+ } else {
265
+ console.log(`
266
+ \u2501\u2501\u2501 PHASE 2: CREATION \u2501\u2501\u2501`);
267
+ console.log(` Creating new GitHub issue...`);
268
+ try {
269
+ const args = [
270
+ "issue",
271
+ "create",
272
+ "--title",
273
+ title,
274
+ "--body",
275
+ body
276
+ ];
277
+ if (repo) {
278
+ args.push("--repo", repo);
279
+ }
280
+ labels.forEach((label) => {
281
+ args.push("--label", label);
282
+ });
283
+ if (milestone) {
284
+ args.push("--milestone", milestone);
285
+ }
286
+ assignees.forEach((assignee) => {
287
+ args.push("--assignee", assignee);
288
+ });
289
+ const output = execFileSync("gh", args, { encoding: "utf-8" });
290
+ const match = output.match(/\/issues\/(\d+)/);
291
+ if (!match) {
292
+ throw new Error("Could not extract issue number from gh CLI output");
293
+ }
294
+ issueNumber = parseInt(match[1], 10);
295
+ issueUrl = output.trim();
296
+ console.log(` \u2705 Created issue #${issueNumber}`);
297
+ console.log(` \u{1F4CE} ${issueUrl}`);
298
+ } catch (error) {
299
+ throw new Error(`Failed to create GitHub issue: ${error.message}`);
300
+ }
301
+ }
302
+ console.log(`
303
+ \u2501\u2501\u2501 PHASE 3: VERIFICATION \u2501\u2501\u2501`);
304
+ const verification = await this.verifyAfterCreate(titlePattern, 1, repo);
305
+ let duplicatesClosed = 0;
306
+ if (!verification.success && verification.duplicates.length > 0) {
307
+ console.log(`
308
+ \u2501\u2501\u2501 PHASE 4: REFLECTION \u2501\u2501\u2501`);
309
+ console.warn(` \u26A0\uFE0F ${verification.duplicates.length} duplicate(s) detected!`);
310
+ const correction = await this.correctDuplicates(
311
+ verification.duplicates,
312
+ issueNumber,
313
+ repo
314
+ );
315
+ duplicatesClosed = correction.duplicatesClosed;
316
+ if (correction.errors.length > 0) {
317
+ console.warn(` \u26A0\uFE0F Some duplicates could not be closed:`);
318
+ correction.errors.forEach((err) => console.warn(` - ${err}`));
319
+ }
320
+ } else if (verification.success) {
321
+ console.log(` \u2705 No duplicates detected!`);
322
+ }
323
+ console.log(`
324
+ \u2705 Issue creation complete!`);
325
+ console.log(` Issue: #${issueNumber}`);
326
+ console.log(` Duplicates found: ${verification.duplicates.length}`);
327
+ console.log(` Duplicates closed: ${duplicatesClosed}`);
328
+ console.log(` Reused existing: ${wasReused ? "Yes" : "No"}`);
329
+ return {
330
+ issue: {
331
+ number: issueNumber,
332
+ title,
333
+ url: issueUrl
334
+ },
335
+ duplicatesFound: verification.duplicates.length,
336
+ duplicatesClosed,
337
+ wasReused
338
+ };
339
+ }
340
+ /**
341
+ * Utility: Extract title pattern from full title
342
+ *
343
+ * Examples:
344
+ * - "[FS-031] Feature Title" → "[FS-031]"
345
+ * - "[INC-0031] Increment Title" → "[INC-0031]"
346
+ *
347
+ * @param title - Full issue title
348
+ * @returns Extracted pattern or null
349
+ */
350
+ static extractTitlePattern(title) {
351
+ const match = title.match(/^(\[[^\]]+\])/);
352
+ return match ? match[1] : null;
353
+ }
354
+ /**
355
+ * Utility: Check if GitHub CLI is available and authenticated
356
+ *
357
+ * @returns true if gh CLI is ready, false otherwise
358
+ */
359
+ static checkGitHubCLI() {
360
+ try {
361
+ execFileSync("gh", ["auth", "status"], { encoding: "utf-8" });
362
+ return true;
363
+ } catch {
364
+ return false;
365
+ }
366
+ }
367
+ }
368
+ export {
369
+ DuplicateDetector
370
+ };