specweave 0.9.1 → 0.10.1

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 (75) hide show
  1. package/CLAUDE.md +153 -13
  2. package/README.md +97 -251
  3. package/bin/install-agents.sh +1 -1
  4. package/bin/install-commands.sh +1 -1
  5. package/bin/install-hooks.sh +1 -1
  6. package/bin/install-skills.sh +1 -1
  7. package/bin/specweave.js +32 -0
  8. package/dist/cli/commands/init.d.ts.map +1 -1
  9. package/dist/cli/commands/init.js +29 -1
  10. package/dist/cli/commands/init.js.map +1 -1
  11. package/dist/cli/commands/validate-jira.d.ts +35 -0
  12. package/dist/cli/commands/validate-jira.d.ts.map +1 -0
  13. package/dist/cli/commands/validate-jira.js +112 -0
  14. package/dist/cli/commands/validate-jira.js.map +1 -0
  15. package/dist/cli/commands/validate-plugins.d.ts +41 -0
  16. package/dist/cli/commands/validate-plugins.d.ts.map +1 -0
  17. package/dist/cli/commands/validate-plugins.js +171 -0
  18. package/dist/cli/commands/validate-plugins.js.map +1 -0
  19. package/dist/core/types/sync-profile.d.ts +177 -29
  20. package/dist/core/types/sync-profile.d.ts.map +1 -1
  21. package/dist/core/types/sync-profile.js +48 -1
  22. package/dist/core/types/sync-profile.js.map +1 -1
  23. package/dist/hooks/lib/translate-living-docs.d.ts.map +1 -1
  24. package/dist/hooks/lib/translate-living-docs.js +16 -7
  25. package/dist/hooks/lib/translate-living-docs.js.map +1 -1
  26. package/dist/metrics/dora-calculator.d.ts +7 -3
  27. package/dist/metrics/dora-calculator.d.ts.map +1 -1
  28. package/dist/metrics/dora-calculator.js +19 -6
  29. package/dist/metrics/dora-calculator.js.map +1 -1
  30. package/dist/metrics/report-generator.d.ts +17 -0
  31. package/dist/metrics/report-generator.d.ts.map +1 -0
  32. package/dist/metrics/report-generator.js +403 -0
  33. package/dist/metrics/report-generator.js.map +1 -0
  34. package/dist/utils/external-resource-validator.d.ts +102 -0
  35. package/dist/utils/external-resource-validator.d.ts.map +1 -0
  36. package/dist/utils/external-resource-validator.js +400 -0
  37. package/dist/utils/external-resource-validator.js.map +1 -0
  38. package/dist/utils/plugin-validator.d.ts +161 -0
  39. package/dist/utils/plugin-validator.d.ts.map +1 -0
  40. package/dist/utils/plugin-validator.js +565 -0
  41. package/dist/utils/plugin-validator.js.map +1 -0
  42. package/package.json +2 -1
  43. package/plugins/specweave/commands/specweave-do.md +47 -0
  44. package/plugins/specweave/commands/specweave-increment.md +82 -0
  45. package/plugins/specweave/commands/specweave-next.md +47 -0
  46. package/plugins/specweave/hooks/post-increment-planning.sh +117 -38
  47. package/plugins/specweave/hooks/pre-tool-use.sh +133 -0
  48. package/plugins/specweave/plugin.json +22 -0
  49. package/plugins/specweave/skills/SKILLS-INDEX.md +23 -2
  50. package/plugins/specweave/skills/plugin-installer/SKILL.md +340 -0
  51. package/plugins/specweave/skills/plugin-validator/SKILL.md +427 -0
  52. package/plugins/specweave-ado/.claude-plugin/plugin.json +2 -4
  53. package/plugins/specweave-ado/lib/ado-board-resolver.ts +328 -0
  54. package/plugins/specweave-ado/lib/ado-hierarchical-sync.ts +484 -0
  55. package/plugins/specweave-ado/plugin.json +20 -0
  56. package/plugins/specweave-alternatives/.claude-plugin/plugin.json +15 -2
  57. package/plugins/specweave-backend/.claude-plugin/plugin.json +15 -2
  58. package/plugins/specweave-cost-optimizer/.claude-plugin/plugin.json +14 -2
  59. package/plugins/specweave-diagrams/.claude-plugin/plugin.json +14 -2
  60. package/plugins/specweave-docs/.claude-plugin/plugin.json +13 -2
  61. package/plugins/specweave-figma/.claude-plugin/plugin.json +14 -2
  62. package/plugins/specweave-frontend/.claude-plugin/plugin.json +15 -2
  63. package/plugins/specweave-github/lib/github-board-resolver.ts +164 -0
  64. package/plugins/specweave-github/lib/github-hierarchical-sync.ts +344 -0
  65. package/plugins/specweave-github/plugin.json +19 -0
  66. package/plugins/specweave-infrastructure/.claude-plugin/plugin.json +15 -2
  67. package/plugins/specweave-jira/.claude-plugin/plugin.json +14 -2
  68. package/plugins/specweave-jira/lib/jira-board-resolver.ts +127 -0
  69. package/plugins/specweave-jira/lib/jira-hierarchical-sync.ts +283 -0
  70. package/plugins/specweave-jira/plugin.json +20 -0
  71. package/plugins/specweave-jira/skills/jira-resource-validator/SKILL.md +647 -0
  72. package/plugins/specweave-kubernetes/.claude-plugin/plugin.json +14 -2
  73. package/plugins/specweave-payments/.claude-plugin/plugin.json +14 -2
  74. package/plugins/specweave-testing/.claude-plugin/plugin.json +14 -2
  75. 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
+ }