specweave 1.0.508 → 1.0.509
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/dist/src/adapters/aider/adapter.d.ts.map +1 -1
- package/dist/src/adapters/aider/adapter.js +9 -9
- package/dist/src/adapters/aider/adapter.js.map +1 -1
- package/dist/src/adapters/amazonq/adapter.d.ts.map +1 -1
- package/dist/src/adapters/amazonq/adapter.js +9 -9
- package/dist/src/adapters/amazonq/adapter.js.map +1 -1
- package/dist/src/adapters/cline/adapter.d.ts.map +1 -1
- package/dist/src/adapters/cline/adapter.js +9 -9
- package/dist/src/adapters/cline/adapter.js.map +1 -1
- package/dist/src/adapters/codex/adapter.d.ts +3 -3
- package/dist/src/adapters/codex/adapter.js +10 -10
- package/dist/src/adapters/codex/adapter.js.map +1 -1
- package/dist/src/adapters/continue/adapter.d.ts +3 -3
- package/dist/src/adapters/continue/adapter.d.ts.map +1 -1
- package/dist/src/adapters/continue/adapter.js +13 -13
- package/dist/src/adapters/continue/adapter.js.map +1 -1
- package/dist/src/adapters/copilot/adapter.d.ts +3 -4
- package/dist/src/adapters/copilot/adapter.d.ts.map +1 -1
- package/dist/src/adapters/copilot/adapter.js +13 -14
- package/dist/src/adapters/copilot/adapter.js.map +1 -1
- package/dist/src/adapters/cursor/adapter.d.ts +3 -3
- package/dist/src/adapters/cursor/adapter.js +10 -10
- package/dist/src/adapters/cursor/adapter.js.map +1 -1
- package/dist/src/adapters/jetbrains/adapter.d.ts.map +1 -1
- package/dist/src/adapters/jetbrains/adapter.js +10 -10
- package/dist/src/adapters/jetbrains/adapter.js.map +1 -1
- package/dist/src/adapters/registry.yaml +32 -32
- package/dist/src/adapters/tabnine/adapter.d.ts.map +1 -1
- package/dist/src/adapters/tabnine/adapter.js +9 -9
- package/dist/src/adapters/tabnine/adapter.js.map +1 -1
- package/dist/src/adapters/trae/adapter.d.ts.map +1 -1
- package/dist/src/adapters/trae/adapter.js +9 -9
- package/dist/src/adapters/trae/adapter.js.map +1 -1
- package/dist/src/adapters/windsurf/adapter.d.ts +3 -4
- package/dist/src/adapters/windsurf/adapter.d.ts.map +1 -1
- package/dist/src/adapters/windsurf/adapter.js +13 -14
- package/dist/src/adapters/windsurf/adapter.js.map +1 -1
- package/dist/src/adapters/zed/adapter.d.ts.map +1 -1
- package/dist/src/adapters/zed/adapter.js +9 -9
- package/dist/src/adapters/zed/adapter.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/agents/sw-architect.md +1 -1
- package/plugins/specweave/lib/integrations/jira/jira-spec-sync.js +100 -90
- package/plugins/specweave/lib/integrations/jira/jira-status-sync.js +54 -2
- package/plugins/specweave/skills/architect/SKILL.md +2 -2
- package/plugins/specweave/skills/team-lead/agents/frontend.md +1 -2
|
@@ -8,7 +8,7 @@ export class ZedAdapter extends AdapterBase {
|
|
|
8
8
|
constructor() {
|
|
9
9
|
super(...arguments);
|
|
10
10
|
this.name = 'zed';
|
|
11
|
-
this.description = 'Zed adapter — skills installed in .
|
|
11
|
+
this.description = 'Zed adapter — skills installed in .zed/skills/';
|
|
12
12
|
this.automationLevel = 'semi';
|
|
13
13
|
}
|
|
14
14
|
async detect() {
|
|
@@ -19,7 +19,7 @@ export class ZedAdapter extends AdapterBase {
|
|
|
19
19
|
}
|
|
20
20
|
async install(options) {
|
|
21
21
|
console.log('\n📦 Installing Zed Adapter\n');
|
|
22
|
-
await fs.ensureDir(path.join(options.projectPath, '.
|
|
22
|
+
await fs.ensureDir(path.join(options.projectPath, '.zed/skills'));
|
|
23
23
|
console.log('\n✨ Zed adapter installed!');
|
|
24
24
|
console.log(' Run `specweave refresh-plugins` to install skills.');
|
|
25
25
|
}
|
|
@@ -27,20 +27,20 @@ export class ZedAdapter extends AdapterBase {
|
|
|
27
27
|
return true;
|
|
28
28
|
}
|
|
29
29
|
async compilePlugin(plugin) {
|
|
30
|
-
const
|
|
30
|
+
const skillsDir = '.zed/skills';
|
|
31
31
|
console.log(`\n📦 Installing plugin skills for Zed: ${plugin.manifest.name}`);
|
|
32
|
-
await this.writeSkillFiles(plugin,
|
|
33
|
-
console.log(` ✓ ${plugin.skills.length} skill(s) written to ${
|
|
32
|
+
await this.writeSkillFiles(plugin, skillsDir);
|
|
33
|
+
console.log(` ✓ ${plugin.skills.length} skill(s) written to ${skillsDir}/`);
|
|
34
34
|
console.log(`\n✅ Plugin ${plugin.manifest.name} installed for Zed!`);
|
|
35
35
|
}
|
|
36
36
|
async unloadPlugin(pluginName) {
|
|
37
37
|
console.log(`\n🗑️ Unloading plugin from Zed: ${pluginName}`);
|
|
38
|
-
await this.removeSkillFiles(pluginName, '.
|
|
39
|
-
console.log(` ✓ Removed from .
|
|
38
|
+
await this.removeSkillFiles(pluginName, '.zed/skills');
|
|
39
|
+
console.log(` ✓ Removed from .zed/skills/`);
|
|
40
40
|
console.log(`\n✅ Plugin ${pluginName} unloaded!`);
|
|
41
41
|
}
|
|
42
42
|
async getInstalledPlugins() {
|
|
43
|
-
return await this.listInstalledPluginsInDir('.
|
|
43
|
+
return await this.listInstalledPluginsInDir('.zed/skills');
|
|
44
44
|
}
|
|
45
45
|
async postInstall(options) {
|
|
46
46
|
console.log(this.getInstructions());
|
|
@@ -49,7 +49,7 @@ export class ZedAdapter extends AdapterBase {
|
|
|
49
49
|
return `
|
|
50
50
|
Zed Adapter
|
|
51
51
|
|
|
52
|
-
Skills installed in .
|
|
52
|
+
Skills installed in .zed/skills/.
|
|
53
53
|
Run: specweave refresh-plugins
|
|
54
54
|
`;
|
|
55
55
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../../src/adapters/zed/adapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAIjD,MAAM,OAAO,UAAW,SAAQ,WAAW;IAA3C;;QACE,SAAI,GAAG,KAAK,CAAC;QACb,gBAAW,GAAG,
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../../src/adapters/zed/adapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAIjD,MAAM,OAAO,UAAW,SAAQ,WAAW;IAA3C;;QACE,SAAI,GAAG,KAAK,CAAC;QACb,gBAAW,GAAG,gDAAgD,CAAC;QAC/D,oBAAe,GAAG,MAAe,CAAC;IAoDpC,CAAC;IAlDC,KAAK,CAAC,MAAM;QACV,OAAO,MAAM,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAC1E,CAAC;IAED,QAAQ;QACN,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAuB;QACnC,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;QAC7C,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC;QAClE,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;IACvE,CAAC;IAED,eAAe;QACb,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,MAAc;QAChC,MAAM,SAAS,GAAG,aAAa,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,0CAA0C,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9E,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,QAAQ,MAAM,CAAC,MAAM,CAAC,MAAM,wBAAwB,SAAS,GAAG,CAAC,CAAC;QAC9E,OAAO,CAAC,GAAG,CAAC,cAAc,MAAM,CAAC,QAAQ,CAAC,IAAI,qBAAqB,CAAC,CAAC;IACvE,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,UAAkB;QACnC,OAAO,CAAC,GAAG,CAAC,qCAAqC,UAAU,EAAE,CAAC,CAAC;QAC/D,MAAM,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,cAAc,UAAU,YAAY,CAAC,CAAC;IACpD,CAAC;IAED,KAAK,CAAC,mBAAmB;QACvB,OAAO,MAAM,IAAI,CAAC,yBAAyB,CAAC,aAAa,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAuB;QACvC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,eAAe;QACb,OAAO;;;;;KAKN,CAAC;IACJ,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specweave",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.509",
|
|
4
4
|
"description": "100+ domain-expert AI skills — PM, Architect, Frontend, QA, Security and more. Skills learn your team's patterns permanently. Spec-first planning, autonomous execution, multi-agent teams, synced to GitHub/JIRA. Claude Code, Cursor, Copilot & more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,4 +19,4 @@ The `sw:architect` skill is preloaded with full domain logic including design pa
|
|
|
19
19
|
|
|
20
20
|
- **Register skill-chain marker** (STEP 0 in preloaded skill) before writing plan.md
|
|
21
21
|
- **Check ADRs first** at `.specweave/docs/internal/architecture/adr/`
|
|
22
|
-
- **Domain delegation** — after plan.md, recommend domain skills (sw:architect for all domains
|
|
22
|
+
- **Domain delegation** — after plan.md, recommend domain skills (sw:architect for all domains)
|
|
@@ -9,8 +9,6 @@ import { toDescription } from "./content-format-adapter.js";
|
|
|
9
9
|
import { getEpicLinkFieldForProject } from "./jira-field-discovery.js";
|
|
10
10
|
import { searchAllIssues } from "./jira-paginated-search.js";
|
|
11
11
|
import axios from "axios";
|
|
12
|
-
import { CircuitBreakerRegistry } from "../../../../../src/core/sync/circuit-breaker-registry.js";
|
|
13
|
-
import { SyncRetryQueue } from "../../../../../src/core/sync/sync-retry-queue.js";
|
|
14
12
|
import { SyncError } from "../../../../../src/core/errors/sync-error.js";
|
|
15
13
|
import { LockManager } from "../../../../../src/utils/lock-manager.js";
|
|
16
14
|
function buildStoryDescription(us) {
|
|
@@ -55,6 +53,9 @@ class JiraSpecSync {
|
|
|
55
53
|
}
|
|
56
54
|
});
|
|
57
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Execute fn under file lock (if lockManager configured).
|
|
58
|
+
*/
|
|
58
59
|
async withLock(fn) {
|
|
59
60
|
if (!this.lockManager) return fn();
|
|
60
61
|
const acquired = await this.lockManager.acquire();
|
|
@@ -67,6 +68,9 @@ class JiraSpecSync {
|
|
|
67
68
|
await this.lockManager.release();
|
|
68
69
|
}
|
|
69
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Check circuit breaker before making API calls.
|
|
73
|
+
*/
|
|
70
74
|
checkCircuitBreaker() {
|
|
71
75
|
if (!this.circuitBreakerRegistry) return;
|
|
72
76
|
const breaker = this.circuitBreakerRegistry.get("jira");
|
|
@@ -74,10 +78,16 @@ class JiraSpecSync {
|
|
|
74
78
|
throw new SyncError("jira", 0, "", "Circuit breaker open for jira");
|
|
75
79
|
}
|
|
76
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Record success on circuit breaker.
|
|
83
|
+
*/
|
|
77
84
|
recordApiSuccess() {
|
|
78
85
|
if (!this.circuitBreakerRegistry) return;
|
|
79
86
|
this.circuitBreakerRegistry.get("jira").recordSuccess();
|
|
80
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Record failure on circuit breaker and optionally enqueue retry.
|
|
90
|
+
*/
|
|
81
91
|
async recordApiFailure(error, operation) {
|
|
82
92
|
const httpStatus = error?.response?.status ?? 0;
|
|
83
93
|
if (this.circuitBreakerRegistry) {
|
|
@@ -111,56 +121,56 @@ class JiraSpecSync {
|
|
|
111
121
|
console.log(`
|
|
112
122
|
\u{1F504} Syncing spec ${specId} to Jira Epic...`);
|
|
113
123
|
return this.withLock(async () => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
124
|
+
this.checkCircuitBreaker();
|
|
125
|
+
try {
|
|
126
|
+
const spec = await this.specManager.loadSpec(specId);
|
|
127
|
+
if (!spec) {
|
|
128
|
+
return {
|
|
129
|
+
success: false,
|
|
130
|
+
specId,
|
|
131
|
+
provider: "jira",
|
|
132
|
+
error: `Spec ${specId} not found`
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const existingLink = spec.metadata.externalLinks?.jira;
|
|
136
|
+
let epic;
|
|
137
|
+
if (existingLink?.epicKey) {
|
|
138
|
+
console.log(` Found existing Jira Epic ${existingLink.epicKey}`);
|
|
139
|
+
epic = await this.updateJiraEpic(existingLink.epicKey, spec);
|
|
140
|
+
} else {
|
|
141
|
+
console.log(" Creating new Jira Epic...");
|
|
142
|
+
epic = await this.createJiraEpic(spec);
|
|
143
|
+
await this.specManager.linkToExternal(specId, "jira", {
|
|
144
|
+
id: epic.key,
|
|
145
|
+
url: epic.url,
|
|
146
|
+
projectKey: this.config.projectKey,
|
|
147
|
+
domain: this.config.domain
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const changes = await this.syncUserStories(epic.key, spec);
|
|
151
|
+
this.recordApiSuccess();
|
|
152
|
+
console.log("\u2705 Sync complete!");
|
|
153
|
+
return {
|
|
154
|
+
success: true,
|
|
155
|
+
specId,
|
|
156
|
+
provider: "jira",
|
|
157
|
+
externalId: epic.key,
|
|
158
|
+
url: epic.url,
|
|
159
|
+
changes
|
|
160
|
+
};
|
|
161
|
+
} catch (error) {
|
|
162
|
+
await this.recordApiFailure(error, "syncSpecToJira");
|
|
163
|
+
const axiosData = error?.response?.data;
|
|
164
|
+
const detail = axiosData ? JSON.stringify(axiosData) : "";
|
|
165
|
+
console.error("\u274C Error syncing to Jira:", error?.message || error, detail ? `
|
|
166
|
+
Response: ${detail}` : "");
|
|
118
167
|
return {
|
|
119
168
|
success: false,
|
|
120
169
|
specId,
|
|
121
170
|
provider: "jira",
|
|
122
|
-
error:
|
|
171
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
123
172
|
};
|
|
124
173
|
}
|
|
125
|
-
const existingLink = spec.metadata.externalLinks?.jira;
|
|
126
|
-
let epic;
|
|
127
|
-
if (existingLink?.epicKey) {
|
|
128
|
-
console.log(` Found existing Jira Epic ${existingLink.epicKey}`);
|
|
129
|
-
epic = await this.updateJiraEpic(existingLink.epicKey, spec);
|
|
130
|
-
} else {
|
|
131
|
-
console.log(" Creating new Jira Epic...");
|
|
132
|
-
epic = await this.createJiraEpic(spec);
|
|
133
|
-
await this.specManager.linkToExternal(specId, "jira", {
|
|
134
|
-
id: epic.key,
|
|
135
|
-
url: epic.url,
|
|
136
|
-
projectKey: this.config.projectKey,
|
|
137
|
-
domain: this.config.domain
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
const changes = await this.syncUserStories(epic.key, spec);
|
|
141
|
-
this.recordApiSuccess();
|
|
142
|
-
console.log("\u2705 Sync complete!");
|
|
143
|
-
return {
|
|
144
|
-
success: true,
|
|
145
|
-
specId,
|
|
146
|
-
provider: "jira",
|
|
147
|
-
externalId: epic.key,
|
|
148
|
-
url: epic.url,
|
|
149
|
-
changes
|
|
150
|
-
};
|
|
151
|
-
} catch (error) {
|
|
152
|
-
await this.recordApiFailure(error, "syncSpecToJira");
|
|
153
|
-
const axiosData = error?.response?.data;
|
|
154
|
-
const detail = axiosData ? JSON.stringify(axiosData) : "";
|
|
155
|
-
console.error("\u274C Error syncing to Jira:", error?.message || error, detail ? `
|
|
156
|
-
Response: ${detail}` : "");
|
|
157
|
-
return {
|
|
158
|
-
success: false,
|
|
159
|
-
specId,
|
|
160
|
-
provider: "jira",
|
|
161
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
174
|
});
|
|
165
175
|
}
|
|
166
176
|
/**
|
|
@@ -170,60 +180,60 @@ class JiraSpecSync {
|
|
|
170
180
|
console.log(`
|
|
171
181
|
\u{1F504} Syncing FROM Jira to spec ${specId}...`);
|
|
172
182
|
return this.withLock(async () => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
this.checkCircuitBreaker();
|
|
184
|
+
try {
|
|
185
|
+
const spec = await this.specManager.loadSpec(specId);
|
|
186
|
+
if (!spec) {
|
|
187
|
+
return {
|
|
188
|
+
success: false,
|
|
189
|
+
specId,
|
|
190
|
+
provider: "jira",
|
|
191
|
+
error: `Spec ${specId} not found`
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const jiraLink = spec.metadata.externalLinks?.jira;
|
|
195
|
+
if (!jiraLink?.epicKey) {
|
|
196
|
+
return {
|
|
197
|
+
success: false,
|
|
198
|
+
specId,
|
|
199
|
+
provider: "jira",
|
|
200
|
+
error: "Spec not linked to Jira Epic"
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const epic = await this.fetchJiraEpic(jiraLink.epicKey);
|
|
204
|
+
const conflicts = await this.detectConflicts(spec, epic);
|
|
205
|
+
if (conflicts.length === 0) {
|
|
206
|
+
console.log("\u2705 No conflicts - spec and Jira in sync");
|
|
207
|
+
return {
|
|
208
|
+
success: true,
|
|
209
|
+
specId,
|
|
210
|
+
provider: "jira",
|
|
211
|
+
externalId: epic.key,
|
|
212
|
+
url: epic.url
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
console.log(`\u26A0\uFE0F Detected ${conflicts.length} conflict(s)`);
|
|
216
|
+
await this.writeConflictReport(specId, conflicts);
|
|
217
|
+
await this.resolveConflicts(spec, conflicts);
|
|
218
|
+
console.log("\u2705 Sync FROM Jira complete!");
|
|
177
219
|
return {
|
|
178
|
-
success:
|
|
220
|
+
success: true,
|
|
179
221
|
specId,
|
|
180
222
|
provider: "jira",
|
|
181
|
-
|
|
223
|
+
externalId: epic.key,
|
|
224
|
+
url: epic.url,
|
|
225
|
+
conflicts
|
|
182
226
|
};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
227
|
+
} catch (error) {
|
|
228
|
+
await this.recordApiFailure(error, "syncFromJira");
|
|
229
|
+
console.error("\u274C Error syncing FROM Jira:", error);
|
|
186
230
|
return {
|
|
187
231
|
success: false,
|
|
188
232
|
specId,
|
|
189
233
|
provider: "jira",
|
|
190
|
-
error:
|
|
234
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
191
235
|
};
|
|
192
236
|
}
|
|
193
|
-
const epic = await this.fetchJiraEpic(jiraLink.epicKey);
|
|
194
|
-
const conflicts = await this.detectConflicts(spec, epic);
|
|
195
|
-
if (conflicts.length === 0) {
|
|
196
|
-
console.log("\u2705 No conflicts - spec and Jira in sync");
|
|
197
|
-
return {
|
|
198
|
-
success: true,
|
|
199
|
-
specId,
|
|
200
|
-
provider: "jira",
|
|
201
|
-
externalId: epic.key,
|
|
202
|
-
url: epic.url
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
console.log(`\u26A0\uFE0F Detected ${conflicts.length} conflict(s)`);
|
|
206
|
-
await this.writeConflictReport(specId, conflicts);
|
|
207
|
-
await this.resolveConflicts(spec, conflicts);
|
|
208
|
-
console.log("\u2705 Sync FROM Jira complete!");
|
|
209
|
-
return {
|
|
210
|
-
success: true,
|
|
211
|
-
specId,
|
|
212
|
-
provider: "jira",
|
|
213
|
-
externalId: epic.key,
|
|
214
|
-
url: epic.url,
|
|
215
|
-
conflicts
|
|
216
|
-
};
|
|
217
|
-
} catch (error) {
|
|
218
|
-
await this.recordApiFailure(error, "syncFromJira");
|
|
219
|
-
console.error("\u274C Error syncing FROM Jira:", error);
|
|
220
|
-
return {
|
|
221
|
-
success: false,
|
|
222
|
-
specId,
|
|
223
|
-
provider: "jira",
|
|
224
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
237
|
});
|
|
228
238
|
}
|
|
229
239
|
/**
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
import { detectDeploymentType, getApiBaseUrl } from "./jira-deployment-detector.js";
|
|
3
3
|
import { toCommentBody } from "./content-format-adapter.js";
|
|
4
|
-
import { CircuitBreakerRegistry } from "../../../../../src/core/sync/circuit-breaker-registry.js";
|
|
5
|
-
import { SyncRetryQueue } from "../../../../../src/core/sync/sync-retry-queue.js";
|
|
6
4
|
import { SyncError } from "../../../../../src/core/errors/sync-error.js";
|
|
7
5
|
import { LockManager } from "../../../../../src/utils/lock-manager.js";
|
|
8
6
|
class JiraStatusSync {
|
|
@@ -30,6 +28,9 @@ class JiraStatusSync {
|
|
|
30
28
|
}
|
|
31
29
|
});
|
|
32
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Execute fn under file lock (if lockManager configured).
|
|
33
|
+
*/
|
|
33
34
|
async withLock(fn) {
|
|
34
35
|
if (!this.lockManager) return fn();
|
|
35
36
|
const acquired = await this.lockManager.acquire();
|
|
@@ -42,6 +43,10 @@ class JiraStatusSync {
|
|
|
42
43
|
await this.lockManager.release();
|
|
43
44
|
}
|
|
44
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Check circuit breaker before making API calls.
|
|
48
|
+
* Throws if breaker is open.
|
|
49
|
+
*/
|
|
45
50
|
checkCircuitBreaker() {
|
|
46
51
|
if (!this.circuitBreakerRegistry) return;
|
|
47
52
|
const breaker = this.circuitBreakerRegistry.get("jira");
|
|
@@ -49,10 +54,16 @@ class JiraStatusSync {
|
|
|
49
54
|
throw new SyncError("jira", 0, "", "Circuit breaker open for jira");
|
|
50
55
|
}
|
|
51
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Record success on circuit breaker.
|
|
59
|
+
*/
|
|
52
60
|
recordSuccess() {
|
|
53
61
|
if (!this.circuitBreakerRegistry) return;
|
|
54
62
|
this.circuitBreakerRegistry.get("jira").recordSuccess();
|
|
55
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Handle API error: record failure on circuit breaker, enqueue retry, throw SyncError.
|
|
66
|
+
*/
|
|
56
67
|
async handleApiError(error, operation) {
|
|
57
68
|
const httpStatus = error?.response?.status ?? 0;
|
|
58
69
|
const responseBody = JSON.stringify(error?.response?.data ?? "");
|
|
@@ -72,6 +83,9 @@ class JiraStatusSync {
|
|
|
72
83
|
}
|
|
73
84
|
throw new SyncError("jira", httpStatus, responseBody, detail);
|
|
74
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Initialize: detect deployment type and update client baseURL
|
|
88
|
+
*/
|
|
75
89
|
async init() {
|
|
76
90
|
const deployment = await detectDeploymentType(this.domain, {
|
|
77
91
|
email: this.client.defaults.auth?.username || "",
|
|
@@ -79,6 +93,12 @@ class JiraStatusSync {
|
|
|
79
93
|
});
|
|
80
94
|
this.client.defaults.baseURL = deployment.baseUrl;
|
|
81
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Get current status from JIRA issue
|
|
98
|
+
*
|
|
99
|
+
* @param issueKey - JIRA issue key (e.g., PROJ-123)
|
|
100
|
+
* @returns Current issue status
|
|
101
|
+
*/
|
|
82
102
|
async getStatus(issueKey) {
|
|
83
103
|
return this.withLock(async () => {
|
|
84
104
|
this.checkCircuitBreaker();
|
|
@@ -93,6 +113,19 @@ class JiraStatusSync {
|
|
|
93
113
|
}
|
|
94
114
|
});
|
|
95
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Update JIRA issue status via transitions
|
|
118
|
+
*
|
|
119
|
+
* JIRA requires using transitions to change status.
|
|
120
|
+
* Cannot directly set status field.
|
|
121
|
+
*
|
|
122
|
+
* Handles missing transitions gracefully by logging a warning
|
|
123
|
+
* instead of throwing an error.
|
|
124
|
+
*
|
|
125
|
+
* @param issueKey - JIRA issue key (e.g., PROJ-123)
|
|
126
|
+
* @param status - Desired status
|
|
127
|
+
* @returns true if transition succeeded, false if not available
|
|
128
|
+
*/
|
|
96
129
|
async updateStatus(issueKey, status) {
|
|
97
130
|
return this.withLock(async () => {
|
|
98
131
|
this.checkCircuitBreaker();
|
|
@@ -120,6 +153,13 @@ class JiraStatusSync {
|
|
|
120
153
|
}
|
|
121
154
|
});
|
|
122
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Post comment about status change to JIRA issue
|
|
158
|
+
*
|
|
159
|
+
* @param issueKey - JIRA issue key (e.g., PROJ-123)
|
|
160
|
+
* @param oldStatus - Previous SpecWeave status
|
|
161
|
+
* @param newStatus - New SpecWeave status
|
|
162
|
+
*/
|
|
123
163
|
async postStatusComment(issueKey, oldStatus, newStatus) {
|
|
124
164
|
return this.withLock(async () => {
|
|
125
165
|
this.checkCircuitBreaker();
|
|
@@ -142,6 +182,18 @@ _Synced from SpecWeave_`;
|
|
|
142
182
|
}
|
|
143
183
|
});
|
|
144
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Post AC progress comment with proper ADF formatting and dedup.
|
|
187
|
+
*
|
|
188
|
+
* Builds native ADF with:
|
|
189
|
+
* - Bold header showing completion percentage
|
|
190
|
+
* - Bullet list with checkmark/cross emojis per AC
|
|
191
|
+
* - Fingerprint marker to prevent duplicate comments
|
|
192
|
+
*
|
|
193
|
+
* @param issueKey - JIRA issue key (e.g., PROJ-123)
|
|
194
|
+
* @param acStates - Array of AC states with id, description, completed
|
|
195
|
+
* @returns true if comment was posted, false if skipped (duplicate)
|
|
196
|
+
*/
|
|
145
197
|
async postProgressComment(issueKey, acStates) {
|
|
146
198
|
return this.withLock(async () => {
|
|
147
199
|
this.checkCircuitBreaker();
|
|
@@ -109,8 +109,8 @@ AskUserQuestion({
|
|
|
109
109
|
## Delegation
|
|
110
110
|
|
|
111
111
|
After architecture is ready, delegate to domain skills:
|
|
112
|
-
- Frontend: `sw:architect` (implement UI directly
|
|
113
|
-
- Backend: `sw:architect` (implement API/services directly
|
|
112
|
+
- Frontend: `sw:architect` (implement UI directly)
|
|
113
|
+
- Backend: `sw:architect` (implement API/services directly)
|
|
114
114
|
|
|
115
115
|
Output: `plan.md` with architecture decisions and component breakdown.
|
|
116
116
|
|
|
@@ -9,8 +9,7 @@ MASTER SPEC (SOURCE OF TRUTH):
|
|
|
9
9
|
SKILLS TO INVOKE:
|
|
10
10
|
Skill({ skill: "sw:architect" }) // architecture and component design
|
|
11
11
|
Skill({ skill: "sw:service-connect" }) // for external service setup
|
|
12
|
-
//
|
|
13
|
-
// If not installed, implement frontend work directly using code tools.
|
|
12
|
+
// Implement frontend work directly using code tools.
|
|
14
13
|
|
|
15
14
|
FILE OWNERSHIP (WRITE access):
|
|
16
15
|
src/components/**
|