specweave 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +100 -13
- package/README.md +97 -251
- package/bin/install-agents.sh +1 -1
- package/bin/install-commands.sh +1 -1
- package/bin/install-hooks.sh +1 -1
- package/bin/install-skills.sh +1 -1
- package/bin/specweave.js +32 -0
- package/dist/cli/commands/validate-jira.d.ts +35 -0
- package/dist/cli/commands/validate-jira.d.ts.map +1 -0
- package/dist/cli/commands/validate-jira.js +112 -0
- package/dist/cli/commands/validate-jira.js.map +1 -0
- package/dist/cli/commands/validate-plugins.d.ts +41 -0
- package/dist/cli/commands/validate-plugins.d.ts.map +1 -0
- package/dist/cli/commands/validate-plugins.js +171 -0
- package/dist/cli/commands/validate-plugins.js.map +1 -0
- package/dist/core/types/sync-profile.d.ts +177 -29
- package/dist/core/types/sync-profile.d.ts.map +1 -1
- package/dist/core/types/sync-profile.js +48 -1
- package/dist/core/types/sync-profile.js.map +1 -1
- package/dist/hooks/lib/translate-living-docs.d.ts.map +1 -1
- package/dist/hooks/lib/translate-living-docs.js +16 -7
- package/dist/hooks/lib/translate-living-docs.js.map +1 -1
- package/dist/metrics/dora-calculator.d.ts +7 -3
- package/dist/metrics/dora-calculator.d.ts.map +1 -1
- package/dist/metrics/dora-calculator.js +19 -6
- package/dist/metrics/dora-calculator.js.map +1 -1
- package/dist/metrics/report-generator.d.ts +17 -0
- package/dist/metrics/report-generator.d.ts.map +1 -0
- package/dist/metrics/report-generator.js +403 -0
- package/dist/metrics/report-generator.js.map +1 -0
- package/dist/utils/external-resource-validator.d.ts +102 -0
- package/dist/utils/external-resource-validator.d.ts.map +1 -0
- package/dist/utils/external-resource-validator.js +381 -0
- package/dist/utils/external-resource-validator.js.map +1 -0
- package/dist/utils/plugin-validator.d.ts +161 -0
- package/dist/utils/plugin-validator.d.ts.map +1 -0
- package/dist/utils/plugin-validator.js +565 -0
- package/dist/utils/plugin-validator.js.map +1 -0
- package/package.json +2 -1
- package/plugins/specweave/commands/specweave-do.md +47 -0
- package/plugins/specweave/commands/specweave-increment.md +82 -0
- package/plugins/specweave/commands/specweave-next.md +47 -0
- package/plugins/specweave/hooks/post-increment-planning.sh +117 -38
- package/plugins/specweave/hooks/pre-tool-use.sh +133 -0
- package/plugins/specweave/plugin.json +22 -0
- package/plugins/specweave/skills/plugin-validator/SKILL.md +427 -0
- package/plugins/specweave-ado/.claude-plugin/plugin.json +2 -4
- package/plugins/specweave-ado/lib/ado-board-resolver.ts +328 -0
- package/plugins/specweave-ado/lib/ado-hierarchical-sync.ts +484 -0
- package/plugins/specweave-ado/plugin.json +20 -0
- package/plugins/specweave-alternatives/.claude-plugin/plugin.json +15 -2
- package/plugins/specweave-backend/.claude-plugin/plugin.json +15 -2
- package/plugins/specweave-cost-optimizer/.claude-plugin/plugin.json +14 -2
- package/plugins/specweave-diagrams/.claude-plugin/plugin.json +14 -2
- package/plugins/specweave-docs/.claude-plugin/plugin.json +13 -2
- package/plugins/specweave-figma/.claude-plugin/plugin.json +14 -2
- package/plugins/specweave-frontend/.claude-plugin/plugin.json +15 -2
- package/plugins/specweave-github/lib/github-board-resolver.ts +164 -0
- package/plugins/specweave-github/lib/github-hierarchical-sync.ts +344 -0
- package/plugins/specweave-github/plugin.json +19 -0
- package/plugins/specweave-infrastructure/.claude-plugin/plugin.json +15 -2
- package/plugins/specweave-jira/.claude-plugin/plugin.json +14 -2
- package/plugins/specweave-jira/lib/jira-board-resolver.ts +127 -0
- package/plugins/specweave-jira/lib/jira-hierarchical-sync.ts +283 -0
- package/plugins/specweave-jira/plugin.json +20 -0
- package/plugins/specweave-jira/skills/jira-resource-validator/SKILL.md +584 -0
- package/plugins/specweave-kubernetes/.claude-plugin/plugin.json +14 -2
- package/plugins/specweave-payments/.claude-plugin/plugin.json +14 -2
- package/plugins/specweave-testing/.claude-plugin/plugin.json +14 -2
- package/plugins/specweave-tooling/.claude-plugin/plugin.json +13 -2
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure DevOps Team & Area Path Resolution for Hierarchical Sync
|
|
3
|
+
*
|
|
4
|
+
* Resolves team names and area paths for use in WIQL queries.
|
|
5
|
+
* Azure DevOps organizes work by:
|
|
6
|
+
* - Teams (organizational unit)
|
|
7
|
+
* - Area Paths (hierarchical work categorization)
|
|
8
|
+
* - Iteration Paths (sprints/time-based organization)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import https from 'https';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* ADO Team
|
|
15
|
+
*/
|
|
16
|
+
export interface AdoTeam {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
projectName: string;
|
|
21
|
+
projectId: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* ADO Area Path
|
|
26
|
+
*/
|
|
27
|
+
export interface AdoAreaPath {
|
|
28
|
+
id: number;
|
|
29
|
+
identifier: string;
|
|
30
|
+
name: string;
|
|
31
|
+
path: string;
|
|
32
|
+
hasChildren: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* ADO Iteration Path
|
|
37
|
+
*/
|
|
38
|
+
export interface AdoIterationPath {
|
|
39
|
+
id: number;
|
|
40
|
+
identifier: string;
|
|
41
|
+
name: string;
|
|
42
|
+
path: string;
|
|
43
|
+
attributes: {
|
|
44
|
+
startDate?: string;
|
|
45
|
+
finishDate?: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Fetch all teams for an ADO project
|
|
51
|
+
*
|
|
52
|
+
* Uses Azure DevOps REST API: GET /{organization}/_apis/projects/{project}/teams
|
|
53
|
+
*
|
|
54
|
+
* @param organization Organization name
|
|
55
|
+
* @param project Project name
|
|
56
|
+
* @param pat Personal Access Token
|
|
57
|
+
* @returns Array of teams
|
|
58
|
+
*/
|
|
59
|
+
export async function fetchTeamsForProject(
|
|
60
|
+
organization: string,
|
|
61
|
+
project: string,
|
|
62
|
+
pat: string
|
|
63
|
+
): Promise<AdoTeam[]> {
|
|
64
|
+
console.log(`🔍 Fetching teams for project: ${project}`);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const url = `https://dev.azure.com/${organization}/_apis/projects/${project}/teams?api-version=7.1`;
|
|
68
|
+
|
|
69
|
+
const response = await makeRequest<{ value: AdoTeam[] }>(url, pat);
|
|
70
|
+
|
|
71
|
+
const teams = response.value || [];
|
|
72
|
+
|
|
73
|
+
console.log(`✅ Found ${teams.length} team(s) for project ${project}`);
|
|
74
|
+
|
|
75
|
+
return teams;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(`❌ Error fetching teams for ${project}:`, (error as Error).message);
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Fetch area paths for a project
|
|
84
|
+
*
|
|
85
|
+
* Uses Azure DevOps REST API: GET /{organization}/{project}/_apis/wit/classificationnodes/areas
|
|
86
|
+
*
|
|
87
|
+
* @param organization Organization name
|
|
88
|
+
* @param project Project name
|
|
89
|
+
* @param pat Personal Access Token
|
|
90
|
+
* @returns Array of area paths (flattened hierarchy)
|
|
91
|
+
*/
|
|
92
|
+
export async function fetchAreaPathsForProject(
|
|
93
|
+
organization: string,
|
|
94
|
+
project: string,
|
|
95
|
+
pat: string
|
|
96
|
+
): Promise<AdoAreaPath[]> {
|
|
97
|
+
console.log(`🔍 Fetching area paths for project: ${project}`);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const url = `https://dev.azure.com/${organization}/${project}/_apis/wit/classificationnodes/areas?$depth=10&api-version=7.1`;
|
|
101
|
+
|
|
102
|
+
const response = await makeRequest<any>(url, pat);
|
|
103
|
+
|
|
104
|
+
// Flatten hierarchy into array
|
|
105
|
+
const areaPaths: AdoAreaPath[] = [];
|
|
106
|
+
flattenAreaPaths(response, areaPaths, project);
|
|
107
|
+
|
|
108
|
+
console.log(`✅ Found ${areaPaths.length} area path(s) for project ${project}`);
|
|
109
|
+
|
|
110
|
+
return areaPaths;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(`❌ Error fetching area paths for ${project}:`, (error as Error).message);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Flatten area path hierarchy into flat array
|
|
119
|
+
*/
|
|
120
|
+
function flattenAreaPaths(
|
|
121
|
+
node: any,
|
|
122
|
+
result: AdoAreaPath[],
|
|
123
|
+
projectName: string,
|
|
124
|
+
parentPath: string = ''
|
|
125
|
+
): void {
|
|
126
|
+
if (!node) return;
|
|
127
|
+
|
|
128
|
+
const fullPath = parentPath ? `${parentPath}\\${node.name}` : node.name;
|
|
129
|
+
|
|
130
|
+
result.push({
|
|
131
|
+
id: node.id,
|
|
132
|
+
identifier: node.identifier,
|
|
133
|
+
name: node.name,
|
|
134
|
+
path: fullPath,
|
|
135
|
+
hasChildren: node.hasChildren || false,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Recursively process children
|
|
139
|
+
if (node.children && node.children.length > 0) {
|
|
140
|
+
for (const child of node.children) {
|
|
141
|
+
flattenAreaPaths(child, result, projectName, fullPath);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Fetch iteration paths for a project
|
|
148
|
+
*
|
|
149
|
+
* @param organization Organization name
|
|
150
|
+
* @param project Project name
|
|
151
|
+
* @param pat Personal Access Token
|
|
152
|
+
* @returns Array of iteration paths (flattened hierarchy)
|
|
153
|
+
*/
|
|
154
|
+
export async function fetchIterationPathsForProject(
|
|
155
|
+
organization: string,
|
|
156
|
+
project: string,
|
|
157
|
+
pat: string
|
|
158
|
+
): Promise<AdoIterationPath[]> {
|
|
159
|
+
console.log(`🔍 Fetching iteration paths for project: ${project}`);
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const url = `https://dev.azure.com/${organization}/${project}/_apis/wit/classificationnodes/iterations?$depth=10&api-version=7.1`;
|
|
163
|
+
|
|
164
|
+
const response = await makeRequest<any>(url, pat);
|
|
165
|
+
|
|
166
|
+
// Flatten hierarchy into array
|
|
167
|
+
const iterationPaths: AdoIterationPath[] = [];
|
|
168
|
+
flattenIterationPaths(response, iterationPaths, project);
|
|
169
|
+
|
|
170
|
+
console.log(`✅ Found ${iterationPaths.length} iteration path(s) for project ${project}`);
|
|
171
|
+
|
|
172
|
+
return iterationPaths;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error(`❌ Error fetching iteration paths for ${project}:`, (error as Error).message);
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Flatten iteration path hierarchy into flat array
|
|
181
|
+
*/
|
|
182
|
+
function flattenIterationPaths(
|
|
183
|
+
node: any,
|
|
184
|
+
result: AdoIterationPath[],
|
|
185
|
+
projectName: string,
|
|
186
|
+
parentPath: string = ''
|
|
187
|
+
): void {
|
|
188
|
+
if (!node) return;
|
|
189
|
+
|
|
190
|
+
const fullPath = parentPath ? `${parentPath}\\${node.name}` : node.name;
|
|
191
|
+
|
|
192
|
+
result.push({
|
|
193
|
+
id: node.id,
|
|
194
|
+
identifier: node.identifier,
|
|
195
|
+
name: node.name,
|
|
196
|
+
path: fullPath,
|
|
197
|
+
attributes: node.attributes || {},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Recursively process children
|
|
201
|
+
if (node.children && node.children.length > 0) {
|
|
202
|
+
for (const child of node.children) {
|
|
203
|
+
flattenIterationPaths(child, result, projectName, fullPath);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Resolve area path names to full paths
|
|
210
|
+
*
|
|
211
|
+
* @param organization Organization name
|
|
212
|
+
* @param project Project name
|
|
213
|
+
* @param pat Personal Access Token
|
|
214
|
+
* @param areaPathNames Array of area path names (can be partial)
|
|
215
|
+
* @returns Map of area path name → full path
|
|
216
|
+
*/
|
|
217
|
+
export async function resolveAreaPathNames(
|
|
218
|
+
organization: string,
|
|
219
|
+
project: string,
|
|
220
|
+
pat: string,
|
|
221
|
+
areaPathNames: string[]
|
|
222
|
+
): Promise<Map<string, string>> {
|
|
223
|
+
if (!areaPathNames || areaPathNames.length === 0) {
|
|
224
|
+
return new Map();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const areaPaths = await fetchAreaPathsForProject(organization, project, pat);
|
|
228
|
+
|
|
229
|
+
const pathMap = new Map<string, string>();
|
|
230
|
+
|
|
231
|
+
for (const areaPathName of areaPathNames) {
|
|
232
|
+
// Try exact match first
|
|
233
|
+
const exactMatch = areaPaths.find(
|
|
234
|
+
(ap) => ap.path === areaPathName || ap.name === areaPathName
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
if (exactMatch) {
|
|
238
|
+
pathMap.set(areaPathName, exactMatch.path);
|
|
239
|
+
console.log(`✅ Resolved area path "${areaPathName}" → "${exactMatch.path}"`);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Try partial match (contains)
|
|
244
|
+
const partialMatch = areaPaths.find(
|
|
245
|
+
(ap) =>
|
|
246
|
+
ap.path.toLowerCase().includes(areaPathName.toLowerCase()) ||
|
|
247
|
+
ap.name.toLowerCase().includes(areaPathName.toLowerCase())
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
if (partialMatch) {
|
|
251
|
+
pathMap.set(areaPathName, partialMatch.path);
|
|
252
|
+
console.log(`✅ Resolved area path "${areaPathName}" → "${partialMatch.path}" (partial match)`);
|
|
253
|
+
} else {
|
|
254
|
+
console.warn(`⚠️ Area path "${areaPathName}" not found in project ${project}`);
|
|
255
|
+
// Don't throw - just skip (user may have typo or path doesn't exist)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return pathMap;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Get full area paths for a list of names (helper function)
|
|
264
|
+
*
|
|
265
|
+
* @param organization Organization name
|
|
266
|
+
* @param project Project name
|
|
267
|
+
* @param pat Personal Access Token
|
|
268
|
+
* @param areaPathNames Array of area path names
|
|
269
|
+
* @returns Array of full area paths (skips paths not found)
|
|
270
|
+
*/
|
|
271
|
+
export async function getAreaPaths(
|
|
272
|
+
organization: string,
|
|
273
|
+
project: string,
|
|
274
|
+
pat: string,
|
|
275
|
+
areaPathNames: string[]
|
|
276
|
+
): Promise<string[]> {
|
|
277
|
+
const pathMap = await resolveAreaPathNames(organization, project, pat, areaPathNames);
|
|
278
|
+
return Array.from(pathMap.values());
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Make HTTPS request to ADO API
|
|
283
|
+
*/
|
|
284
|
+
function makeRequest<T>(url: string, pat: string): Promise<T> {
|
|
285
|
+
return new Promise((resolve, reject) => {
|
|
286
|
+
const { hostname, pathname, search } = new URL(url);
|
|
287
|
+
|
|
288
|
+
const authHeader = 'Basic ' + Buffer.from(`:${pat}`).toString('base64');
|
|
289
|
+
|
|
290
|
+
const options = {
|
|
291
|
+
hostname,
|
|
292
|
+
path: pathname + search,
|
|
293
|
+
method: 'GET',
|
|
294
|
+
headers: {
|
|
295
|
+
Authorization: authHeader,
|
|
296
|
+
Accept: 'application/json',
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const req = https.request(options, (res) => {
|
|
301
|
+
let data = '';
|
|
302
|
+
|
|
303
|
+
res.on('data', (chunk) => {
|
|
304
|
+
data += chunk;
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
res.on('end', () => {
|
|
308
|
+
try {
|
|
309
|
+
const parsed = JSON.parse(data);
|
|
310
|
+
|
|
311
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
312
|
+
resolve(parsed as T);
|
|
313
|
+
} else {
|
|
314
|
+
reject(new Error(`HTTP ${res.statusCode}: ${parsed.message || data}`));
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
reject(new Error(`Failed to parse response: ${data}`));
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
req.on('error', (error) => {
|
|
323
|
+
reject(error);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
req.end();
|
|
327
|
+
});
|
|
328
|
+
}
|