specweave 1.0.304 → 1.0.305
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/plugins/specweave-github/lib/github-us-auto-closer.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-us-auto-closer.js +43 -23
- package/dist/plugins/specweave-github/lib/github-us-auto-closer.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/skills/architect/SKILL.md +12 -0
- package/plugins/specweave-github/lib/github-us-auto-closer.js +31 -19
- package/plugins/specweave-github/lib/github-us-auto-closer.ts +43 -25
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-us-auto-closer.d.ts","sourceRoot":"","sources":["../../../../plugins/specweave-github/lib/github-us-auto-closer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"github-us-auto-closer.d.ts","sourceRoot":"","sources":["../../../../plugins/specweave-github/lib/github-us-auto-closer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAaD;;;;;;;;;;;;GAYG;AACH,wBAAsB,6BAA6B,CACjD,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EAAE,EACvB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,CAsF1B"}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* @module github-us-auto-closer
|
|
9
9
|
*/
|
|
10
10
|
import { readFile } from 'fs/promises';
|
|
11
|
+
import { existsSync } from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
11
13
|
import { execFileNoThrow } from '../../../src/utils/execFileNoThrow.js';
|
|
12
14
|
/**
|
|
13
15
|
* Auto-close GitHub issues for user stories with all ACs complete.
|
|
@@ -38,7 +40,7 @@ export async function autoCloseCompletedUserStories(incrementId, affectedUSIds,
|
|
|
38
40
|
});
|
|
39
41
|
return result;
|
|
40
42
|
}
|
|
41
|
-
const issueLinks = parseIssueLinks(
|
|
43
|
+
const issueLinks = await parseIssueLinks(specPath);
|
|
42
44
|
const repoSlug = `${options.owner}/${options.repo}`;
|
|
43
45
|
const env = options.token ? { GH_TOKEN: options.token } : undefined;
|
|
44
46
|
const execOpts = env ? { env } : {};
|
|
@@ -104,31 +106,49 @@ function buildCompletionComment(incrementId, usId, acStates) {
|
|
|
104
106
|
return comment;
|
|
105
107
|
}
|
|
106
108
|
/**
|
|
107
|
-
* Parse issue links from
|
|
109
|
+
* Parse issue links from metadata.json (sibling of spec.md).
|
|
110
|
+
*
|
|
111
|
+
* Supports TWO formats:
|
|
112
|
+
* - OLD: metadata.github.issues[] with { userStory, number, url }
|
|
113
|
+
* - NEW: metadata.externalLinks.github.issues with { [US-XXX]: { issueNumber, issueUrl } }
|
|
114
|
+
*
|
|
115
|
+
* Falls back to empty if metadata.json is missing or invalid.
|
|
108
116
|
*/
|
|
109
|
-
function parseIssueLinks(
|
|
117
|
+
async function parseIssueLinks(specPath) {
|
|
110
118
|
const links = {};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
links[idMatch[1]] = {
|
|
128
|
-
issueNumber: parseInt(numMatch[1], 10),
|
|
129
|
-
issueUrl: urlMatch ? urlMatch[1] : '',
|
|
130
|
-
};
|
|
119
|
+
try {
|
|
120
|
+
const metadataPath = path.join(path.dirname(specPath), 'metadata.json');
|
|
121
|
+
if (!existsSync(metadataPath))
|
|
122
|
+
return links;
|
|
123
|
+
const raw = await readFile(metadataPath, 'utf-8');
|
|
124
|
+
const metadata = JSON.parse(raw);
|
|
125
|
+
// OLD format: metadata.github.issues[] array
|
|
126
|
+
if (metadata.github?.issues && Array.isArray(metadata.github.issues)) {
|
|
127
|
+
for (const entry of metadata.github.issues) {
|
|
128
|
+
if (entry.userStory && entry.number) {
|
|
129
|
+
links[entry.userStory] = {
|
|
130
|
+
issueNumber: entry.number,
|
|
131
|
+
issueUrl: entry.url || '',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
131
135
|
}
|
|
136
|
+
// NEW format: metadata.externalLinks.github.issues object
|
|
137
|
+
if (metadata.externalLinks?.github?.issues) {
|
|
138
|
+
const issues = metadata.externalLinks.github.issues;
|
|
139
|
+
for (const [usId, data] of Object.entries(issues)) {
|
|
140
|
+
const issueData = data;
|
|
141
|
+
if (issueData.issueNumber) {
|
|
142
|
+
links[usId] = {
|
|
143
|
+
issueNumber: issueData.issueNumber,
|
|
144
|
+
issueUrl: issueData.issueUrl || '',
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Graceful fallback: return empty if metadata.json is missing or invalid
|
|
132
152
|
}
|
|
133
153
|
return links;
|
|
134
154
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-us-auto-closer.js","sourceRoot":"","sources":["../../../../plugins/specweave-github/lib/github-us-auto-closer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAyBxE;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,WAAmB,EACnB,aAAuB,EACvB,QAAgB,EAChB,OAAyB;IAEzB,MAAM,MAAM,GAAoB,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAExE,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;YACjB,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC;YACtB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACxD,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,UAAU,GAAG,eAAe,CAAC,
|
|
1
|
+
{"version":3,"file":"github-us-auto-closer.js","sourceRoot":"","sources":["../../../../plugins/specweave-github/lib/github-us-auto-closer.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specweave",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.305",
|
|
4
4
|
"description": "Spec-driven development framework for AI coding agents. Works with Claude Code, Codex, Antigravity, Cursor, Copilot & more. 100+ skills, 49 CLI commands, verified skill certification, autonomous execution, and living documentation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,6 +17,18 @@ Design system architecture with focus on:
|
|
|
17
17
|
3. **Trade-off analysis** — Evaluate options with clear pros/cons
|
|
18
18
|
4. **Technology selection** — Choose stack based on project constraints
|
|
19
19
|
|
|
20
|
+
## Key Architectural Patterns
|
|
21
|
+
|
|
22
|
+
### Code Mode for API-Heavy Services (ADR-0140)
|
|
23
|
+
|
|
24
|
+
When a service exposes 50+ API endpoints to AI agents, avoid exposing each as a separate MCP tool. Instead, use the **Code Mode pattern**: expose a typed schema (OpenAPI/JSON Schema) and let the agent write code to discover and call endpoints. This follows Cloudflare's proven approach (2,500+ endpoints → 2 tools, 99.9% token reduction) and SpecWeave's own "Code First, Tools Second" architecture.
|
|
25
|
+
|
|
26
|
+
**Apply when**: designing agent-facing APIs, MCP servers, or any system where AI agents consume a large surface area.
|
|
27
|
+
|
|
28
|
+
**Reference**: ADR-0140 (Code Execution Over Direct MCP Tool Calls) in `.specweave/docs/internal/architecture/adr/`
|
|
29
|
+
|
|
30
|
+
## Delegation
|
|
31
|
+
|
|
20
32
|
After architecture is ready, delegate to domain skills:
|
|
21
33
|
- Frontend: `sw-frontend:frontend-architect`
|
|
22
34
|
- Backend: `sw-backend:*` (dotnet, nodejs, python, go, java-spring, rust)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { readFile } from "fs/promises";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import * as path from "path";
|
|
2
4
|
import { execFileNoThrow } from "../../../src/utils/execFileNoThrow.js";
|
|
3
5
|
async function autoCloseCompletedUserStories(incrementId, affectedUSIds, specPath, options) {
|
|
4
6
|
const result = { closed: [], skipped: [], errors: [] };
|
|
@@ -15,7 +17,7 @@ async function autoCloseCompletedUserStories(incrementId, affectedUSIds, specPat
|
|
|
15
17
|
});
|
|
16
18
|
return result;
|
|
17
19
|
}
|
|
18
|
-
const issueLinks = parseIssueLinks(
|
|
20
|
+
const issueLinks = await parseIssueLinks(specPath);
|
|
19
21
|
const repoSlug = `${options.owner}/${options.repo}`;
|
|
20
22
|
const env = options.token ? { GH_TOKEN: options.token } : void 0;
|
|
21
23
|
const execOpts = env ? { env } : {};
|
|
@@ -89,26 +91,36 @@ function buildCompletionComment(incrementId, usId, acStates) {
|
|
|
89
91
|
`;
|
|
90
92
|
return comment;
|
|
91
93
|
}
|
|
92
|
-
function parseIssueLinks(
|
|
94
|
+
async function parseIssueLinks(specPath) {
|
|
93
95
|
const links = {};
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
96
|
+
try {
|
|
97
|
+
const metadataPath = path.join(path.dirname(specPath), "metadata.json");
|
|
98
|
+
if (!existsSync(metadataPath)) return links;
|
|
99
|
+
const raw = await readFile(metadataPath, "utf-8");
|
|
100
|
+
const metadata = JSON.parse(raw);
|
|
101
|
+
if (metadata.github?.issues && Array.isArray(metadata.github.issues)) {
|
|
102
|
+
for (const entry of metadata.github.issues) {
|
|
103
|
+
if (entry.userStory && entry.number) {
|
|
104
|
+
links[entry.userStory] = {
|
|
105
|
+
issueNumber: entry.number,
|
|
106
|
+
issueUrl: entry.url || ""
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (metadata.externalLinks?.github?.issues) {
|
|
112
|
+
const issues = metadata.externalLinks.github.issues;
|
|
113
|
+
for (const [usId, data] of Object.entries(issues)) {
|
|
114
|
+
const issueData = data;
|
|
115
|
+
if (issueData.issueNumber) {
|
|
116
|
+
links[usId] = {
|
|
117
|
+
issueNumber: issueData.issueNumber,
|
|
118
|
+
issueUrl: issueData.issueUrl || ""
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
111
122
|
}
|
|
123
|
+
} catch {
|
|
112
124
|
}
|
|
113
125
|
return links;
|
|
114
126
|
}
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { readFile } from 'fs/promises';
|
|
12
|
+
import { existsSync } from 'fs';
|
|
13
|
+
import * as path from 'path';
|
|
12
14
|
import { execFileNoThrow } from '../../../src/utils/execFileNoThrow.js';
|
|
13
15
|
|
|
14
16
|
export interface AutoCloseOptions {
|
|
@@ -70,7 +72,7 @@ export async function autoCloseCompletedUserStories(
|
|
|
70
72
|
return result;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
const issueLinks = parseIssueLinks(
|
|
75
|
+
const issueLinks = await parseIssueLinks(specPath);
|
|
74
76
|
const repoSlug = `${options.owner}/${options.repo}`;
|
|
75
77
|
const env = options.token ? { GH_TOKEN: options.token } : undefined;
|
|
76
78
|
const execOpts = env ? { env } : {};
|
|
@@ -166,35 +168,51 @@ function buildCompletionComment(
|
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
/**
|
|
169
|
-
* Parse issue links from
|
|
171
|
+
* Parse issue links from metadata.json (sibling of spec.md).
|
|
172
|
+
*
|
|
173
|
+
* Supports TWO formats:
|
|
174
|
+
* - OLD: metadata.github.issues[] with { userStory, number, url }
|
|
175
|
+
* - NEW: metadata.externalLinks.github.issues with { [US-XXX]: { issueNumber, issueUrl } }
|
|
176
|
+
*
|
|
177
|
+
* Falls back to empty if metadata.json is missing or invalid.
|
|
170
178
|
*/
|
|
171
|
-
function parseIssueLinks(
|
|
179
|
+
async function parseIssueLinks(specPath: string): Promise<Record<string, ParsedUSIssueLink>> {
|
|
172
180
|
const links: Record<string, ParsedUSIssueLink> = {};
|
|
173
181
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
182
|
+
try {
|
|
183
|
+
const metadataPath = path.join(path.dirname(specPath), 'metadata.json');
|
|
184
|
+
if (!existsSync(metadataPath)) return links;
|
|
185
|
+
|
|
186
|
+
const raw = await readFile(metadataPath, 'utf-8');
|
|
187
|
+
const metadata = JSON.parse(raw);
|
|
188
|
+
|
|
189
|
+
// OLD format: metadata.github.issues[] array
|
|
190
|
+
if (metadata.github?.issues && Array.isArray(metadata.github.issues)) {
|
|
191
|
+
for (const entry of metadata.github.issues) {
|
|
192
|
+
if (entry.userStory && entry.number) {
|
|
193
|
+
links[entry.userStory] = {
|
|
194
|
+
issueNumber: entry.number,
|
|
195
|
+
issueUrl: entry.url || '',
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
191
200
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
201
|
+
// NEW format: metadata.externalLinks.github.issues object
|
|
202
|
+
if (metadata.externalLinks?.github?.issues) {
|
|
203
|
+
const issues = metadata.externalLinks.github.issues;
|
|
204
|
+
for (const [usId, data] of Object.entries(issues)) {
|
|
205
|
+
const issueData = data as { issueNumber?: number; issueUrl?: string };
|
|
206
|
+
if (issueData.issueNumber) {
|
|
207
|
+
links[usId] = {
|
|
208
|
+
issueNumber: issueData.issueNumber,
|
|
209
|
+
issueUrl: issueData.issueUrl || '',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
197
213
|
}
|
|
214
|
+
} catch {
|
|
215
|
+
// Graceful fallback: return empty if metadata.json is missing or invalid
|
|
198
216
|
}
|
|
199
217
|
|
|
200
218
|
return links;
|