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.
- package/CLAUDE.md +153 -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/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +29 -1
- package/dist/cli/commands/init.js.map +1 -1
- 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 +400 -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/SKILLS-INDEX.md +23 -2
- package/plugins/specweave/skills/plugin-installer/SKILL.md +340 -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 +647 -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,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure DevOps Hierarchical Sync Implementation
|
|
3
|
+
*
|
|
4
|
+
* Supports three sync strategies:
|
|
5
|
+
* 1. Simple: One project, all work items (backward compatible)
|
|
6
|
+
* 2. Filtered: Multiple projects + area paths + filters
|
|
7
|
+
* 3. Custom: Raw WIQL query
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
SyncProfile,
|
|
12
|
+
SyncContainer,
|
|
13
|
+
AdoConfig,
|
|
14
|
+
isSimpleStrategy,
|
|
15
|
+
isFilteredStrategy,
|
|
16
|
+
isCustomStrategy,
|
|
17
|
+
TimeRangePreset,
|
|
18
|
+
} from '../../../src/core/types/sync-profile.js';
|
|
19
|
+
import { WorkItem } from './ado-client-v2.js';
|
|
20
|
+
import { getAreaPaths } from './ado-board-resolver.js';
|
|
21
|
+
import https from 'https';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build hierarchical WIQL query from containers
|
|
25
|
+
*
|
|
26
|
+
* Example output:
|
|
27
|
+
* SELECT [System.Id], [System.Title], [System.State]
|
|
28
|
+
* FROM WorkItems
|
|
29
|
+
* WHERE (
|
|
30
|
+
* ([System.TeamProject] = 'PROJECT-A' AND [System.AreaPath] UNDER 'PROJECT-A\\Team Alpha')
|
|
31
|
+
* OR
|
|
32
|
+
* ([System.TeamProject] = 'PROJECT-B' AND [System.AreaPath] UNDER 'PROJECT-B\\Platform')
|
|
33
|
+
* )
|
|
34
|
+
* AND [System.WorkItemType] IN ('User Story', 'Bug')
|
|
35
|
+
* AND [System.State] IN ('Active', 'New')
|
|
36
|
+
*
|
|
37
|
+
* @param organization Organization name
|
|
38
|
+
* @param pat Personal Access Token
|
|
39
|
+
* @param containers Array of containers (projects) with filters
|
|
40
|
+
* @returns WIQL query string
|
|
41
|
+
*/
|
|
42
|
+
export async function buildHierarchicalWIQL(
|
|
43
|
+
organization: string,
|
|
44
|
+
pat: string,
|
|
45
|
+
containers: SyncContainer[]
|
|
46
|
+
): Promise<string> {
|
|
47
|
+
const projectClauses: string[] = [];
|
|
48
|
+
|
|
49
|
+
for (const container of containers) {
|
|
50
|
+
const parts: string[] = [];
|
|
51
|
+
|
|
52
|
+
// Project clause
|
|
53
|
+
parts.push(`[System.TeamProject] = '${container.id}'`);
|
|
54
|
+
|
|
55
|
+
// Area path filtering (sub-organizations)
|
|
56
|
+
if (container.subOrganizations && container.subOrganizations.length > 0) {
|
|
57
|
+
try {
|
|
58
|
+
const areaPaths = await getAreaPaths(
|
|
59
|
+
organization,
|
|
60
|
+
container.id,
|
|
61
|
+
pat,
|
|
62
|
+
container.subOrganizations
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (areaPaths.length > 0) {
|
|
66
|
+
const areaPathClauses = areaPaths
|
|
67
|
+
.map((path) => `[System.AreaPath] UNDER '${path}'`)
|
|
68
|
+
.join(' OR ');
|
|
69
|
+
|
|
70
|
+
parts.push(`(${areaPathClauses})`);
|
|
71
|
+
} else {
|
|
72
|
+
console.warn(
|
|
73
|
+
`⚠️ No valid area paths found for project ${container.id}, syncing all areas`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.warn(
|
|
78
|
+
`⚠️ Failed to resolve area paths for ${container.id}, syncing all areas:`,
|
|
79
|
+
(error as Error).message
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Combine parts with AND
|
|
85
|
+
projectClauses.push(`(${parts.join(' AND ')})`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Build WHERE clause with project clauses
|
|
89
|
+
let whereClause = projectClauses.join(' OR ');
|
|
90
|
+
|
|
91
|
+
// Add global filters (apply to all projects)
|
|
92
|
+
const filters = containers[0]?.filters;
|
|
93
|
+
if (filters) {
|
|
94
|
+
const filterClauses = buildFilterClauses(filters);
|
|
95
|
+
if (filterClauses.length > 0) {
|
|
96
|
+
whereClause = `(${whereClause}) AND ${filterClauses.join(' AND ')}`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Build complete WIQL query
|
|
101
|
+
return `
|
|
102
|
+
SELECT [System.Id], [System.Title], [System.Description], [System.State],
|
|
103
|
+
[System.CreatedDate], [System.ChangedDate], [System.WorkItemType],
|
|
104
|
+
[System.AreaPath], [System.IterationPath], [System.Tags]
|
|
105
|
+
FROM WorkItems
|
|
106
|
+
WHERE ${whereClause}
|
|
107
|
+
ORDER BY [System.CreatedDate] DESC
|
|
108
|
+
`.trim();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Build filter clauses from container filters
|
|
113
|
+
*
|
|
114
|
+
* @param filters Container filters
|
|
115
|
+
* @returns Array of WIQL filter clauses
|
|
116
|
+
*/
|
|
117
|
+
function buildFilterClauses(filters: any): string[] {
|
|
118
|
+
const clauses: string[] = [];
|
|
119
|
+
|
|
120
|
+
// Work item types (ADO-specific)
|
|
121
|
+
if (filters.workItemTypes && filters.workItemTypes.length > 0) {
|
|
122
|
+
const types = filters.workItemTypes.map((t: string) => `'${t}'`).join(', ');
|
|
123
|
+
clauses.push(`[System.WorkItemType] IN (${types})`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Status categories (ADO uses State field)
|
|
127
|
+
if (filters.statusCategories && filters.statusCategories.length > 0) {
|
|
128
|
+
const states = filters.statusCategories.map((s: string) => `'${s}'`).join(', ');
|
|
129
|
+
clauses.push(`[System.State] IN (${states})`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Area paths (additional filtering beyond sub-organizations)
|
|
133
|
+
if (filters.areaPaths && filters.areaPaths.length > 0) {
|
|
134
|
+
const areaPathClauses = filters.areaPaths
|
|
135
|
+
.map((path: string) => `[System.AreaPath] UNDER '${path}'`)
|
|
136
|
+
.join(' OR ');
|
|
137
|
+
clauses.push(`(${areaPathClauses})`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Iteration paths (sprints in ADO)
|
|
141
|
+
if (filters.iterationPaths && filters.iterationPaths.length > 0) {
|
|
142
|
+
const iterationClauses = filters.iterationPaths
|
|
143
|
+
.map((path: string) => `[System.IterationPath] UNDER '${path}'`)
|
|
144
|
+
.join(' OR ');
|
|
145
|
+
clauses.push(`(${iterationClauses})`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Tags (labels in ADO)
|
|
149
|
+
if (filters.includeLabels && filters.includeLabels.length > 0) {
|
|
150
|
+
const tagClauses = filters.includeLabels
|
|
151
|
+
.map((tag: string) => `[System.Tags] CONTAINS '${tag}'`)
|
|
152
|
+
.join(' OR ');
|
|
153
|
+
clauses.push(`(${tagClauses})`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Exclude labels/tags
|
|
157
|
+
if (filters.excludeLabels && filters.excludeLabels.length > 0) {
|
|
158
|
+
for (const tag of filters.excludeLabels) {
|
|
159
|
+
clauses.push(`NOT [System.Tags] CONTAINS '${tag}'`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Assignees (assigned to)
|
|
164
|
+
if (filters.assignees && filters.assignees.length > 0) {
|
|
165
|
+
const assigneeClauses = filters.assignees
|
|
166
|
+
.map((assignee: string) => `[System.AssignedTo] = '${assignee}'`)
|
|
167
|
+
.join(' OR ');
|
|
168
|
+
clauses.push(`(${assigneeClauses})`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return clauses;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Add time range filter to WIQL query
|
|
176
|
+
*
|
|
177
|
+
* @param wiql Base WIQL query
|
|
178
|
+
* @param timeRange Time range preset (1W, 1M, 3M, 6M, ALL)
|
|
179
|
+
* @returns WIQL with time range filter
|
|
180
|
+
*/
|
|
181
|
+
function addTimeRangeFilter(wiql: string, timeRange: string): string {
|
|
182
|
+
if (timeRange === 'ALL') {
|
|
183
|
+
return wiql; // No time filter
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const { since, until } = calculateTimeRange(timeRange as TimeRangePreset);
|
|
187
|
+
|
|
188
|
+
// Add time range to WHERE clause
|
|
189
|
+
const timeFilter = `[System.CreatedDate] >= '${since}' AND [System.CreatedDate] <= '${until}'`;
|
|
190
|
+
|
|
191
|
+
// Insert before ORDER BY if present, otherwise append
|
|
192
|
+
if (wiql.includes('ORDER BY')) {
|
|
193
|
+
return wiql.replace('ORDER BY', `AND ${timeFilter} ORDER BY`);
|
|
194
|
+
} else {
|
|
195
|
+
return `${wiql} AND ${timeFilter}`;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Calculate date range from time range preset
|
|
201
|
+
*/
|
|
202
|
+
function calculateTimeRange(timeRange: TimeRangePreset): {
|
|
203
|
+
since: string;
|
|
204
|
+
until: string;
|
|
205
|
+
} {
|
|
206
|
+
const now = new Date();
|
|
207
|
+
const since = new Date(now);
|
|
208
|
+
|
|
209
|
+
switch (timeRange) {
|
|
210
|
+
case '1W':
|
|
211
|
+
since.setDate(now.getDate() - 7);
|
|
212
|
+
break;
|
|
213
|
+
case '2W':
|
|
214
|
+
since.setDate(now.getDate() - 14);
|
|
215
|
+
break;
|
|
216
|
+
case '1M':
|
|
217
|
+
since.setMonth(now.getMonth() - 1);
|
|
218
|
+
break;
|
|
219
|
+
case '3M':
|
|
220
|
+
since.setMonth(now.getMonth() - 3);
|
|
221
|
+
break;
|
|
222
|
+
case '6M':
|
|
223
|
+
since.setMonth(now.getMonth() - 6);
|
|
224
|
+
break;
|
|
225
|
+
case '1Y':
|
|
226
|
+
since.setFullYear(now.getFullYear() - 1);
|
|
227
|
+
break;
|
|
228
|
+
case 'ALL':
|
|
229
|
+
return {
|
|
230
|
+
since: '1970-01-01T00:00:00Z',
|
|
231
|
+
until: now.toISOString(),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
since: since.toISOString(),
|
|
237
|
+
until: now.toISOString(),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Fetch work items hierarchically based on sync strategy
|
|
243
|
+
*
|
|
244
|
+
* @param profile Sync profile with strategy
|
|
245
|
+
* @param pat Personal Access Token
|
|
246
|
+
* @param timeRange Time range preset
|
|
247
|
+
* @returns Array of work items
|
|
248
|
+
*/
|
|
249
|
+
export async function fetchWorkItemsHierarchical(
|
|
250
|
+
profile: SyncProfile,
|
|
251
|
+
pat: string,
|
|
252
|
+
timeRange: string = '1M'
|
|
253
|
+
): Promise<WorkItem[]> {
|
|
254
|
+
const config = profile.config as AdoConfig;
|
|
255
|
+
|
|
256
|
+
// Strategy 1: SIMPLE (backward compatible)
|
|
257
|
+
if (isSimpleStrategy(profile)) {
|
|
258
|
+
return fetchWorkItemsSimple(config, pat, timeRange);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Strategy 2: CUSTOM (raw WIQL)
|
|
262
|
+
if (isCustomStrategy(profile)) {
|
|
263
|
+
return fetchWorkItemsCustom(config, pat, timeRange);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Strategy 3: FILTERED (hierarchical)
|
|
267
|
+
if (isFilteredStrategy(profile)) {
|
|
268
|
+
return fetchWorkItemsFiltered(config, pat, timeRange);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Default to simple if strategy not recognized
|
|
272
|
+
console.warn('⚠️ Unknown strategy, defaulting to simple');
|
|
273
|
+
return fetchWorkItemsSimple(config, pat, timeRange);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Fetch work items using SIMPLE strategy (one project, all work items)
|
|
278
|
+
*
|
|
279
|
+
* @param config ADO configuration
|
|
280
|
+
* @param pat Personal Access Token
|
|
281
|
+
* @param timeRange Time range preset
|
|
282
|
+
* @returns Array of work items
|
|
283
|
+
*/
|
|
284
|
+
async function fetchWorkItemsSimple(
|
|
285
|
+
config: AdoConfig,
|
|
286
|
+
pat: string,
|
|
287
|
+
timeRange: string
|
|
288
|
+
): Promise<WorkItem[]> {
|
|
289
|
+
const organization = config.organization;
|
|
290
|
+
const project = config.project;
|
|
291
|
+
|
|
292
|
+
if (!project) {
|
|
293
|
+
throw new Error('Simple strategy requires project in config');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let wiql = `
|
|
297
|
+
SELECT [System.Id], [System.Title], [System.State], [System.CreatedDate]
|
|
298
|
+
FROM WorkItems
|
|
299
|
+
WHERE [System.TeamProject] = '${project}'
|
|
300
|
+
`.trim();
|
|
301
|
+
|
|
302
|
+
// Add time range
|
|
303
|
+
wiql = addTimeRangeFilter(wiql, timeRange);
|
|
304
|
+
|
|
305
|
+
console.log('🔍 Fetching work items (SIMPLE strategy):', wiql);
|
|
306
|
+
|
|
307
|
+
return executeQuery(organization, project, pat, wiql);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Fetch work items using CUSTOM strategy (raw WIQL)
|
|
312
|
+
*
|
|
313
|
+
* @param config ADO configuration
|
|
314
|
+
* @param pat Personal Access Token
|
|
315
|
+
* @param timeRange Time range preset
|
|
316
|
+
* @returns Array of work items
|
|
317
|
+
*/
|
|
318
|
+
async function fetchWorkItemsCustom(
|
|
319
|
+
config: AdoConfig,
|
|
320
|
+
pat: string,
|
|
321
|
+
timeRange: string
|
|
322
|
+
): Promise<WorkItem[]> {
|
|
323
|
+
const organization = config.organization;
|
|
324
|
+
const project = config.project || 'DefaultProject';
|
|
325
|
+
const customQuery = config.customQuery;
|
|
326
|
+
|
|
327
|
+
if (!customQuery) {
|
|
328
|
+
throw new Error('Custom strategy requires customQuery in config');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Add time range to custom query
|
|
332
|
+
const wiql = addTimeRangeFilter(customQuery, timeRange);
|
|
333
|
+
|
|
334
|
+
console.log('🔍 Fetching work items (CUSTOM strategy):', wiql);
|
|
335
|
+
|
|
336
|
+
return executeQuery(organization, project, pat, wiql);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Fetch work items using FILTERED strategy (multiple projects + area paths + filters)
|
|
341
|
+
*
|
|
342
|
+
* @param config ADO configuration
|
|
343
|
+
* @param pat Personal Access Token
|
|
344
|
+
* @param timeRange Time range preset
|
|
345
|
+
* @returns Array of work items
|
|
346
|
+
*/
|
|
347
|
+
async function fetchWorkItemsFiltered(
|
|
348
|
+
config: AdoConfig,
|
|
349
|
+
pat: string,
|
|
350
|
+
timeRange: string
|
|
351
|
+
): Promise<WorkItem[]> {
|
|
352
|
+
const organization = config.organization;
|
|
353
|
+
const containers = config.containers;
|
|
354
|
+
|
|
355
|
+
if (!containers || containers.length === 0) {
|
|
356
|
+
throw new Error('Filtered strategy requires containers array in config');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Build hierarchical WIQL
|
|
360
|
+
const baseWiql = await buildHierarchicalWIQL(organization, pat, containers);
|
|
361
|
+
|
|
362
|
+
// Add time range
|
|
363
|
+
const wiql = addTimeRangeFilter(baseWiql, timeRange);
|
|
364
|
+
|
|
365
|
+
console.log('🔍 Fetching work items (FILTERED strategy):', wiql);
|
|
366
|
+
|
|
367
|
+
// Use first project for API endpoint (WIQL can query across projects)
|
|
368
|
+
const project = containers[0].id;
|
|
369
|
+
|
|
370
|
+
return executeQuery(organization, project, pat, wiql);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Execute WIQL query and return work items
|
|
375
|
+
*
|
|
376
|
+
* @param organization Organization name
|
|
377
|
+
* @param project Project name
|
|
378
|
+
* @param pat Personal Access Token
|
|
379
|
+
* @param wiql WIQL query string
|
|
380
|
+
* @returns Array of work items
|
|
381
|
+
*/
|
|
382
|
+
async function executeQuery(
|
|
383
|
+
organization: string,
|
|
384
|
+
project: string,
|
|
385
|
+
pat: string,
|
|
386
|
+
wiql: string
|
|
387
|
+
): Promise<WorkItem[]> {
|
|
388
|
+
const baseUrl = `https://dev.azure.com/${organization}/${project}`;
|
|
389
|
+
|
|
390
|
+
// Execute query
|
|
391
|
+
const queryUrl = `${baseUrl}/_apis/wit/wiql?api-version=7.1`;
|
|
392
|
+
const queryResult: any = await makeRequest(queryUrl, pat, 'POST', { query: wiql });
|
|
393
|
+
|
|
394
|
+
if (!queryResult.workItems || queryResult.workItems.length === 0) {
|
|
395
|
+
return [];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Get full work item details (batch request)
|
|
399
|
+
const ids = queryResult.workItems.map((wi: any) => wi.id);
|
|
400
|
+
const batchUrl = `${baseUrl}/_apis/wit/workitemsbatch?api-version=7.1`;
|
|
401
|
+
|
|
402
|
+
const response: any = await makeRequest(batchUrl, pat, 'POST', {
|
|
403
|
+
ids,
|
|
404
|
+
fields: [
|
|
405
|
+
'System.Id',
|
|
406
|
+
'System.Title',
|
|
407
|
+
'System.Description',
|
|
408
|
+
'System.State',
|
|
409
|
+
'System.CreatedDate',
|
|
410
|
+
'System.ChangedDate',
|
|
411
|
+
'System.WorkItemType',
|
|
412
|
+
'System.AreaPath',
|
|
413
|
+
'System.IterationPath',
|
|
414
|
+
'System.Tags',
|
|
415
|
+
],
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
return response.value || [];
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Make HTTPS request to ADO API
|
|
423
|
+
*/
|
|
424
|
+
function makeRequest(
|
|
425
|
+
url: string,
|
|
426
|
+
pat: string,
|
|
427
|
+
method: string = 'GET',
|
|
428
|
+
body?: any
|
|
429
|
+
): Promise<any> {
|
|
430
|
+
return new Promise((resolve, reject) => {
|
|
431
|
+
const { hostname, pathname, search } = new URL(url);
|
|
432
|
+
|
|
433
|
+
const authHeader = 'Basic ' + Buffer.from(`:${pat}`).toString('base64');
|
|
434
|
+
|
|
435
|
+
const headers: Record<string, string> = {
|
|
436
|
+
Authorization: authHeader,
|
|
437
|
+
Accept: 'application/json',
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
if (body) {
|
|
441
|
+
headers['Content-Type'] = 'application/json';
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const options = {
|
|
445
|
+
hostname,
|
|
446
|
+
path: pathname + search,
|
|
447
|
+
method,
|
|
448
|
+
headers,
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const req = https.request(options, (res) => {
|
|
452
|
+
let data = '';
|
|
453
|
+
|
|
454
|
+
res.on('data', (chunk) => {
|
|
455
|
+
data += chunk;
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
res.on('end', () => {
|
|
459
|
+
try {
|
|
460
|
+
const parsed = JSON.parse(data);
|
|
461
|
+
|
|
462
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
463
|
+
resolve(parsed);
|
|
464
|
+
} else {
|
|
465
|
+
reject(new Error(`HTTP ${res.statusCode}: ${parsed.message || data}`));
|
|
466
|
+
}
|
|
467
|
+
} catch (error) {
|
|
468
|
+
reject(new Error(`Failed to parse response: ${data}`));
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
req.on('error', (error) => {
|
|
474
|
+
reject(error);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// Send body if present
|
|
478
|
+
if (body) {
|
|
479
|
+
req.write(JSON.stringify(body));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
req.end();
|
|
483
|
+
});
|
|
484
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "specweave-ado",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Azure DevOps integration for SpecWeave - sync increments with ADO work items, track progress, and manage project workflows",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "SpecWeave Team",
|
|
7
|
+
"url": "https://spec-weave.com"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://spec-weave.com",
|
|
10
|
+
"repository": "https://github.com/anton-abyzov/specweave",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"azure-devops",
|
|
13
|
+
"ado",
|
|
14
|
+
"project-management",
|
|
15
|
+
"work-items",
|
|
16
|
+
"sync",
|
|
17
|
+
"integration"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT"
|
|
20
|
+
}
|
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
"description": "Compare SpecWeave with BMAD, spec-kit, openspec, and other spec-driven frameworks. Get gap analysis, feature comparison, and strategic recommendations to choose the best framework for your needs.",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"author": {
|
|
6
|
-
"name": "SpecWeave Team"
|
|
7
|
-
|
|
6
|
+
"name": "SpecWeave Team",
|
|
7
|
+
"url": "https://spec-weave.com"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://spec-weave.com",
|
|
10
|
+
"repository": "https://github.com/anton-abyzov/specweave",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"alternatives",
|
|
14
|
+
"comparison",
|
|
15
|
+
"bmad",
|
|
16
|
+
"spec-kit",
|
|
17
|
+
"openspec",
|
|
18
|
+
"framework-comparison",
|
|
19
|
+
"specweave"
|
|
20
|
+
]
|
|
8
21
|
}
|
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
"description": "Backend API development for Node.js, Python, and .NET stacks. Includes Express, NestJS, FastAPI, Django, Flask, ASP.NET Core, and Entity Framework Core. Focus on REST APIs, authentication, database operations, and background services.",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"author": {
|
|
6
|
-
"name": "SpecWeave Team"
|
|
7
|
-
|
|
6
|
+
"name": "SpecWeave Team",
|
|
7
|
+
"url": "https://spec-weave.com"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://spec-weave.com",
|
|
10
|
+
"repository": "https://github.com/anton-abyzov/specweave",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"backend",
|
|
14
|
+
"nodejs",
|
|
15
|
+
"python",
|
|
16
|
+
"dotnet",
|
|
17
|
+
"rest-api",
|
|
18
|
+
"server",
|
|
19
|
+
"specweave"
|
|
20
|
+
]
|
|
8
21
|
}
|
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
"description": "Cloud cost optimization and platform comparison. Analyzes infrastructure requirements and recommends cheapest cloud platform (Hetzner, Vercel, AWS, Railway, Fly.io, DigitalOcean). Shows cost breakdown and savings calculations.",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"author": {
|
|
6
|
-
"name": "SpecWeave Team"
|
|
7
|
-
|
|
6
|
+
"name": "SpecWeave Team",
|
|
7
|
+
"url": "https://spec-weave.com"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://spec-weave.com",
|
|
10
|
+
"repository": "https://github.com/anton-abyzov/specweave",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cost",
|
|
14
|
+
"optimization",
|
|
15
|
+
"cloud",
|
|
16
|
+
"pricing",
|
|
17
|
+
"budget",
|
|
18
|
+
"specweave"
|
|
19
|
+
]
|
|
8
20
|
}
|
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
"description": "Architecture diagram generation with Mermaid following C4 Model conventions. Creates C4 Context/Container/Component diagrams, sequence diagrams, ER diagrams, and deployment diagrams. SpecWeave-aware for HLD/LLD documentation.",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"author": {
|
|
6
|
-
"name": "SpecWeave Team"
|
|
7
|
-
|
|
6
|
+
"name": "SpecWeave Team",
|
|
7
|
+
"url": "https://spec-weave.com"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://spec-weave.com",
|
|
10
|
+
"repository": "https://github.com/anton-abyzov/specweave",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"diagrams",
|
|
14
|
+
"mermaid",
|
|
15
|
+
"c4-model",
|
|
16
|
+
"architecture",
|
|
17
|
+
"visualization",
|
|
18
|
+
"specweave"
|
|
19
|
+
]
|
|
8
20
|
}
|
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
"description": "Documentation generation and spec-driven workflows. Includes Docusaurus site generation from SpecWeave structure, spec-driven brainstorming for feature ideation, and spec-driven debugging. Focus on living documentation and knowledge management.",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"author": {
|
|
6
|
-
"name": "SpecWeave Team"
|
|
7
|
-
|
|
6
|
+
"name": "SpecWeave Team",
|
|
7
|
+
"url": "https://spec-weave.com"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://spec-weave.com",
|
|
10
|
+
"repository": "https://github.com/anton-abyzov/specweave",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"documentation",
|
|
14
|
+
"docusaurus",
|
|
15
|
+
"specs",
|
|
16
|
+
"docs-generation",
|
|
17
|
+
"specweave"
|
|
18
|
+
]
|
|
8
19
|
}
|
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
"description": "Design-to-code workflow patterns for Figma (MCP-first). Requires Figma MCP server. Extracts design tokens, generates components (React/Angular/Vue/Svelte), scaffolds tests. Uses Atomic Design and TypeScript.",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"author": {
|
|
6
|
-
"name": "SpecWeave Team"
|
|
7
|
-
|
|
6
|
+
"name": "SpecWeave Team",
|
|
7
|
+
"url": "https://spec-weave.com"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://spec-weave.com",
|
|
10
|
+
"repository": "https://github.com/anton-abyzov/specweave",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"figma",
|
|
14
|
+
"design",
|
|
15
|
+
"design-system",
|
|
16
|
+
"mcp",
|
|
17
|
+
"design-tokens",
|
|
18
|
+
"specweave"
|
|
19
|
+
]
|
|
8
20
|
}
|
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
"description": "Frontend development for React, Vue, and Angular projects. Includes Next.js 14+ App Router support, design system architecture (Atomic Design), and UI component best practices. Focus on modern frontend patterns and performance.",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"author": {
|
|
6
|
-
"name": "SpecWeave Team"
|
|
7
|
-
|
|
6
|
+
"name": "SpecWeave Team",
|
|
7
|
+
"url": "https://spec-weave.com"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://spec-weave.com",
|
|
10
|
+
"repository": "https://github.com/anton-abyzov/specweave",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"frontend",
|
|
14
|
+
"react",
|
|
15
|
+
"vue",
|
|
16
|
+
"angular",
|
|
17
|
+
"nextjs",
|
|
18
|
+
"ui",
|
|
19
|
+
"specweave"
|
|
20
|
+
]
|
|
8
21
|
}
|