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,248 @@
1
+ ---
2
+ name: specweave-github-sync-epic
3
+ description: Sync SpecWeave Epic folder to GitHub (Milestone + Issues). Implements Universal Hierarchy architecture - Epic → Milestone, Increments → Issues.
4
+ ---
5
+
6
+ # Sync Epic to GitHub (Universal Hierarchy)
7
+
8
+ **Architecture**: Hierarchical sync using Epic folder structure
9
+
10
+ - **Epic (FS-001)** → **GitHub Milestone**
11
+ - **Increment (0001-core-framework)** → **GitHub Issue** (linked to Milestone)
12
+
13
+ ## Usage
14
+
15
+ ```bash
16
+ /specweave-github:sync-epic <epic-id>
17
+ ```
18
+
19
+ ## What It Does
20
+
21
+ **Hierarchical Sync Process**:
22
+
23
+ 1. **Load Epic folder** from `.specweave/docs/internal/specs/FS-XXX-name/`
24
+ 2. **Parse Epic README.md** to get Epic metadata (title, increments, status)
25
+ 3. **Create or update GitHub Milestone**:
26
+ - Title: `[FS-001] Epic Title`
27
+ - Description: Epic overview + progress stats
28
+ - State: Open (active/planning) or Closed (complete)
29
+ 4. **Sync each increment as GitHub Issue**:
30
+ - Title: `[INC-0001-core-framework] Title`
31
+ - Body: Increment overview + link to tasks.md
32
+ - Milestone: Linked to Epic Milestone
33
+ - Labels: `increment`, `epic-sync`
34
+ 5. **Update frontmatter** in Epic README.md and increment files
35
+
36
+ ## Examples
37
+
38
+ ### Sync Epic FS-001 (Core Framework Architecture)
39
+
40
+ ```bash
41
+ /specweave-github:sync-epic FS-001
42
+ ```
43
+
44
+ **Output**:
45
+ ```
46
+ 🔄 Syncing Epic FS-001 to GitHub...
47
+ 📦 Epic: Core Framework Architecture
48
+ 📊 Increments: 4
49
+ 🚀 Creating GitHub Milestone...
50
+ ✅ Created Milestone #10
51
+
52
+ 📝 Syncing 4 increments...
53
+ ✅ Created Issue #130 for 0001-core-framework
54
+ ✅ Created Issue #131 for 0002-core-enhancements
55
+ ✅ Created Issue #132 for 0004-plugin-architecture
56
+ ✅ Created Issue #133 for 0005-cross-platform-cli
57
+
58
+ ✅ Epic sync complete!
59
+ Milestone: https://github.com/owner/repo/milestone/10
60
+ Issues created: 4
61
+ Issues updated: 0
62
+ ```
63
+
64
+ ### Sync Epic with short ID
65
+
66
+ ```bash
67
+ /specweave-github:sync-epic 031
68
+ # Resolves to FS-031
69
+ ```
70
+
71
+ ### Re-sync Epic (updates existing Milestone/Issues)
72
+
73
+ ```bash
74
+ /specweave-github:sync-epic FS-001
75
+ ```
76
+
77
+ **Output**:
78
+ ```
79
+ 🔄 Syncing Epic FS-001 to GitHub...
80
+ ♻️ Updating existing Milestone #10...
81
+ ✅ Updated Milestone #10
82
+
83
+ 📝 Syncing 4 increments...
84
+ ♻️ Updated Issue #130 for 0001-core-framework
85
+ ♻️ Updated Issue #131 for 0002-core-enhancements
86
+ ♻️ Updated Issue #132 for 0004-plugin-architecture
87
+ ♻️ Updated Issue #133 for 0005-cross-platform-cli
88
+
89
+ ✅ Epic sync complete!
90
+ Milestone: https://github.com/owner/repo/milestone/10
91
+ Issues created: 0
92
+ Issues updated: 4
93
+ ```
94
+
95
+ ## Arguments
96
+
97
+ - `<epic-id>` - Epic ID (e.g., `FS-001` or just `001`)
98
+
99
+ ## What Gets Created
100
+
101
+ ### GitHub Milestone (Epic-level)
102
+
103
+ ```
104
+ Title: [FS-001] Core Framework Architecture
105
+ Description:
106
+ Epic: Core Framework Architecture
107
+
108
+ Progress: 4/4 increments (100%)
109
+
110
+ Priority: P0
111
+ Status: complete
112
+
113
+ State: Closed (if complete) or Open (if active/planning)
114
+ ```
115
+
116
+ ### GitHub Issues (Increment-level)
117
+
118
+ ```markdown
119
+ Title: [INC-0001-core-framework] Core Framework
120
+
121
+ # Core Framework
122
+
123
+ Foundation framework with CLI, plugin system, and agent architecture...
124
+
125
+ ---
126
+
127
+ **Increment**: 0001-core-framework
128
+ **Milestone**: See milestone for Epic progress
129
+
130
+ 🤖 Auto-created by SpecWeave Epic Sync
131
+ ```
132
+
133
+ **Labels**: `increment`, `epic-sync`
134
+ **Milestone**: Linked to Epic Milestone
135
+
136
+ ## Frontmatter Updates
137
+
138
+ ### Epic README.md (after sync)
139
+
140
+ ```yaml
141
+ ---
142
+ id: FS-001
143
+ title: "Core Framework Architecture"
144
+ external_tools:
145
+ github:
146
+ type: milestone
147
+ id: 10 # ← Added
148
+ url: https://github.com/.../milestone/10 # ← Added
149
+ increments:
150
+ - id: 0001-core-framework
151
+ external:
152
+ github: 130 # ← Added
153
+ - id: 0002-core-enhancements
154
+ external:
155
+ github: 131 # ← Added
156
+ ---
157
+ ```
158
+
159
+ ### Increment file (0001-core-framework.md)
160
+
161
+ ```yaml
162
+ ---
163
+ id: 0001-core-framework
164
+ epic: FS-001
165
+ external:
166
+ github:
167
+ issue: 130 # ← Added
168
+ url: https://github.com/.../issues/130 # ← Added
169
+ ---
170
+ ```
171
+
172
+ ## Benefits
173
+
174
+ ✅ **Hierarchical tracking**: GitHub Milestones group related increments
175
+ ✅ **Epic-level progress**: See completion percentage in Milestone
176
+ ✅ **Automatic linking**: All Issues linked to Milestone
177
+ ✅ **Idempotent**: Safe to re-run (updates existing Milestone/Issues)
178
+ ✅ **Brownfield-ready**: Links existing GitHub Milestones/Issues
179
+
180
+ ## Requirements
181
+
182
+ 1. **GitHub CLI** (`gh`) installed and authenticated
183
+ 2. **Git repository** with GitHub remote
184
+ 3. **Write access** to repository (for creating Milestones/Issues)
185
+ 4. **Epic folder exists** at `.specweave/docs/internal/specs/FS-XXX-name/`
186
+
187
+ ## Architecture: Why Milestones?
188
+
189
+ **GitHub's Hierarchy**:
190
+ - GitHub Milestones = Epic-level grouping
191
+ - GitHub Issues = Increment-level work items
192
+ - GitHub Projects = Optional (cross-Epic tracking)
193
+
194
+ **Comparison with JIRA/ADO**:
195
+ - JIRA: Epic → Epic, Increment → Story (with Epic Link field)
196
+ - ADO: Epic → Feature, Increment → User Story (with Parent link)
197
+ - GitHub: Epic → Milestone, Increment → Issue (with Milestone link)
198
+
199
+ All three implement the same Universal Hierarchy, just with different terminology.
200
+
201
+ ## Troubleshooting
202
+
203
+ **"Epic FS-001 not found"**:
204
+ - Check Epic folder exists: `ls .specweave/docs/internal/specs/`
205
+ - Verify Epic ID format: `FS-001-epic-name/`
206
+
207
+ **"Epic README.md missing YAML frontmatter"**:
208
+ - Ensure Epic was migrated with `migrate-to-epic-folders.ts`
209
+ - Frontmatter must start with `---` on line 1
210
+
211
+ **"Failed to create GitHub Milestone"**:
212
+ - Check GitHub CLI auth: `gh auth status`
213
+ - Verify write access: `gh repo view`
214
+ - Check rate limits: `gh api rate_limit`
215
+
216
+ **"Could not extract issue number"**:
217
+ - GitHub CLI output format may have changed
218
+ - Check CLI version: `gh --version` (need v2.0.0+)
219
+
220
+ ## Related Commands
221
+
222
+ - `/specweave-github:sync-spec` - OLD (flat spec → project) - DEPRECATED for Epic architecture
223
+ - `/specweave-jira:sync-epic` - Sync to JIRA Epic + Stories
224
+ - `/specweave-ado:sync-epic` - Sync to ADO Feature + User Stories
225
+
226
+ ## Implementation
227
+
228
+ **File**: `plugins/specweave-github/lib/github-epic-sync.ts`
229
+
230
+ **Core Class**: `GitHubEpicSync`
231
+
232
+ **Methods**:
233
+ - `syncEpicToGitHub(epicId)` - Main sync logic
234
+ - `createMilestone(epic)` - Create GitHub Milestone
235
+ - `updateMilestone(number, epic)` - Update existing Milestone
236
+ - `createIssue(increment, milestone)` - Create GitHub Issue
237
+ - `updateIssue(number, increment, milestone)` - Update existing Issue
238
+ - `updateEpicReadme(path, github)` - Update frontmatter
239
+ - `updateIncrementExternalLink(...)` - Update increment frontmatter
240
+
241
+ ## Next Steps
242
+
243
+ After syncing Epic to GitHub:
244
+
245
+ 1. **View Milestone progress**: `gh milestone view 10`
246
+ 2. **List Issues in Milestone**: `gh issue list --milestone 10`
247
+ 3. **Track completion**: GitHub automatically calculates Milestone progress
248
+ 4. **Close Milestone**: When all increments complete, Milestone auto-closes
@@ -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
+ };