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.
- package/CLAUDE.md +405 -2495
- package/README.md +92 -2
- package/dist/locales/de/.gitkeep +0 -0
- package/dist/locales/de/cli.json +108 -0
- package/dist/locales/en/cli.json +287 -0
- package/dist/locales/en/errors.json +7 -0
- package/dist/locales/en/templates.json +6 -0
- package/dist/locales/es/.gitkeep +0 -0
- package/dist/locales/es/cli.json +41 -0
- package/dist/locales/fr/.gitkeep +0 -0
- package/dist/locales/fr/cli.json +108 -0
- package/dist/locales/ja/.gitkeep +0 -0
- package/dist/locales/ja/cli.json +108 -0
- package/dist/locales/ko/.gitkeep +0 -0
- package/dist/locales/ko/cli.json +108 -0
- package/dist/locales/pt/.gitkeep +0 -0
- package/dist/locales/pt/cli.json +108 -0
- package/dist/locales/ru/.gitkeep +0 -0
- package/dist/locales/ru/cli.json +269 -0
- package/dist/locales/zh/.gitkeep +0 -0
- package/dist/locales/zh/cli.json +108 -0
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.d.ts.map +1 -1
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js +188 -36
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts +54 -0
- package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-status-sync.js +86 -0
- package/dist/plugins/specweave-ado/lib/ado-status-sync.js.map +1 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts +25 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js +191 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.d.ts +139 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.js +389 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.js.map +1 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.d.ts +26 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.js +249 -0
- package/dist/plugins/specweave-github/lib/enhanced-github-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-client.d.ts +1 -1
- package/dist/plugins/specweave-github/lib/github-client.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client.js +25 -13
- package/dist/plugins/specweave-github/lib/github-client.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts +83 -0
- package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-epic-sync.js +451 -0
- package/dist/plugins/specweave-github/lib/github-epic-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.d.ts +43 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.js +82 -0
- package/dist/plugins/specweave-github/lib/github-status-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/task-sync.d.ts +5 -0
- package/dist/plugins/specweave-github/lib/task-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/task-sync.js +38 -2
- package/dist/plugins/specweave-github/lib/task-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts +26 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js +195 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts +66 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.js +274 -0
- package/dist/plugins/specweave-jira/lib/jira-epic-sync.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +56 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js +93 -0
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -0
- package/dist/spec-parser.js +629 -0
- package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.js +48 -3
- package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
- package/dist/src/core/living-docs/hierarchy-mapper.d.ts +142 -0
- package/dist/src/core/living-docs/hierarchy-mapper.d.ts.map +1 -0
- package/dist/src/core/living-docs/hierarchy-mapper.js +453 -0
- package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -0
- package/dist/src/core/living-docs/index.d.ts +10 -84
- package/dist/src/core/living-docs/index.d.ts.map +1 -1
- package/dist/src/core/living-docs/index.js +10 -164
- package/dist/src/core/living-docs/index.js.map +1 -1
- package/dist/src/core/living-docs/spec-distributor.d.ts +106 -0
- package/dist/src/core/living-docs/spec-distributor.d.ts.map +1 -0
- package/dist/src/core/living-docs/spec-distributor.js +823 -0
- package/dist/src/core/living-docs/spec-distributor.js.map +1 -0
- package/dist/src/core/living-docs/types.d.ts +201 -0
- package/dist/src/core/living-docs/types.d.ts.map +1 -0
- package/dist/src/core/living-docs/types.js +15 -0
- package/dist/src/core/living-docs/types.js.map +1 -0
- package/dist/src/core/logging/prompt-logger.d.ts +70 -0
- package/dist/src/core/logging/prompt-logger.d.ts.map +1 -0
- package/dist/src/core/logging/prompt-logger.js +247 -0
- package/dist/src/core/logging/prompt-logger.js.map +1 -0
- package/dist/src/core/status-line/status-line-manager.d.ts +15 -24
- package/dist/src/core/status-line/status-line-manager.d.ts.map +1 -1
- package/dist/src/core/status-line/status-line-manager.js +33 -70
- package/dist/src/core/status-line/status-line-manager.js.map +1 -1
- package/dist/src/core/status-line/types.d.ts +19 -31
- package/dist/src/core/status-line/types.d.ts.map +1 -1
- package/dist/src/core/status-line/types.js +5 -9
- package/dist/src/core/status-line/types.js.map +1 -1
- package/dist/src/core/sync/conflict-resolver.d.ts +66 -0
- package/dist/src/core/sync/conflict-resolver.d.ts.map +1 -0
- package/dist/src/core/sync/conflict-resolver.js +108 -0
- package/dist/src/core/sync/conflict-resolver.js.map +1 -0
- package/dist/src/core/sync/enhanced-content-builder.d.ts +77 -0
- package/dist/src/core/sync/enhanced-content-builder.d.ts.map +1 -0
- package/dist/src/core/sync/enhanced-content-builder.js +199 -0
- package/dist/src/core/sync/enhanced-content-builder.js.map +1 -0
- package/dist/src/core/sync/label-detector.d.ts +66 -0
- package/dist/src/core/sync/label-detector.d.ts.map +1 -0
- package/dist/src/core/sync/label-detector.js +211 -0
- package/dist/src/core/sync/label-detector.js.map +1 -0
- package/dist/src/core/sync/retry-logic.d.ts +64 -0
- package/dist/src/core/sync/retry-logic.d.ts.map +1 -0
- package/dist/src/core/sync/retry-logic.js +165 -0
- package/dist/src/core/sync/retry-logic.js.map +1 -0
- package/dist/src/core/sync/spec-content-sync.d.ts +88 -0
- package/dist/src/core/sync/spec-content-sync.d.ts.map +1 -0
- package/dist/src/core/sync/spec-content-sync.js +5 -0
- package/dist/src/core/sync/spec-content-sync.js.map +1 -0
- package/dist/src/core/sync/spec-increment-mapper.d.ts +100 -0
- package/dist/src/core/sync/spec-increment-mapper.d.ts.map +1 -0
- package/dist/src/core/sync/spec-increment-mapper.js +424 -0
- package/dist/src/core/sync/spec-increment-mapper.js.map +1 -0
- package/dist/src/core/sync/status-cache.d.ts +91 -0
- package/dist/src/core/sync/status-cache.d.ts.map +1 -0
- package/dist/src/core/sync/status-cache.js +140 -0
- package/dist/src/core/sync/status-cache.js.map +1 -0
- package/dist/src/core/sync/status-mapper.d.ts +69 -0
- package/dist/src/core/sync/status-mapper.d.ts.map +1 -0
- package/dist/src/core/sync/status-mapper.js +90 -0
- package/dist/src/core/sync/status-mapper.js.map +1 -0
- package/dist/src/core/sync/status-sync-engine.d.ts +162 -0
- package/dist/src/core/sync/status-sync-engine.d.ts.map +1 -0
- package/dist/src/core/sync/status-sync-engine.js +347 -0
- package/dist/src/core/sync/status-sync-engine.js.map +1 -0
- package/dist/src/core/sync/sync-event-logger.d.ts +99 -0
- package/dist/src/core/sync/sync-event-logger.d.ts.map +1 -0
- package/dist/src/core/sync/sync-event-logger.js +103 -0
- package/dist/src/core/sync/sync-event-logger.js.map +1 -0
- package/dist/src/core/sync/workflow-detector.d.ts +95 -0
- package/dist/src/core/sync/workflow-detector.d.ts.map +1 -0
- package/dist/src/core/sync/workflow-detector.js +175 -0
- package/dist/src/core/sync/workflow-detector.js.map +1 -0
- package/dist/src/core/types/config.d.ts.map +1 -1
- package/dist/src/core/types/config.js +31 -0
- package/dist/src/core/types/config.js.map +1 -1
- package/dist/src/utils/github-url.d.ts +53 -0
- package/dist/src/utils/github-url.d.ts.map +1 -0
- package/dist/src/utils/github-url.js +90 -0
- package/dist/src/utils/github-url.js.map +1 -0
- package/dist/src/utils/plugin-validator.d.ts +9 -0
- package/dist/src/utils/plugin-validator.d.ts.map +1 -1
- package/dist/src/utils/plugin-validator.js +86 -19
- package/dist/src/utils/plugin-validator.js.map +1 -1
- package/dist/src/utils/spec-parser.d.ts +145 -0
- package/dist/src/utils/spec-parser.d.ts.map +1 -0
- package/dist/src/utils/spec-parser.js +640 -0
- package/dist/src/utils/spec-parser.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +1 -1
- package/plugins/specweave/agents/pm/AGENT.md +1 -1
- package/plugins/specweave/agents/pm/templates/increment-spec.md +158 -0
- package/plugins/specweave/agents/pm/templates/living-docs-spec.md +113 -0
- package/plugins/specweave/commands/specweave-done.md +163 -0
- package/plugins/specweave/hooks/lib/update-status-line.sh +79 -111
- package/plugins/specweave/hooks/post-increment-planning.sh +107 -35
- package/plugins/specweave/lib/hooks/sync-living-docs.js +139 -34
- package/plugins/specweave/lib/hooks/sync-living-docs.ts +234 -38
- package/plugins/specweave/skills/SKILLS-INDEX.md +4 -24
- package/plugins/specweave/skills/increment-planner/SKILL.md +94 -0
- package/plugins/specweave/skills/increment-work-router/SKILL.md +466 -0
- package/plugins/specweave/skills/plugin-validator/SKILL.md +16 -13
- package/plugins/specweave-ado/lib/ado-status-sync.js +80 -0
- package/plugins/specweave-ado/lib/ado-status-sync.ts +121 -0
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
- package/plugins/specweave-github/commands/specweave-github-cleanup-duplicates.md +205 -0
- package/plugins/specweave-github/commands/specweave-github-sync-epic.md +248 -0
- package/plugins/specweave-github/lib/duplicate-detector.js +370 -0
- package/plugins/specweave-github/lib/duplicate-detector.ts +525 -0
- package/plugins/specweave-github/lib/enhanced-github-sync.js +220 -0
- package/plugins/specweave-github/lib/enhanced-github-sync.ts +322 -0
- package/plugins/specweave-github/lib/github-client.js +21 -10
- package/plugins/specweave-github/lib/github-client.ts +27 -16
- package/plugins/specweave-github/lib/github-epic-sync.js +489 -0
- package/plugins/specweave-github/lib/github-epic-sync.ts +690 -0
- package/plugins/specweave-github/lib/github-status-sync.js +71 -0
- package/plugins/specweave-github/lib/github-status-sync.ts +107 -0
- package/plugins/specweave-github/lib/task-sync.js +33 -2
- package/plugins/specweave-github/lib/task-sync.ts +44 -2
- package/plugins/specweave-jira/commands/specweave-jira-sync-epic.md +267 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.ts.disabled +222 -0
- package/plugins/specweave-jira/lib/jira-epic-sync.js +304 -0
- package/plugins/specweave-jira/lib/jira-epic-sync.ts +459 -0
- package/plugins/specweave-jira/lib/jira-status-sync.js +79 -0
- package/plugins/specweave-jira/lib/jira-status-sync.ts +139 -0
- package/src/templates/AGENTS.md.template +88 -1
- package/src/templates/CLAUDE.md.template +49 -0
- package/plugins/specweave/skills/increment-quality-judge/SKILL.md +0 -524
- 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.
|