specweave 0.13.6 → 0.14.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.
Files changed (35) hide show
  1. package/CLAUDE.md +189 -0
  2. package/dist/cli/commands/init.js +1 -1
  3. package/dist/cli/commands/init.js.map +1 -1
  4. package/dist/cli/commands/status-line.d.ts +14 -0
  5. package/dist/cli/commands/status-line.d.ts.map +1 -0
  6. package/dist/cli/commands/status-line.js +75 -0
  7. package/dist/cli/commands/status-line.js.map +1 -0
  8. package/dist/core/status-line/status-line-manager.d.ts +62 -0
  9. package/dist/core/status-line/status-line-manager.d.ts.map +1 -0
  10. package/dist/core/status-line/status-line-manager.js +169 -0
  11. package/dist/core/status-line/status-line-manager.js.map +1 -0
  12. package/dist/core/status-line/types.d.ts +50 -0
  13. package/dist/core/status-line/types.d.ts.map +1 -0
  14. package/dist/core/status-line/types.js +17 -0
  15. package/dist/core/status-line/types.js.map +1 -0
  16. package/dist/utils/project-mapper.d.ts +74 -0
  17. package/dist/utils/project-mapper.d.ts.map +1 -0
  18. package/dist/utils/project-mapper.js +273 -0
  19. package/dist/utils/project-mapper.js.map +1 -0
  20. package/dist/utils/spec-splitter.d.ts +68 -0
  21. package/dist/utils/spec-splitter.d.ts.map +1 -0
  22. package/dist/utils/spec-splitter.js +314 -0
  23. package/dist/utils/spec-splitter.js.map +1 -0
  24. package/package.json +1 -1
  25. package/plugins/specweave/hooks/lib/update-status-line.sh +138 -0
  26. package/plugins/specweave/hooks/post-task-completion.sh +10 -0
  27. package/plugins/specweave/skills/multi-project-spec-mapper/SKILL.md +399 -0
  28. package/plugins/specweave-ado/lib/ado-multi-project-sync.js +453 -0
  29. package/plugins/specweave-ado/lib/ado-multi-project-sync.ts +633 -0
  30. package/plugins/specweave-docs/skills/docusaurus/SKILL.md +17 -3
  31. package/plugins/specweave-docs-preview/commands/preview.md +29 -4
  32. package/plugins/specweave-github/lib/github-multi-project-sync.js +340 -0
  33. package/plugins/specweave-github/lib/github-multi-project-sync.ts +461 -0
  34. package/plugins/specweave-jira/lib/jira-multi-project-sync.js +244 -0
  35. package/plugins/specweave-jira/lib/jira-multi-project-sync.ts +358 -0
@@ -7,6 +7,19 @@ description: Launch interactive documentation preview server with Docusaurus. Au
7
7
 
8
8
  Launch an interactive Docusaurus development server to preview your SpecWeave living documentation.
9
9
 
10
+ ## 🎨 Beautiful Docusaurus Experience
11
+
12
+ **This command ALWAYS uses Docusaurus** - not basic file serving! You get:
13
+ - ✅ Auto-generated navigation from folder structure
14
+ - ✅ Search functionality (instant local search)
15
+ - ✅ Beautiful theming (light/dark mode)
16
+ - ✅ Mermaid diagram rendering
17
+ - ✅ Hot reload (edit markdown, see changes instantly)
18
+ - ✅ Professional typography and layout
19
+ - ✅ Mobile responsive design
20
+
21
+ **Never settle for basic markdown rendering when you can have this!**
22
+
10
23
  ## Your Task
11
24
 
12
25
  Execute the following workflow to launch the documentation preview server:
@@ -45,13 +58,25 @@ const docsConfig = config.documentation?.preview || {
45
58
  };
46
59
  ```
47
60
 
48
- ### Step 3: Check if Documentation Preview is Enabled
61
+ ### Step 3: Check if Documentation Preview is Enabled (Auto-Enable if Needed)
49
62
 
50
63
  ```typescript
51
64
  if (!docsConfig.enabled) {
52
- console.log('📚 Documentation preview is disabled in config.');
53
- console.log(' To enable, set documentation.preview.enabled = true in .specweave/config.json');
54
- process.exit(0);
65
+ console.log('📚 Documentation preview is currently disabled.');
66
+ console.log(' Enabling it now for beautiful Docusaurus experience...\n');
67
+
68
+ // Auto-enable the feature
69
+ config.documentation = config.documentation || {};
70
+ config.documentation.preview = {
71
+ ...docsConfig,
72
+ enabled: true,
73
+ autoInstall: true
74
+ };
75
+
76
+ // Save updated config
77
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
78
+
79
+ console.log('✅ Documentation preview enabled!\n');
55
80
  }
56
81
  ```
57
82
 
@@ -0,0 +1,340 @@
1
+ import { Octokit } from "@octokit/rest";
2
+ import { getPrimaryProject } from "../../../src/utils/project-mapper.js";
3
+ import { parseSpecFile } from "../../../src/utils/spec-splitter.js";
4
+ class GitHubMultiProjectSync {
5
+ constructor(config) {
6
+ this.config = config;
7
+ this.octokit = new Octokit({ auth: config.token });
8
+ }
9
+ /**
10
+ * Sync spec to appropriate GitHub repos based on project mapping
11
+ *
12
+ * @param specPath Path to spec file
13
+ * @returns Array of sync results
14
+ */
15
+ async syncSpec(specPath) {
16
+ const results = [];
17
+ const parsedSpec = await parseSpecFile(specPath);
18
+ const isMasterNested = !!this.config.masterRepo && !!this.config.nestedRepos;
19
+ if (isMasterNested) {
20
+ results.push(...await this.syncMasterNested(parsedSpec));
21
+ } else if (this.config.repos) {
22
+ results.push(...await this.syncMultipleRepos(parsedSpec));
23
+ } else {
24
+ throw new Error("Invalid config: Must specify repos[] or masterRepo+nestedRepos[]");
25
+ }
26
+ return results;
27
+ }
28
+ /**
29
+ * Pattern 1: Sync to multiple repos (simple)
30
+ *
31
+ * Each project → separate repo
32
+ * - FE user stories → company/frontend-web
33
+ * - BE user stories → company/backend-api
34
+ * - MOBILE user stories → company/mobile-app
35
+ */
36
+ async syncMultipleRepos(parsedSpec) {
37
+ const results = [];
38
+ const projectStories = /* @__PURE__ */ new Map();
39
+ for (const userStory of parsedSpec.userStories) {
40
+ const primaryProject = getPrimaryProject(userStory);
41
+ if (primaryProject) {
42
+ const existing = projectStories.get(primaryProject.projectId) || [];
43
+ existing.push(userStory);
44
+ projectStories.set(primaryProject.projectId, existing);
45
+ } else {
46
+ console.warn(`\u26A0\uFE0F No confident project match for ${userStory.id} - skipping`);
47
+ }
48
+ }
49
+ for (const [projectId, stories] of projectStories.entries()) {
50
+ const repo = this.findRepoForProject(projectId);
51
+ if (!repo) {
52
+ console.warn(`\u26A0\uFE0F No GitHub repo configured for project ${projectId} - skipping`);
53
+ continue;
54
+ }
55
+ for (const story of stories) {
56
+ const result = await this.createOrUpdateIssue(repo, story, projectId);
57
+ results.push(result);
58
+ }
59
+ }
60
+ return results;
61
+ }
62
+ /**
63
+ * Pattern 2: Sync to master + nested repos (advanced)
64
+ *
65
+ * - Master repo (epic-level): High-level overview
66
+ * - Nested repos (task-level): Detailed implementation
67
+ *
68
+ * Example:
69
+ * Master (company/master-project):
70
+ * Epic #10: User Authentication
71
+ * → Links to: frontend-web#42, backend-api#15, mobile-app#8
72
+ *
73
+ * Nested (company/frontend-web):
74
+ * Issue #42: Implement Login UI
75
+ * Task 1: Create login component
76
+ * Task 2: Add form validation
77
+ */
78
+ async syncMasterNested(parsedSpec) {
79
+ const results = [];
80
+ if (!this.config.masterRepo || !this.config.nestedRepos) {
81
+ throw new Error("Master+nested mode requires masterRepo and nestedRepos");
82
+ }
83
+ const epicResult = await this.createEpicInMasterRepo(parsedSpec);
84
+ results.push(epicResult);
85
+ const projectStories = /* @__PURE__ */ new Map();
86
+ for (const userStory of parsedSpec.userStories) {
87
+ const primaryProject = getPrimaryProject(userStory);
88
+ if (primaryProject) {
89
+ const existing = projectStories.get(primaryProject.projectId) || [];
90
+ existing.push(userStory);
91
+ projectStories.set(primaryProject.projectId, existing);
92
+ }
93
+ }
94
+ for (const [projectId, stories] of projectStories.entries()) {
95
+ const repo = this.findNestedRepoForProject(projectId);
96
+ if (!repo) {
97
+ console.warn(`\u26A0\uFE0F No nested repo for project ${projectId} - skipping`);
98
+ continue;
99
+ }
100
+ for (const story of stories) {
101
+ const result = await this.createIssueInNestedRepo(repo, story, projectId, epicResult.issueNumber);
102
+ results.push(result);
103
+ }
104
+ }
105
+ if (this.config.crossLinking) {
106
+ await this.updateEpicWithLinks(epicResult.issueNumber, results.filter((r) => r.repo !== this.config.masterRepo));
107
+ }
108
+ return results;
109
+ }
110
+ /**
111
+ * Create epic issue in master repo
112
+ */
113
+ async createEpicInMasterRepo(parsedSpec) {
114
+ const title = `Epic: ${parsedSpec.metadata.title}`;
115
+ const body = `# ${parsedSpec.metadata.title}
116
+
117
+ **Status**: ${parsedSpec.metadata.status}
118
+ **Priority**: ${parsedSpec.metadata.priority}
119
+ **Estimated Effort**: ${parsedSpec.metadata.estimatedEffort || parsedSpec.metadata.estimated_effort}
120
+
121
+ ## Executive Summary
122
+
123
+ ${parsedSpec.executiveSummary}
124
+
125
+ ## User Stories (${parsedSpec.userStories.length} total)
126
+
127
+ ${parsedSpec.userStories.map((s, i) => `${i + 1}. ${s.id}: ${s.title}`).join("\n")}
128
+
129
+ ---
130
+
131
+ \u{1F4CA} **This is a high-level epic** - detailed implementation tracked in nested repos
132
+ \u{1F517} **Links to implementation issues** (will be added automatically)
133
+
134
+ \u{1F916} Auto-generated by SpecWeave
135
+ `;
136
+ const response = await this.octokit.issues.create({
137
+ owner: this.config.owner,
138
+ repo: this.config.masterRepo,
139
+ title,
140
+ body,
141
+ labels: ["epic", "specweave"]
142
+ });
143
+ return {
144
+ project: "MASTER",
145
+ repo: this.config.masterRepo,
146
+ issueNumber: response.data.number,
147
+ url: response.data.html_url,
148
+ action: "created"
149
+ };
150
+ }
151
+ /**
152
+ * Create issue in nested repo + link to epic
153
+ */
154
+ async createIssueInNestedRepo(repo, userStory, projectId, epicNumber) {
155
+ const title = `${userStory.id}: ${userStory.title}`;
156
+ let body = `# ${userStory.title}
157
+
158
+ ${userStory.description}
159
+
160
+ ## Acceptance Criteria
161
+
162
+ ${userStory.acceptanceCriteria.map((ac, i) => `- [ ] ${ac}`).join("\n")}
163
+ `;
164
+ if (userStory.technicalContext) {
165
+ body += `
166
+ ## Technical Context
167
+
168
+ ${userStory.technicalContext}
169
+ `;
170
+ }
171
+ if (this.config.crossLinking && epicNumber) {
172
+ body += `
173
+ ---
174
+
175
+ \u{1F4CA} Part of Epic: ${this.config.owner}/${this.config.masterRepo}#${epicNumber}
176
+ `;
177
+ }
178
+ body += `
179
+ \u{1F916} Auto-generated by SpecWeave
180
+ `;
181
+ const response = await this.octokit.issues.create({
182
+ owner: this.config.owner,
183
+ repo,
184
+ title,
185
+ body,
186
+ labels: ["story", "specweave", projectId.toLowerCase()]
187
+ });
188
+ return {
189
+ project: projectId,
190
+ repo,
191
+ issueNumber: response.data.number,
192
+ url: response.data.html_url,
193
+ action: "created"
194
+ };
195
+ }
196
+ /**
197
+ * Create or update issue (for simple multi-repo pattern)
198
+ */
199
+ async createOrUpdateIssue(repo, userStory, projectId) {
200
+ const title = `${userStory.id}: ${userStory.title}`;
201
+ const body = `# ${userStory.title}
202
+
203
+ ${userStory.description}
204
+
205
+ ## Acceptance Criteria
206
+
207
+ ${userStory.acceptanceCriteria.map((ac, i) => `- [ ] ${ac}`).join("\n")}
208
+
209
+ ${userStory.technicalContext ? `
210
+ ## Technical Context
211
+
212
+ ${userStory.technicalContext}
213
+ ` : ""}
214
+
215
+ \u{1F916} Auto-generated by SpecWeave
216
+ `;
217
+ const existingIssues = await this.octokit.issues.listForRepo({
218
+ owner: this.config.owner,
219
+ repo,
220
+ labels: "specweave",
221
+ state: "all"
222
+ });
223
+ const existing = existingIssues.data.find((issue) => issue.title === title);
224
+ if (existing) {
225
+ const response = await this.octokit.issues.update({
226
+ owner: this.config.owner,
227
+ repo,
228
+ issue_number: existing.number,
229
+ body
230
+ });
231
+ return {
232
+ project: projectId,
233
+ repo,
234
+ issueNumber: response.data.number,
235
+ url: response.data.html_url,
236
+ action: "updated"
237
+ };
238
+ } else {
239
+ const response = await this.octokit.issues.create({
240
+ owner: this.config.owner,
241
+ repo,
242
+ title,
243
+ body,
244
+ labels: ["specweave", projectId.toLowerCase()]
245
+ });
246
+ return {
247
+ project: projectId,
248
+ repo,
249
+ issueNumber: response.data.number,
250
+ url: response.data.html_url,
251
+ action: "created"
252
+ };
253
+ }
254
+ }
255
+ /**
256
+ * Update epic with links to nested issues
257
+ */
258
+ async updateEpicWithLinks(epicNumber, nestedResults) {
259
+ if (!this.config.masterRepo) return;
260
+ const linksSection = `
261
+
262
+ ## Implementation Issues
263
+
264
+ ${nestedResults.map((r) => `- ${r.project}: ${this.config.owner}/${r.repo}#${r.issueNumber} - ${r.url}`).join("\n")}
265
+ `;
266
+ const epic = await this.octokit.issues.get({
267
+ owner: this.config.owner,
268
+ repo: this.config.masterRepo,
269
+ issue_number: epicNumber
270
+ });
271
+ const updatedBody = epic.data.body + linksSection;
272
+ await this.octokit.issues.update({
273
+ owner: this.config.owner,
274
+ repo: this.config.masterRepo,
275
+ issue_number: epicNumber,
276
+ body: updatedBody
277
+ });
278
+ }
279
+ /**
280
+ * Find GitHub repo for project ID
281
+ *
282
+ * Maps project IDs to repo names:
283
+ * - FE → frontend-web
284
+ * - BE → backend-api
285
+ * - MOBILE → mobile-app
286
+ */
287
+ findRepoForProject(projectId) {
288
+ if (!this.config.repos) return void 0;
289
+ let match = this.config.repos.find((repo) => repo.toLowerCase().includes(projectId.toLowerCase()));
290
+ if (!match) {
291
+ const fuzzyMap = {
292
+ FE: ["frontend", "web", "ui", "client"],
293
+ BE: ["backend", "api", "server"],
294
+ MOBILE: ["mobile", "app", "ios", "android"],
295
+ INFRA: ["infra", "infrastructure", "devops", "platform"]
296
+ };
297
+ const keywords = fuzzyMap[projectId] || [];
298
+ match = this.config.repos.find(
299
+ (repo) => keywords.some((keyword) => repo.toLowerCase().includes(keyword))
300
+ );
301
+ }
302
+ return match;
303
+ }
304
+ /**
305
+ * Find nested repo for project ID (same logic as findRepoForProject)
306
+ */
307
+ findNestedRepoForProject(projectId) {
308
+ if (!this.config.nestedRepos) return void 0;
309
+ const tempConfig = { ...this.config, repos: this.config.nestedRepos };
310
+ const tempSync = new GitHubMultiProjectSync(tempConfig);
311
+ return tempSync.findRepoForProject(projectId);
312
+ }
313
+ }
314
+ function formatSyncResults(results) {
315
+ const lines = [];
316
+ lines.push("\u{1F4CA} GitHub Multi-Project Sync Results:\n");
317
+ const byProject = /* @__PURE__ */ new Map();
318
+ for (const result of results) {
319
+ const existing = byProject.get(result.project) || [];
320
+ existing.push(result);
321
+ byProject.set(result.project, existing);
322
+ }
323
+ for (const [project, projectResults] of byProject.entries()) {
324
+ lines.push(`
325
+ **${project}**:`);
326
+ for (const result of projectResults) {
327
+ const icon = result.action === "created" ? "\u2705" : result.action === "updated" ? "\u{1F504}" : "\u23ED\uFE0F";
328
+ lines.push(` ${icon} ${result.repo}#${result.issueNumber} (${result.action})`);
329
+ lines.push(` ${result.url}`);
330
+ }
331
+ }
332
+ lines.push(`
333
+ \u2705 Total: ${results.length} issues synced
334
+ `);
335
+ return lines.join("\n");
336
+ }
337
+ export {
338
+ GitHubMultiProjectSync,
339
+ formatSyncResults
340
+ };