specweave 0.16.5 → 0.17.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/bin/fix-marketplace-errors.sh +136 -0
- package/dist/cli/helpers/issue-tracker/index.d.ts.map +1 -1
- package/dist/cli/helpers/issue-tracker/index.js +21 -0
- package/dist/cli/helpers/issue-tracker/index.js.map +1 -1
- package/package.json +2 -2
- package/plugins/specweave-ado/agents/ado-multi-project-mapper/AGENT.md +521 -0
- package/plugins/specweave-ado/agents/ado-sync-judge/AGENT.md +418 -0
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh +353 -0
- package/plugins/specweave-ado/lib/ado-project-detector.js +469 -0
- package/plugins/specweave-ado/lib/ado-project-detector.ts +510 -0
- package/plugins/specweave-ado/lib/conflict-resolver.js +297 -0
- package/plugins/specweave-ado/lib/conflict-resolver.ts +443 -0
- package/plugins/specweave-ado/skills/ado-multi-project/SKILL.md +541 -0
- package/plugins/specweave-ado/skills/ado-resource-validator/SKILL.md +719 -0
- package/src/templates/CLAUDE.md.template +24 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import * as fs from "fs-extra";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as yaml from "yaml";
|
|
4
|
+
const STATUS_MAPPING = {
|
|
5
|
+
ado: {
|
|
6
|
+
"New": "draft",
|
|
7
|
+
"Active": "in-progress",
|
|
8
|
+
"Resolved": "implemented",
|
|
9
|
+
"Closed": "complete",
|
|
10
|
+
"In Review": "in-qa",
|
|
11
|
+
"In QA": "in-qa",
|
|
12
|
+
"Blocked": "blocked",
|
|
13
|
+
"Removed": "cancelled"
|
|
14
|
+
},
|
|
15
|
+
jira: {
|
|
16
|
+
"To Do": "draft",
|
|
17
|
+
"In Progress": "in-progress",
|
|
18
|
+
"Code Review": "implemented",
|
|
19
|
+
"In Review": "implemented",
|
|
20
|
+
"QA": "in-qa",
|
|
21
|
+
"Testing": "in-qa",
|
|
22
|
+
"Done": "complete",
|
|
23
|
+
"Closed": "complete",
|
|
24
|
+
"Blocked": "blocked",
|
|
25
|
+
"Cancelled": "cancelled"
|
|
26
|
+
},
|
|
27
|
+
github: {
|
|
28
|
+
"open": "in-progress",
|
|
29
|
+
"closed": "complete"
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const REVERSE_STATUS_MAPPING = {
|
|
33
|
+
ado: {
|
|
34
|
+
"draft": "New",
|
|
35
|
+
"in-progress": "Active",
|
|
36
|
+
"implemented": "Resolved",
|
|
37
|
+
"in-qa": "In QA",
|
|
38
|
+
"complete": "Closed",
|
|
39
|
+
"blocked": "Blocked",
|
|
40
|
+
"cancelled": "Removed"
|
|
41
|
+
},
|
|
42
|
+
jira: {
|
|
43
|
+
"draft": "To Do",
|
|
44
|
+
"in-progress": "In Progress",
|
|
45
|
+
"implemented": "Code Review",
|
|
46
|
+
"in-qa": "QA",
|
|
47
|
+
"complete": "Done",
|
|
48
|
+
"blocked": "Blocked",
|
|
49
|
+
"cancelled": "Cancelled"
|
|
50
|
+
},
|
|
51
|
+
github: {
|
|
52
|
+
"draft": "open",
|
|
53
|
+
"in-progress": "open",
|
|
54
|
+
"implemented": "open",
|
|
55
|
+
"in-qa": "open",
|
|
56
|
+
"complete": "closed",
|
|
57
|
+
"blocked": "open",
|
|
58
|
+
"cancelled": "closed"
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
class ConflictResolver {
|
|
62
|
+
constructor() {
|
|
63
|
+
this.resolutionLog = [];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Map external status to local SpecWeave status
|
|
67
|
+
*/
|
|
68
|
+
mapExternalStatus(tool, externalStatus) {
|
|
69
|
+
const mapping = STATUS_MAPPING[tool];
|
|
70
|
+
return mapping[externalStatus] || "unknown";
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Map local status to external tool status
|
|
74
|
+
*/
|
|
75
|
+
mapLocalStatus(tool, localStatus) {
|
|
76
|
+
const mapping = REVERSE_STATUS_MAPPING[tool];
|
|
77
|
+
return mapping[localStatus] || "Active";
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* CRITICAL: Resolve status conflict - EXTERNAL ALWAYS WINS
|
|
81
|
+
*/
|
|
82
|
+
resolveStatusConflict(localStatus, externalStatus) {
|
|
83
|
+
const resolution = {
|
|
84
|
+
field: "status",
|
|
85
|
+
localValue: localStatus,
|
|
86
|
+
externalValue: externalStatus.status,
|
|
87
|
+
resolution: "external",
|
|
88
|
+
// ALWAYS external for status
|
|
89
|
+
resolvedValue: externalStatus.mappedStatus,
|
|
90
|
+
reason: "External tool reflects QA and stakeholder decisions",
|
|
91
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
92
|
+
};
|
|
93
|
+
console.log(`\u{1F4CA} Status Conflict Detected:`);
|
|
94
|
+
console.log(` Local: ${localStatus}`);
|
|
95
|
+
console.log(` External: ${externalStatus.status} (${externalStatus.tool})`);
|
|
96
|
+
console.log(` \u2705 Resolution: EXTERNAL WINS - ${externalStatus.mappedStatus}`);
|
|
97
|
+
this.resolutionLog.push(resolution);
|
|
98
|
+
return resolution;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Resolve priority conflict - EXTERNAL WINS
|
|
102
|
+
*/
|
|
103
|
+
resolvePriorityConflict(localPriority, externalPriority) {
|
|
104
|
+
const resolution = {
|
|
105
|
+
field: "priority",
|
|
106
|
+
localValue: localPriority,
|
|
107
|
+
externalValue: externalPriority,
|
|
108
|
+
resolution: "external",
|
|
109
|
+
resolvedValue: externalPriority || localPriority,
|
|
110
|
+
reason: "External tool reflects stakeholder prioritization",
|
|
111
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
112
|
+
};
|
|
113
|
+
if (localPriority !== externalPriority && externalPriority) {
|
|
114
|
+
console.log(`\u{1F4CA} Priority Conflict Detected:`);
|
|
115
|
+
console.log(` Local: ${localPriority}`);
|
|
116
|
+
console.log(` External: ${externalPriority}`);
|
|
117
|
+
console.log(` \u2705 Resolution: EXTERNAL WINS - ${externalPriority}`);
|
|
118
|
+
this.resolutionLog.push(resolution);
|
|
119
|
+
}
|
|
120
|
+
return resolution;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Apply conflict resolutions to spec
|
|
124
|
+
*/
|
|
125
|
+
async applyResolutions(specPath, resolutions) {
|
|
126
|
+
const content = await fs.readFile(specPath, "utf-8");
|
|
127
|
+
const lines = content.split("\n");
|
|
128
|
+
let inFrontmatter = false;
|
|
129
|
+
let frontmatterEnd = -1;
|
|
130
|
+
for (let i = 0; i < lines.length; i++) {
|
|
131
|
+
if (lines[i] === "---") {
|
|
132
|
+
if (!inFrontmatter) {
|
|
133
|
+
inFrontmatter = true;
|
|
134
|
+
} else {
|
|
135
|
+
frontmatterEnd = i;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
for (const resolution of resolutions) {
|
|
141
|
+
if (resolution.field === "status") {
|
|
142
|
+
for (let i = 1; i < frontmatterEnd; i++) {
|
|
143
|
+
if (lines[i].startsWith("status:")) {
|
|
144
|
+
lines[i] = `status: ${resolution.resolvedValue}`;
|
|
145
|
+
console.log(`\u2705 Applied status resolution: ${resolution.resolvedValue}`);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const syncTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
150
|
+
let syncedAtFound = false;
|
|
151
|
+
for (let i = 1; i < frontmatterEnd; i++) {
|
|
152
|
+
if (lines[i].includes("syncedAt:")) {
|
|
153
|
+
lines[i] = ` syncedAt: "${syncTimestamp}"`;
|
|
154
|
+
syncedAtFound = true;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (!syncedAtFound) {
|
|
159
|
+
for (let i = 1; i < frontmatterEnd; i++) {
|
|
160
|
+
if (lines[i].includes("externalLinks:")) {
|
|
161
|
+
for (let j = i + 1; j < frontmatterEnd; j++) {
|
|
162
|
+
if (lines[j].includes("ado:") || lines[j].includes("jira:") || lines[j].includes("github:")) {
|
|
163
|
+
for (let k = j + 1; k < frontmatterEnd; k++) {
|
|
164
|
+
if (lines[k].includes("Url:")) {
|
|
165
|
+
lines.splice(k + 1, 0, ` syncedAt: "${syncTimestamp}"`);
|
|
166
|
+
frontmatterEnd++;
|
|
167
|
+
syncedAtFound = true;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (syncedAtFound) break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (syncedAtFound) break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} else if (resolution.field === "priority" && resolution.resolvedValue) {
|
|
179
|
+
for (let i = 1; i < frontmatterEnd; i++) {
|
|
180
|
+
if (lines[i].startsWith("priority:")) {
|
|
181
|
+
lines[i] = `priority: ${resolution.resolvedValue}`;
|
|
182
|
+
console.log(`\u2705 Applied priority resolution: ${resolution.resolvedValue}`);
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
await fs.writeFile(specPath, lines.join("\n"));
|
|
189
|
+
console.log(`\u2705 Resolutions applied to ${path.basename(specPath)}`);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Validate that external status wins in implementation
|
|
193
|
+
*/
|
|
194
|
+
validateImplementation(implementationCode) {
|
|
195
|
+
const violations = [];
|
|
196
|
+
const incorrectPatterns = [
|
|
197
|
+
{
|
|
198
|
+
pattern: /if.*conflict.*\{[^}]*spec\.status\s*=\s*localStatus/,
|
|
199
|
+
message: "Local status should never win in conflicts"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
pattern: /resolution\s*:\s*['"]local['"]/,
|
|
203
|
+
message: 'Resolution should be "external" for status conflicts'
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
pattern: /prefer.*local.*status/i,
|
|
207
|
+
message: "Should prefer external status"
|
|
208
|
+
}
|
|
209
|
+
];
|
|
210
|
+
for (const { pattern, message } of incorrectPatterns) {
|
|
211
|
+
if (pattern.test(implementationCode)) {
|
|
212
|
+
violations.push(message);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const requiredPatterns = [
|
|
216
|
+
{
|
|
217
|
+
pattern: /external.*wins|EXTERNAL.*WINS|externalStatus.*applied/i,
|
|
218
|
+
message: "Missing confirmation that external wins"
|
|
219
|
+
}
|
|
220
|
+
];
|
|
221
|
+
for (const { pattern, message } of requiredPatterns) {
|
|
222
|
+
if (!pattern.test(implementationCode)) {
|
|
223
|
+
violations.push(message);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
valid: violations.length === 0,
|
|
228
|
+
violations
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Get resolution history
|
|
233
|
+
*/
|
|
234
|
+
getResolutionLog() {
|
|
235
|
+
return this.resolutionLog;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Generate resolution report
|
|
239
|
+
*/
|
|
240
|
+
generateReport() {
|
|
241
|
+
const report = [];
|
|
242
|
+
report.push("# Conflict Resolution Report");
|
|
243
|
+
report.push(`
|
|
244
|
+
**Generated**: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
245
|
+
report.push(`**Total Resolutions**: ${this.resolutionLog.length}`);
|
|
246
|
+
report.push("\n## Resolutions\n");
|
|
247
|
+
for (const resolution of this.resolutionLog) {
|
|
248
|
+
report.push(`### ${resolution.field}`);
|
|
249
|
+
report.push(`- **Local Value**: ${resolution.localValue}`);
|
|
250
|
+
report.push(`- **External Value**: ${resolution.externalValue}`);
|
|
251
|
+
report.push(`- **Resolution**: ${resolution.resolution.toUpperCase()} WINS`);
|
|
252
|
+
report.push(`- **Resolved To**: ${resolution.resolvedValue}`);
|
|
253
|
+
report.push(`- **Reason**: ${resolution.reason}`);
|
|
254
|
+
report.push(`- **Time**: ${resolution.timestamp}
|
|
255
|
+
`);
|
|
256
|
+
}
|
|
257
|
+
report.push("## Validation");
|
|
258
|
+
report.push("\u2705 All conflicts resolved with external tool priority");
|
|
259
|
+
return report.join("\n");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async function loadSpecMetadata(specPath) {
|
|
263
|
+
const content = await fs.readFile(specPath, "utf-8");
|
|
264
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
265
|
+
if (!frontmatterMatch) {
|
|
266
|
+
throw new Error(`No frontmatter found in ${specPath}`);
|
|
267
|
+
}
|
|
268
|
+
return yaml.parse(frontmatterMatch[1]);
|
|
269
|
+
}
|
|
270
|
+
async function performBidirectionalSync(specPath, externalStatus) {
|
|
271
|
+
const resolver = new ConflictResolver();
|
|
272
|
+
const spec = await loadSpecMetadata(specPath);
|
|
273
|
+
const resolutions = [];
|
|
274
|
+
if (spec.status !== externalStatus.mappedStatus) {
|
|
275
|
+
const statusResolution = resolver.resolveStatusConflict(
|
|
276
|
+
spec.status,
|
|
277
|
+
externalStatus
|
|
278
|
+
);
|
|
279
|
+
resolutions.push(statusResolution);
|
|
280
|
+
}
|
|
281
|
+
if (resolutions.length > 0) {
|
|
282
|
+
await resolver.applyResolutions(specPath, resolutions);
|
|
283
|
+
const report = resolver.generateReport();
|
|
284
|
+
const reportPath = specPath.replace(".md", "-sync-report.md");
|
|
285
|
+
await fs.writeFile(reportPath, report);
|
|
286
|
+
console.log(`\u{1F4C4} Sync report saved to ${path.basename(reportPath)}`);
|
|
287
|
+
} else {
|
|
288
|
+
console.log("\u2705 No conflicts detected - spec in sync with external tool");
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
var conflict_resolver_default = ConflictResolver;
|
|
292
|
+
export {
|
|
293
|
+
ConflictResolver,
|
|
294
|
+
conflict_resolver_default as default,
|
|
295
|
+
loadSpecMetadata,
|
|
296
|
+
performBidirectionalSync
|
|
297
|
+
};
|