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,121 @@
1
+ /**
2
+ * Azure DevOps Status Sync
3
+ *
4
+ * Synchronizes SpecWeave increment statuses with ADO work item states.
5
+ *
6
+ * ADO Work Item State Updates:
7
+ * - Uses JSON Patch format for updates
8
+ * - System.State field controls work item state
9
+ * - Available states: New, Active, On Hold, Resolved, Closed, Removed
10
+ *
11
+ * @module ado-status-sync
12
+ */
13
+
14
+ import axios, { AxiosInstance } from 'axios';
15
+
16
+ /**
17
+ * External status representation (ADO-specific)
18
+ */
19
+ export interface ExternalStatus {
20
+ state: string; // e.g., "New", "Active", "Closed"
21
+ }
22
+
23
+ /**
24
+ * Azure DevOps Status Sync
25
+ *
26
+ * Handles status synchronization with ADO work items.
27
+ */
28
+ export class AdoStatusSync {
29
+ private client: AxiosInstance;
30
+ private organization: string;
31
+ private project: string;
32
+
33
+ constructor(
34
+ organization: string,
35
+ project: string,
36
+ personalAccessToken: string
37
+ ) {
38
+ this.organization = organization;
39
+ this.project = project;
40
+
41
+ // Create ADO API client
42
+ this.client = axios.create({
43
+ baseURL: `https://dev.azure.com/${organization}/${project}/_apis`,
44
+ auth: {
45
+ username: '', // Empty for PAT auth
46
+ password: personalAccessToken
47
+ },
48
+ headers: {
49
+ 'Content-Type': 'application/json-patch+json',
50
+ 'Accept': 'application/json'
51
+ }
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Get current status from ADO work item
57
+ *
58
+ * @param workItemId - ADO work item ID (e.g., 123)
59
+ * @returns Current work item state
60
+ */
61
+ async getStatus(workItemId: number): Promise<ExternalStatus> {
62
+ const response = await this.client.get(
63
+ `/wit/workitems/${workItemId}?api-version=7.0`
64
+ );
65
+
66
+ return {
67
+ state: response.data.fields['System.State']
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Update ADO work item state
73
+ *
74
+ * Uses JSON Patch format to update System.State field.
75
+ *
76
+ * @param workItemId - ADO work item ID (e.g., 123)
77
+ * @param status - Desired status
78
+ */
79
+ async updateStatus(workItemId: number, status: ExternalStatus): Promise<void> {
80
+ // ADO uses JSON Patch format for updates
81
+ const patch = [
82
+ {
83
+ op: 'add',
84
+ path: '/fields/System.State',
85
+ value: status.state
86
+ }
87
+ ];
88
+
89
+ await this.client.patch(
90
+ `/wit/workitems/${workItemId}?api-version=7.0`,
91
+ patch
92
+ );
93
+ }
94
+
95
+ /**
96
+ * Post comment about status change to ADO work item
97
+ *
98
+ * @param workItemId - ADO work item ID (e.g., 123)
99
+ * @param oldStatus - Previous SpecWeave status
100
+ * @param newStatus - New SpecWeave status
101
+ */
102
+ async postStatusComment(
103
+ workItemId: number,
104
+ oldStatus: string,
105
+ newStatus: string
106
+ ): Promise<void> {
107
+ const text = `🔄 Status Update\n\n` +
108
+ `SpecWeave status changed:\n` +
109
+ `• From: ${oldStatus}\n` +
110
+ `• To: ${newStatus}\n` +
111
+ `• When: ${new Date().toISOString()}\n\n` +
112
+ `Synced from SpecWeave`;
113
+
114
+ await this.client.post(
115
+ `/wit/workitems/${workItemId}/comments?api-version=7.0-preview.3`,
116
+ {
117
+ text
118
+ }
119
+ );
120
+ }
121
+ }
@@ -0,0 +1,170 @@
1
+ import { AdoClientV2 } from "./ado-client-v2.js";
2
+ import { EnhancedContentBuilder } from "../../../src/core/sync/enhanced-content-builder.js";
3
+ import { SpecIncrementMapper } from "../../../src/core/sync/spec-increment-mapper.js";
4
+ import { parseSpecContent } from "../../../src/core/spec-content-sync.js";
5
+ import path from "path";
6
+ import fs from "fs/promises";
7
+ async function syncSpecToAdoWithEnhancedContent(options) {
8
+ const { specPath, organization, project, dryRun = false, verbose = false } = options;
9
+ try {
10
+ const baseSpec = await parseSpecContent(specPath);
11
+ if (!baseSpec) {
12
+ return {
13
+ success: false,
14
+ action: "error",
15
+ error: "Failed to parse spec content"
16
+ };
17
+ }
18
+ if (verbose) {
19
+ console.log(`\u{1F4C4} Parsed spec: ${baseSpec.identifier.compact}`);
20
+ }
21
+ const specId = baseSpec.identifier.full || baseSpec.identifier.compact;
22
+ const rootDir = await findSpecWeaveRoot(specPath);
23
+ const mapper = new SpecIncrementMapper(rootDir);
24
+ const mapping = await mapper.mapSpecToIncrements(specId);
25
+ if (verbose) {
26
+ console.log(`\u{1F517} Found ${mapping.increments.length} related increments`);
27
+ }
28
+ const taskMapping = buildTaskMapping(mapping.increments, organization, project);
29
+ const architectureDocs = await findArchitectureDocs(rootDir, specId);
30
+ const enhancedSpec = {
31
+ ...baseSpec,
32
+ summary: baseSpec.description,
33
+ taskMapping,
34
+ architectureDocs
35
+ };
36
+ const builder = new EnhancedContentBuilder();
37
+ const description = builder.buildExternalDescription(enhancedSpec);
38
+ if (verbose) {
39
+ console.log(`\u{1F4DD} Generated description: ${description.length} characters`);
40
+ }
41
+ if (dryRun) {
42
+ console.log("\u{1F50D} DRY RUN - Would create/update feature with:");
43
+ console.log(` Title: ${baseSpec.title}`);
44
+ console.log(` Description length: ${description.length}`);
45
+ return {
46
+ success: true,
47
+ action: "no-change",
48
+ tasksLinked: taskMapping?.tasks.length || 0
49
+ };
50
+ }
51
+ if (!organization || !project) {
52
+ return {
53
+ success: false,
54
+ action: "error",
55
+ error: "Azure DevOps organization/project not specified"
56
+ };
57
+ }
58
+ const profile = {
59
+ provider: "ado",
60
+ displayName: `${organization}/${project}`,
61
+ config: {
62
+ organization,
63
+ project
64
+ },
65
+ timeRange: { default: "1M", max: "6M" }
66
+ };
67
+ const pat = process.env.AZURE_DEVOPS_PAT || "";
68
+ const client = new AdoClientV2(profile, pat);
69
+ const existingFeature = await findExistingFeature(client, baseSpec.identifier.compact);
70
+ let result;
71
+ if (existingFeature) {
72
+ await client.updateWorkItem(existingFeature.id, {
73
+ title: `[${baseSpec.identifier.compact}] ${baseSpec.title}`,
74
+ description
75
+ });
76
+ result = {
77
+ success: true,
78
+ action: "updated",
79
+ featureId: existingFeature.id,
80
+ featureUrl: `https://dev.azure.com/${organization}/${project}/_workitems/edit/${existingFeature.id}`,
81
+ tasksLinked: taskMapping?.tasks.length || 0
82
+ };
83
+ } else {
84
+ const feature = await client.createEpic({
85
+ title: `[${baseSpec.identifier.compact}] ${baseSpec.title}`,
86
+ description,
87
+ tags: ["spec", "external-tool-sync"]
88
+ });
89
+ result = {
90
+ success: true,
91
+ action: "created",
92
+ featureId: feature.id,
93
+ featureUrl: `https://dev.azure.com/${organization}/${project}/_workitems/edit/${feature.id}`,
94
+ tasksLinked: taskMapping?.tasks.length || 0
95
+ };
96
+ }
97
+ if (verbose) {
98
+ console.log(`\u2705 ${result.action === "created" ? "Created" : "Updated"} feature #${result.featureId}`);
99
+ }
100
+ return result;
101
+ } catch (error) {
102
+ return {
103
+ success: false,
104
+ action: "error",
105
+ error: error.message
106
+ };
107
+ }
108
+ }
109
+ async function findSpecWeaveRoot(specPath) {
110
+ let currentDir = path.dirname(specPath);
111
+ while (true) {
112
+ const specweaveDir = path.join(currentDir, ".specweave");
113
+ try {
114
+ await fs.access(specweaveDir);
115
+ return currentDir;
116
+ } catch {
117
+ const parentDir = path.dirname(currentDir);
118
+ if (parentDir === currentDir) {
119
+ throw new Error(".specweave directory not found");
120
+ }
121
+ currentDir = parentDir;
122
+ }
123
+ }
124
+ }
125
+ function buildTaskMapping(increments, organization, project) {
126
+ if (increments.length === 0) return void 0;
127
+ const firstIncrement = increments[0];
128
+ const tasks = firstIncrement.tasks.map((task) => ({
129
+ id: task.id,
130
+ title: task.title,
131
+ userStories: task.userStories
132
+ }));
133
+ return {
134
+ incrementId: firstIncrement.id,
135
+ tasks,
136
+ tasksUrl: `https://dev.azure.com/${organization}/${project}/_git/repo?path=/.specweave/increments/${firstIncrement.id}/tasks.md`
137
+ };
138
+ }
139
+ async function findArchitectureDocs(rootDir, specId) {
140
+ const docs = [];
141
+ const archDir = path.join(rootDir, ".specweave/docs/internal/architecture");
142
+ try {
143
+ const adrDir = path.join(archDir, "adr");
144
+ try {
145
+ const adrs = await fs.readdir(adrDir);
146
+ const relatedAdrs = adrs.filter((file) => file.includes(specId.replace("spec-", "")));
147
+ for (const adr of relatedAdrs) {
148
+ docs.push({
149
+ type: "adr",
150
+ path: path.join(adrDir, adr),
151
+ title: adr.replace(".md", "").replace(/-/g, " ")
152
+ });
153
+ }
154
+ } catch {
155
+ }
156
+ } catch {
157
+ }
158
+ return docs;
159
+ }
160
+ async function findExistingFeature(client, specId) {
161
+ try {
162
+ const features = await client.queryWorkItems(`[System.Title] Contains '[${specId}]' AND [System.WorkItemType] = 'Feature'`);
163
+ return features[0] || null;
164
+ } catch {
165
+ return null;
166
+ }
167
+ }
168
+ export {
169
+ syncSpecToAdoWithEnhancedContent
170
+ };
@@ -0,0 +1,205 @@
1
+ ---
2
+ name: specweave-github-cleanup-duplicates
3
+ description: Clean up duplicate GitHub issues for an Epic. Finds issues with duplicate titles and closes all except the first created issue.
4
+ ---
5
+
6
+ # Clean Up Duplicate GitHub Issues
7
+
8
+ **CRITICAL**: This command detects and closes duplicate GitHub issues created by multiple syncs.
9
+
10
+ ## Usage
11
+
12
+ ```bash
13
+ /specweave-github:cleanup-duplicates <epic-id> [--dry-run]
14
+ ```
15
+
16
+ ## What It Does
17
+
18
+ **Duplicate Detection & Cleanup**:
19
+
20
+ 1. **Find all issues** for the Epic (searches by Epic ID in title)
21
+ 2. **Group by title** (detect duplicates)
22
+ 3. **For each duplicate group**:
23
+ - Keep the **FIRST created** issue (lowest number)
24
+ - Close all **LATER** issues with comment: "Duplicate of #XXX"
25
+ 4. **Update Epic README** with correct issue numbers
26
+
27
+ ## Examples
28
+
29
+ ### Dry Run (No Changes)
30
+
31
+ ```bash
32
+ /specweave-github:cleanup-duplicates FS-031 --dry-run
33
+ ```
34
+
35
+ **Output**:
36
+ ```
37
+ 🔍 Scanning for duplicates in Epic FS-031...
38
+ Found 25 total issues
39
+ Detected 10 duplicate groups:
40
+
41
+ 📋 Group 1: "[FS-031] External Tool Status Synchronization"
42
+ - #250 (KEEP) - Created 2025-11-10
43
+ - #255 (CLOSE) - Created 2025-11-11 - DUPLICATE
44
+ - #260 (CLOSE) - Created 2025-11-12 - DUPLICATE
45
+
46
+ 📋 Group 2: "[FS-031] Multi-Project GitHub Sync"
47
+ - #251 (KEEP) - Created 2025-11-10
48
+ - #256 (CLOSE) - Created 2025-11-11 - DUPLICATE
49
+
50
+ ...
51
+
52
+ ✅ Dry run complete!
53
+ Total issues: 25
54
+ Duplicate groups: 10
55
+ Issues to close: 15
56
+
57
+ ⚠️ This was a DRY RUN - no changes made.
58
+ Run without --dry-run to actually close duplicates.
59
+ ```
60
+
61
+ ### Actual Cleanup
62
+
63
+ ```bash
64
+ /specweave-github:cleanup-duplicates FS-031
65
+ ```
66
+
67
+ **Output**:
68
+ ```
69
+ 🔍 Scanning for duplicates in Epic FS-031...
70
+ Found 25 total issues
71
+ Detected 10 duplicate groups
72
+
73
+ ⚠️ CONFIRM: Close 15 duplicate issues? [y/N]
74
+ > y
75
+
76
+ 🗑️ Closing duplicates...
77
+ ✅ Closed #255 (duplicate of #250)
78
+ ✅ Closed #256 (duplicate of #251)
79
+ ✅ Closed #260 (duplicate of #250)
80
+ ...
81
+
82
+ 📝 Updating Epic README frontmatter...
83
+ ✅ Updated frontmatter with correct issue numbers
84
+
85
+ ✅ Cleanup complete!
86
+ Closed: 15 duplicates
87
+ Kept: 10 original issues
88
+ ```
89
+
90
+ ## Arguments
91
+
92
+ - `<epic-id>` - Epic ID (e.g., `FS-031` or just `031`)
93
+ - `--dry-run` - Preview changes without actually closing issues (optional)
94
+
95
+ ## Safety Features
96
+
97
+ ✅ **Confirmation prompt**: Asks before closing issues (unless --dry-run)
98
+ ✅ **Dry run mode**: Preview changes safely
99
+ ✅ **Keeps oldest issue**: Preserves the first created issue
100
+ ✅ **Adds closure comment**: Links to the original issue
101
+ ✅ **Updates metadata**: Fixes Epic README frontmatter
102
+
103
+ ## What Gets Closed
104
+
105
+ **Closed issues**:
106
+ - ✅ Duplicate titles (second, third, etc. occurrences)
107
+ - ✅ Closed with comment: "Duplicate of #XXX"
108
+ - ✅ Original issue kept open (or maintains its status)
109
+
110
+ **Example comment on closed duplicate**:
111
+ ```markdown
112
+ Duplicate of #250
113
+
114
+ This issue was automatically closed by SpecWeave cleanup because it is a duplicate.
115
+
116
+ The original issue (#250) contains the same content and should be used for tracking instead.
117
+
118
+ 🤖 Auto-closed by SpecWeave Duplicate Cleanup
119
+ ```
120
+
121
+ ## Requirements
122
+
123
+ 1. **GitHub CLI** (`gh`) installed and authenticated
124
+ 2. **Write access** to repository (for closing issues)
125
+ 3. **Epic folder exists** at `.specweave/docs/internal/specs/FS-XXX-name/`
126
+
127
+ ## When to Use
128
+
129
+ **Use this command when**:
130
+ - ✅ You see multiple issues with the same title in GitHub
131
+ - ✅ Epic sync ran multiple times and created duplicates
132
+ - ✅ Epic README frontmatter got corrupted and reset
133
+ - ✅ Post-sync validation warns about duplicates
134
+
135
+ **Example warning that triggers this**:
136
+ ```
137
+ ⚠️ WARNING: 10 duplicate(s) detected!
138
+ Run cleanup command to resolve:
139
+ /specweave-github:cleanup-duplicates FS-031
140
+ ```
141
+
142
+ ## Troubleshooting
143
+
144
+ **"No duplicates found"**:
145
+ - Good! Your Epic has no duplicate issues
146
+ - Run epic sync is working correctly with duplicate detection
147
+
148
+ **"GitHub CLI not authenticated"**:
149
+ - Run: `gh auth login`
150
+ - Ensure you have write access to the repository
151
+
152
+ **"Could not find Epic folder"**:
153
+ - Check Epic exists: `ls .specweave/docs/internal/specs/`
154
+ - Verify Epic ID format: `FS-031-epic-name/`
155
+
156
+ **"Error closing issue"**:
157
+ - Check GitHub CLI: `gh auth status`
158
+ - Verify write permissions: `gh repo view`
159
+
160
+ ## Architecture
161
+
162
+ **Duplicate Detection Logic**:
163
+ 1. Group issues by **exact title match**
164
+ 2. Within each group, sort by **issue number** (ascending)
165
+ 3. Keep **first issue** (lowest number = oldest)
166
+ 4. Close **all others** as duplicates
167
+
168
+ **Why lowest number?**:
169
+ - Lower issue numbers were created first
170
+ - Preserves chronological order
171
+ - Maintains links from old documentation
172
+
173
+ ## Related Commands
174
+
175
+ - `/specweave-github:sync-epic` - Sync Epic (now with duplicate detection!)
176
+ - `/specweave:validate` - Validate increment completeness
177
+ - `gh issue list` - View all issues (GitHub CLI)
178
+
179
+ ## Implementation
180
+
181
+ **File**: `plugins/specweave-github/lib/github-epic-sync.ts`
182
+
183
+ **Method**: `cleanupDuplicates(epicId: string, dryRun: boolean)`
184
+
185
+ **Algorithm**:
186
+ 1. Search GitHub for all issues with Epic ID
187
+ 2. Group by title (Map<string, number[]>)
188
+ 3. Filter groups with >1 issue (duplicates)
189
+ 4. For each duplicate group:
190
+ - Keep first issue (lowest number)
191
+ - Close others with gh CLI
192
+ 5. Update Epic README frontmatter
193
+
194
+ ## Next Steps
195
+
196
+ After cleanup:
197
+
198
+ 1. **Verify cleanup**: `gh issue list --search "[FS-031]"`
199
+ 2. **Check Epic README**: Verify frontmatter has correct issue numbers
200
+ 3. **Re-run sync**: `/specweave-github:sync-epic FS-031` (should show no duplicates)
201
+ 4. **Enable duplicate detection**: Already enabled in v0.18.0+
202
+
203
+ ---
204
+
205
+ **✅ SAFE TO USE**: This command is idempotent and safe to run multiple times.