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.
@@ -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;AAKH,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"}
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(content);
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 spec.md YAML frontmatter.
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(content) {
117
+ async function parseIssueLinks(specPath) {
110
118
  const links = {};
111
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
112
- if (!fmMatch)
113
- return links;
114
- const frontmatter = fmMatch[1];
115
- const usBlockMatch = frontmatter.match(/userStories:\s*\n((?:\s{6,}[\s\S]*?)(?=\n\s{0,3}\S|$))/);
116
- if (!usBlockMatch)
117
- return links;
118
- const usBlock = usBlockMatch[1];
119
- const usEntries = usBlock.match(/^\s+(US-\d+):\s*\n((?:\s+\w[\s\S]*?)(?=\n\s+US-|\s*$))/gm);
120
- if (!usEntries)
121
- return links;
122
- for (const entry of usEntries) {
123
- const idMatch = entry.match(/(US-\d+):/);
124
- const numMatch = entry.match(/issueNumber:\s*(\d+)/);
125
- const urlMatch = entry.match(/issueUrl:\s*"([^"]+)"/);
126
- if (idMatch && numMatch) {
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":"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"}
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.304",
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(content);
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(content) {
94
+ async function parseIssueLinks(specPath) {
93
95
  const links = {};
94
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
95
- if (!fmMatch) return links;
96
- const frontmatter = fmMatch[1];
97
- const usBlockMatch = frontmatter.match(/userStories:\s*\n((?:\s{6,}[\s\S]*?)(?=\n\s{0,3}\S|$))/);
98
- if (!usBlockMatch) return links;
99
- const usBlock = usBlockMatch[1];
100
- const usEntries = usBlock.match(/^\s+(US-\d+):\s*\n((?:\s+\w[\s\S]*?)(?=\n\s+US-|\s*$))/gm);
101
- if (!usEntries) return links;
102
- for (const entry of usEntries) {
103
- const idMatch = entry.match(/(US-\d+):/);
104
- const numMatch = entry.match(/issueNumber:\s*(\d+)/);
105
- const urlMatch = entry.match(/issueUrl:\s*"([^"]+)"/);
106
- if (idMatch && numMatch) {
107
- links[idMatch[1]] = {
108
- issueNumber: parseInt(numMatch[1], 10),
109
- issueUrl: urlMatch ? urlMatch[1] : ""
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(content);
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 spec.md YAML frontmatter.
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(content: string): Record<string, ParsedUSIssueLink> {
179
+ async function parseIssueLinks(specPath: string): Promise<Record<string, ParsedUSIssueLink>> {
172
180
  const links: Record<string, ParsedUSIssueLink> = {};
173
181
 
174
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
175
- if (!fmMatch) return links;
176
-
177
- const frontmatter = fmMatch[1];
178
-
179
- const usBlockMatch = frontmatter.match(/userStories:\s*\n((?:\s{6,}[\s\S]*?)(?=\n\s{0,3}\S|$))/);
180
- if (!usBlockMatch) return links;
181
-
182
- const usBlock = usBlockMatch[1];
183
- const usEntries = usBlock.match(/^\s+(US-\d+):\s*\n((?:\s+\w[\s\S]*?)(?=\n\s+US-|\s*$))/gm);
184
-
185
- if (!usEntries) return links;
186
-
187
- for (const entry of usEntries) {
188
- const idMatch = entry.match(/(US-\d+):/);
189
- const numMatch = entry.match(/issueNumber:\s*(\d+)/);
190
- const urlMatch = entry.match(/issueUrl:\s*"([^"]+)"/);
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
- if (idMatch && numMatch) {
193
- links[idMatch[1]] = {
194
- issueNumber: parseInt(numMatch[1], 10),
195
- issueUrl: urlMatch ? urlMatch[1] : '',
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;