specweave 0.28.17 → 0.28.19

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 (137) hide show
  1. package/dist/plugins/specweave-ado/lib/ado-board-resolver.d.ts +94 -0
  2. package/dist/plugins/specweave-ado/lib/ado-board-resolver.d.ts.map +1 -0
  3. package/dist/plugins/specweave-ado/lib/ado-board-resolver.js +219 -0
  4. package/dist/plugins/specweave-ado/lib/ado-board-resolver.js.map +1 -0
  5. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +6 -11
  6. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
  7. package/dist/plugins/specweave-github/lib/github-feature-sync.js +6 -11
  8. package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
  9. package/dist/plugins/specweave-github/lib/github-increment-sync-cli.d.ts +19 -0
  10. package/dist/plugins/specweave-github/lib/github-increment-sync-cli.d.ts.map +1 -0
  11. package/dist/plugins/specweave-github/lib/github-increment-sync-cli.js +380 -0
  12. package/dist/plugins/specweave-github/lib/github-increment-sync-cli.js.map +1 -0
  13. package/dist/plugins/specweave-github/lib/increment-issue-builder.d.ts +92 -0
  14. package/dist/plugins/specweave-github/lib/increment-issue-builder.d.ts.map +1 -0
  15. package/dist/plugins/specweave-github/lib/increment-issue-builder.js +349 -0
  16. package/dist/plugins/specweave-github/lib/increment-issue-builder.js.map +1 -0
  17. package/dist/plugins/specweave-jira/lib/jira-board-resolver.d.ts +50 -0
  18. package/dist/plugins/specweave-jira/lib/jira-board-resolver.d.ts.map +1 -0
  19. package/dist/plugins/specweave-jira/lib/jira-board-resolver.js +84 -0
  20. package/dist/plugins/specweave-jira/lib/jira-board-resolver.js.map +1 -0
  21. package/dist/src/cli/commands/import-external.d.ts.map +1 -1
  22. package/dist/src/cli/commands/import-external.js +12 -7
  23. package/dist/src/cli/commands/import-external.js.map +1 -1
  24. package/dist/src/cli/helpers/init/external-import.d.ts.map +1 -1
  25. package/dist/src/cli/helpers/init/external-import.js +122 -17
  26. package/dist/src/cli/helpers/init/external-import.js.map +1 -1
  27. package/dist/src/cli/helpers/issue-tracker/ado-area-selection.d.ts +65 -0
  28. package/dist/src/cli/helpers/issue-tracker/ado-area-selection.d.ts.map +1 -0
  29. package/dist/src/cli/helpers/issue-tracker/ado-area-selection.js +278 -0
  30. package/dist/src/cli/helpers/issue-tracker/ado-area-selection.js.map +1 -0
  31. package/dist/src/cli/helpers/issue-tracker/jira-board-selection.d.ts +64 -0
  32. package/dist/src/cli/helpers/issue-tracker/jira-board-selection.d.ts.map +1 -0
  33. package/dist/src/cli/helpers/issue-tracker/jira-board-selection.js +251 -0
  34. package/dist/src/cli/helpers/issue-tracker/jira-board-selection.js.map +1 -0
  35. package/dist/src/core/ac-test-validator-cli.js +4 -1
  36. package/dist/src/core/ac-test-validator-cli.js.map +1 -1
  37. package/dist/src/core/ac-test-validator.d.ts.map +1 -1
  38. package/dist/src/core/ac-test-validator.js +4 -1
  39. package/dist/src/core/ac-test-validator.js.map +1 -1
  40. package/dist/src/core/types/increment-metadata.d.ts +75 -0
  41. package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
  42. package/dist/src/core/types/sync-profile.d.ts +137 -5
  43. package/dist/src/core/types/sync-profile.d.ts.map +1 -1
  44. package/dist/src/core/types/sync-profile.js +63 -0
  45. package/dist/src/core/types/sync-profile.js.map +1 -1
  46. package/dist/src/importers/external-importer.d.ts +25 -0
  47. package/dist/src/importers/external-importer.d.ts.map +1 -1
  48. package/dist/src/importers/github-importer.d.ts.map +1 -1
  49. package/dist/src/importers/github-importer.js +5 -3
  50. package/dist/src/importers/github-importer.js.map +1 -1
  51. package/dist/src/importers/item-converter.d.ts +51 -0
  52. package/dist/src/importers/item-converter.d.ts.map +1 -1
  53. package/dist/src/importers/item-converter.js +39 -12
  54. package/dist/src/importers/item-converter.js.map +1 -1
  55. package/dist/src/init/repo/types.d.ts +1 -1
  56. package/dist/src/living-docs/fs-id-allocator.d.ts +72 -3
  57. package/dist/src/living-docs/fs-id-allocator.d.ts.map +1 -1
  58. package/dist/src/living-docs/fs-id-allocator.js +142 -16
  59. package/dist/src/living-docs/fs-id-allocator.js.map +1 -1
  60. package/dist/src/locales/de/cli.json +14 -0
  61. package/dist/src/locales/es/cli.json +14 -0
  62. package/dist/src/locales/fr/cli.json +14 -0
  63. package/dist/src/locales/ja/cli.json +14 -0
  64. package/dist/src/locales/ko/cli.json +14 -0
  65. package/dist/src/locales/pt/cli.json +14 -0
  66. package/dist/src/locales/ru/cli.json +14 -0
  67. package/dist/src/locales/zh/cli.json +14 -0
  68. package/dist/src/utils/chalk-fallback.d.ts +38 -0
  69. package/dist/src/utils/chalk-fallback.d.ts.map +1 -0
  70. package/dist/src/utils/chalk-fallback.js +118 -0
  71. package/dist/src/utils/chalk-fallback.js.map +1 -0
  72. package/dist/src/utils/project-id-generator.d.ts +127 -0
  73. package/dist/src/utils/project-id-generator.d.ts.map +1 -0
  74. package/dist/src/utils/project-id-generator.js +228 -0
  75. package/dist/src/utils/project-id-generator.js.map +1 -0
  76. package/package.json +1 -1
  77. package/plugins/specweave/agents/pm/AGENT.md +202 -0
  78. package/plugins/specweave/commands/specweave-import-external.md +5 -3
  79. package/plugins/specweave/commands/specweave-sync-docs.md +6 -2
  80. package/plugins/specweave/hooks/pre-task-completion.sh +35 -17
  81. package/plugins/specweave/lib/vendor/core/ac-test-validator-cli.d.ts +16 -0
  82. package/plugins/specweave/lib/vendor/core/ac-test-validator-cli.js +121 -0
  83. package/plugins/specweave/lib/vendor/core/ac-test-validator-cli.js.map +1 -0
  84. package/plugins/specweave/lib/vendor/core/ac-test-validator.d.ts +111 -0
  85. package/plugins/specweave/lib/vendor/core/ac-test-validator.js +295 -0
  86. package/plugins/specweave/lib/vendor/core/ac-test-validator.js.map +1 -0
  87. package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +75 -0
  88. package/plugins/specweave/lib/vendor/utils/chalk-fallback.d.ts +38 -0
  89. package/plugins/specweave/lib/vendor/utils/chalk-fallback.js +118 -0
  90. package/plugins/specweave/lib/vendor/utils/chalk-fallback.js.map +1 -0
  91. package/plugins/specweave/lib/vendor/utils/fs-native.d.ts +179 -0
  92. package/plugins/specweave/lib/vendor/utils/fs-native.js +319 -0
  93. package/plugins/specweave/lib/vendor/utils/fs-native.js.map +1 -0
  94. package/plugins/specweave/skills/code-reviewer/SKILL.md +1 -1
  95. package/plugins/specweave/skills/docs-updater/SKILL.md +61 -0
  96. package/plugins/specweave/skills/increment-planner/SKILL.md +10 -335
  97. package/plugins/specweave/skills/increment-planner/templates/metadata.json +13 -0
  98. package/plugins/specweave/skills/increment-planner/templates/plan.md +50 -0
  99. package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +86 -0
  100. package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +50 -0
  101. package/plugins/specweave/skills/increment-planner/templates/tasks-multi-project.md +86 -0
  102. package/plugins/specweave/skills/increment-planner/templates/tasks-single-project.md +48 -0
  103. package/plugins/specweave-ado/commands/specweave-ado-import-areas.md +358 -0
  104. package/plugins/specweave-alternatives/skills/architecture-alternatives/SKILL.md +1 -0
  105. package/plugins/specweave-alternatives/skills/bmad-method/SKILL.md +1 -0
  106. package/plugins/specweave-core/skills/code-quality/SKILL.md +1 -0
  107. package/plugins/specweave-core/skills/design-patterns/SKILL.md +1 -0
  108. package/plugins/specweave-core/skills/software-architecture/SKILL.md +1 -0
  109. package/plugins/specweave-github/commands/specweave-github-cleanup-duplicates.md +14 -10
  110. package/plugins/specweave-github/commands/specweave-github-sync.md +57 -0
  111. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +68 -0
  112. package/plugins/specweave-github/lib/github-feature-sync.ts +6 -11
  113. package/plugins/specweave-github/lib/github-increment-sync-cli.js +343 -0
  114. package/plugins/specweave-github/lib/github-increment-sync-cli.ts +484 -0
  115. package/plugins/specweave-github/lib/increment-issue-builder.js +368 -0
  116. package/plugins/specweave-github/lib/increment-issue-builder.ts +471 -0
  117. package/plugins/specweave-github/skills/github-issue-standard/SKILL.md +19 -24
  118. package/plugins/specweave-infrastructure/agents/observability-engineer/AGENT.md +15 -23
  119. package/plugins/specweave-jira/commands/specweave-jira-import-boards.md +331 -0
  120. package/plugins/specweave-ml/agents/data-scientist/AGENT.md +16 -20
  121. package/plugins/specweave-ml/agents/ml-engineer/AGENT.md +18 -19
  122. package/plugins/specweave-ml/skills/{ml-pipeline-workflow → mlops-dag-builder}/SKILL.md +18 -14
  123. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +102 -0
  124. package/plugins/specweave-ui/skills/browser-automation/SKILL.md +1 -1
  125. package/plugins/specweave-ui/skills/ui-testing/SKILL.md +10 -122
  126. package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts +0 -70
  127. package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts.map +0 -1
  128. package/dist/plugins/specweave-github/lib/epic-content-builder.js +0 -258
  129. package/dist/plugins/specweave-github/lib/epic-content-builder.js.map +0 -1
  130. package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts +0 -83
  131. package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts.map +0 -1
  132. package/dist/plugins/specweave-github/lib/github-epic-sync.js +0 -466
  133. package/dist/plugins/specweave-github/lib/github-epic-sync.js.map +0 -1
  134. package/plugins/specweave-github/lib/epic-content-builder.js +0 -265
  135. package/plugins/specweave-github/lib/epic-content-builder.ts +0 -376
  136. package/plugins/specweave-github/lib/github-epic-sync.js +0 -488
  137. package/plugins/specweave-github/lib/github-epic-sync.ts +0 -715
@@ -1,265 +0,0 @@
1
- import { readdir, readFile } from "fs/promises";
2
- import { existsSync } from "fs";
3
- import * as path from "path";
4
- import * as yaml from "yaml";
5
- class EpicContentBuilder {
6
- constructor(epicFolder, projectRoot) {
7
- this.epicFolder = epicFolder;
8
- this.projectRoot = projectRoot;
9
- }
10
- /**
11
- * Build hierarchical GitHub issue body
12
- *
13
- * Format:
14
- * - Epic overview
15
- * - User Stories section (checkable, with status + increment)
16
- * - Tasks section (grouped by User Story)
17
- */
18
- async buildIssueBody() {
19
- const epicData = await this.readEpicMetadata();
20
- const userStories = await this.readUserStories();
21
- const overview = this.buildOverviewSection(epicData);
22
- const userStoriesSection = this.buildUserStoriesSection(userStories);
23
- const tasksSection = this.buildTasksSection(userStories);
24
- return `${overview}
25
-
26
- ---
27
-
28
- ${userStoriesSection}
29
-
30
- ---
31
-
32
- ${tasksSection}
33
-
34
- ---
35
-
36
- \u{1F916} Auto-created by SpecWeave Epic Sync`;
37
- }
38
- /**
39
- * Read Epic FEATURE.md frontmatter
40
- */
41
- async readEpicMetadata() {
42
- const featurePath = path.join(this.epicFolder, "FEATURE.md");
43
- const content = await readFile(featurePath, "utf-8");
44
- const match = content.match(/^---\n([\s\S]*?)\n---/);
45
- if (!match) {
46
- throw new Error("FEATURE.md missing YAML frontmatter");
47
- }
48
- return yaml.parse(match[1]);
49
- }
50
- /**
51
- * Read all user stories from us-*.md files
52
- */
53
- async readUserStories() {
54
- const files = await readdir(this.epicFolder);
55
- const usFiles = files.filter((f) => f.startsWith("us-") && f.endsWith(".md"));
56
- const userStories = [];
57
- for (const file of usFiles.sort()) {
58
- const filePath = path.join(this.epicFolder, file);
59
- const content = await readFile(filePath, "utf-8");
60
- const match = content.match(/^---\n([\s\S]*?)\n---/);
61
- if (!match) {
62
- console.warn(` \u26A0\uFE0F ${file} missing frontmatter, skipping`);
63
- continue;
64
- }
65
- const frontmatter = yaml.parse(match[1]);
66
- const bodyContent = content.slice(match[0].length).trim();
67
- const incrementMatch = bodyContent.match(/\*\*Increment\*\*:\s*\[([^\]]+)\]/);
68
- const increment = incrementMatch ? incrementMatch[1] : null;
69
- const acceptanceCriteria = this.extractAcceptanceCriteria(bodyContent);
70
- const tasks = await this.extractTasksForUserStory(
71
- frontmatter.id,
72
- increment,
73
- bodyContent
74
- );
75
- userStories.push({
76
- id: frontmatter.id,
77
- title: frontmatter.title,
78
- status: this.normalizeStatus(frontmatter.status),
79
- increment,
80
- acceptanceCriteria,
81
- tasks
82
- });
83
- }
84
- return userStories;
85
- }
86
- /**
87
- * Extract tasks for a user story from its Implementation section
88
- */
89
- async extractTasksForUserStory(userStoryId, incrementId, content) {
90
- if (!incrementId) {
91
- return [];
92
- }
93
- const incrementFolder = path.join(
94
- this.projectRoot,
95
- ".specweave",
96
- "increments",
97
- incrementId
98
- );
99
- if (!existsSync(incrementFolder)) {
100
- console.warn(` \u26A0\uFE0F Increment folder not found: ${incrementId}`);
101
- return [];
102
- }
103
- const tasksPath = path.join(incrementFolder, "tasks.md");
104
- if (!existsSync(tasksPath)) {
105
- console.warn(` \u26A0\uFE0F tasks.md not found in ${incrementId}`);
106
- return [];
107
- }
108
- const tasksContent = await readFile(tasksPath, "utf-8");
109
- const taskLinkPattern = /- \[([T-\d]+):\s*([^\]]+)\]/g;
110
- const taskLinks = [];
111
- let match;
112
- while ((match = taskLinkPattern.exec(content)) !== null) {
113
- taskLinks.push({
114
- id: match[1],
115
- // e.g., "T-001"
116
- title: match[2].trim()
117
- });
118
- }
119
- const tasks = [];
120
- for (const taskLink of taskLinks) {
121
- const taskPattern = new RegExp(
122
- `###\\s+${taskLink.id}:\\s*([^\\n]+)[\\s\\S]*?\\*\\*Status\\*\\*:\\s*\\[([x\\s])\\]`,
123
- "i"
124
- );
125
- const taskMatch = tasksContent.match(taskPattern);
126
- const isCompleted = taskMatch ? taskMatch[2] === "x" : false;
127
- tasks.push({
128
- id: taskLink.id,
129
- title: taskLink.title,
130
- status: isCompleted,
131
- userStoryId
132
- });
133
- }
134
- return tasks;
135
- }
136
- /**
137
- * Extract acceptance criteria from user story content
138
- *
139
- * Parses the "## Acceptance Criteria" section and extracts all checkable items.
140
- * Format: - [x] **AC-US2-01**: Description...
141
- */
142
- extractAcceptanceCriteria(content) {
143
- const acceptanceCriteria = [];
144
- const acSectionMatch = content.match(/##\s+Acceptance Criteria\s*\n([\s\S]*?)(?=\n##|\n---|\n$)/i);
145
- if (!acSectionMatch) {
146
- return acceptanceCriteria;
147
- }
148
- const acSection = acSectionMatch[1];
149
- const acPattern = /^-\s+\[([x\s])\]\s+\*\*([^*]+)\*\*:\s*(.+)$/gm;
150
- let match;
151
- while ((match = acPattern.exec(acSection)) !== null) {
152
- const completed = match[1].trim().toLowerCase() === "x";
153
- const acId = match[2].trim();
154
- const description = match[3].trim();
155
- acceptanceCriteria.push({
156
- id: acId,
157
- description,
158
- completed
159
- });
160
- }
161
- return acceptanceCriteria;
162
- }
163
- /**
164
- * Build overview section
165
- */
166
- buildOverviewSection(epic) {
167
- return `# [${epic.id}] ${epic.title}
168
-
169
- **Status**: ${epic.status}
170
- **Created**: ${epic.created}
171
- **Last Updated**: ${epic.last_updated}`;
172
- }
173
- /**
174
- * Build User Stories section
175
- */
176
- buildUserStoriesSection(userStories) {
177
- const total = userStories.length;
178
- const completed = userStories.filter((us) => us.status === "complete").length;
179
- const percentage = total > 0 ? Math.round(completed / total * 100) : 0;
180
- let section = `## User Stories
181
-
182
- Progress: ${completed}/${total} user stories complete (${percentage}%)
183
-
184
- `;
185
- for (const us of userStories) {
186
- const checkbox = us.status === "complete" ? "[x]" : "[ ]";
187
- const statusEmoji = this.getStatusEmoji(us.status);
188
- const incrementLink = us.increment ? `[${us.increment}](../../increments/${us.increment}/)` : "TBD";
189
- section += `- ${checkbox} **${us.id}: ${us.title}** (${statusEmoji} ${us.status} | Increment: ${incrementLink})
190
- `;
191
- if (us.acceptanceCriteria.length > 0) {
192
- section += "\n **Acceptance Criteria**:\n";
193
- for (const ac of us.acceptanceCriteria) {
194
- const acCheckbox = ac.completed ? "[x]" : "[ ]";
195
- section += ` - ${acCheckbox} **${ac.id}**: ${ac.description}
196
- `;
197
- }
198
- section += "\n";
199
- }
200
- }
201
- return section;
202
- }
203
- /**
204
- * Build Tasks section (grouped by User Story)
205
- */
206
- buildTasksSection(userStories) {
207
- const totalTasks = userStories.reduce((sum, us) => sum + us.tasks.length, 0);
208
- const completedTasks = userStories.reduce(
209
- (sum, us) => sum + us.tasks.filter((t) => t.status).length,
210
- 0
211
- );
212
- const percentage = totalTasks > 0 ? Math.round(completedTasks / totalTasks * 100) : 0;
213
- let section = `## Tasks by User Story
214
-
215
- Progress: ${completedTasks}/${totalTasks} tasks complete (${percentage}%)
216
-
217
- `;
218
- for (const us of userStories) {
219
- if (us.tasks.length === 0) {
220
- continue;
221
- }
222
- const incrementLink = us.increment ? `[${us.increment}](../../increments/${us.increment}/tasks.md)` : "TBD";
223
- section += `### ${us.id}: ${us.title} (Increment: ${incrementLink})
224
-
225
- `;
226
- for (const task of us.tasks) {
227
- const checkbox = task.status ? "[x]" : "[ ]";
228
- section += `- ${checkbox} ${task.id}: ${task.title}
229
- `;
230
- }
231
- section += "\n";
232
- }
233
- return section;
234
- }
235
- /**
236
- * Normalize status values
237
- */
238
- normalizeStatus(status) {
239
- const normalized = status.toLowerCase();
240
- if (normalized === "complete" || normalized === "completed") return "complete";
241
- if (normalized === "active" || normalized === "in-progress") return "active";
242
- if (normalized === "planning") return "planning";
243
- return "not-started";
244
- }
245
- /**
246
- * Get status emoji
247
- */
248
- getStatusEmoji(status) {
249
- switch (status) {
250
- case "complete":
251
- return "\u2705";
252
- case "active":
253
- return "\u{1F6A7}";
254
- case "planning":
255
- return "\u{1F4CB}";
256
- case "not-started":
257
- return "\u23F3";
258
- default:
259
- return "\u2753";
260
- }
261
- }
262
- }
263
- export {
264
- EpicContentBuilder
265
- };
@@ -1,376 +0,0 @@
1
- /**
2
- * Epic Content Builder - Hierarchical GitHub issue content for Feature Specs
3
- *
4
- * Architecture:
5
- * - Reads FS-* folder (FEATURE.md + us-*.md files)
6
- * - Reads increment tasks.md files to map tasks to user stories
7
- * - Generates hierarchical issue body:
8
- * 1. User Stories section (with status + increment)
9
- * 2. Tasks section (grouped by User Story)
10
- *
11
- * Key Features:
12
- * - NO single "Increment" field (epics span multiple increments)
13
- * - User Stories are checkable with status and increment link
14
- * - Tasks grouped under their User Story
15
- * - Shows which increment each US/task belongs to
16
- */
17
-
18
- import { readdir, readFile } from 'fs/promises';
19
- import { existsSync } from 'fs';
20
- import * as path from 'path';
21
- import * as yaml from 'yaml';
22
-
23
- interface UserStory {
24
- id: string;
25
- title: string;
26
- status: 'complete' | 'active' | 'planning' | 'not-started';
27
- increment: string | null; // e.g., "0031-external-tool-status-sync"
28
- acceptanceCriteria: AcceptanceCriteria[];
29
- tasks: Task[];
30
- }
31
-
32
- interface AcceptanceCriteria {
33
- id: string; // e.g., "AC-US2-01"
34
- description: string;
35
- completed: boolean; // true = [x], false = [ ]
36
- }
37
-
38
- interface Task {
39
- id: string;
40
- title: string;
41
- status: boolean; // true = completed, false = not started
42
- userStoryId: string; // e.g., "US-001"
43
- }
44
-
45
- interface EpicFrontmatter {
46
- id: string;
47
- title: string;
48
- status: string;
49
- created: string;
50
- last_updated: string;
51
- }
52
-
53
- interface UserStoryFrontmatter {
54
- id: string;
55
- epic: string;
56
- title: string;
57
- status: string;
58
- created: string;
59
- completed?: string;
60
- }
61
-
62
- export class EpicContentBuilder {
63
- private epicFolder: string;
64
- private projectRoot: string;
65
-
66
- constructor(epicFolder: string, projectRoot: string) {
67
- this.epicFolder = epicFolder;
68
- this.projectRoot = projectRoot;
69
- }
70
-
71
- /**
72
- * Build hierarchical GitHub issue body
73
- *
74
- * Format:
75
- * - Epic overview
76
- * - User Stories section (checkable, with status + increment)
77
- * - Tasks section (grouped by User Story)
78
- */
79
- async buildIssueBody(): Promise<string> {
80
- // 1. Read Epic metadata
81
- const epicData = await this.readEpicMetadata();
82
-
83
- // 2. Read User Stories
84
- const userStories = await this.readUserStories();
85
-
86
- // 3. Build sections
87
- const overview = this.buildOverviewSection(epicData);
88
- const userStoriesSection = this.buildUserStoriesSection(userStories);
89
- const tasksSection = this.buildTasksSection(userStories);
90
-
91
- // 4. Combine
92
- return `${overview}\n\n---\n\n${userStoriesSection}\n\n---\n\n${tasksSection}\n\n---\n\n🤖 Auto-created by SpecWeave Epic Sync`;
93
- }
94
-
95
- /**
96
- * Read Epic FEATURE.md frontmatter
97
- */
98
- private async readEpicMetadata(): Promise<EpicFrontmatter> {
99
- const featurePath = path.join(this.epicFolder, 'FEATURE.md');
100
- const content = await readFile(featurePath, 'utf-8');
101
- const match = content.match(/^---\n([\s\S]*?)\n---/);
102
-
103
- if (!match) {
104
- throw new Error('FEATURE.md missing YAML frontmatter');
105
- }
106
-
107
- return yaml.parse(match[1]) as EpicFrontmatter;
108
- }
109
-
110
- /**
111
- * Read all user stories from us-*.md files
112
- */
113
- private async readUserStories(): Promise<UserStory[]> {
114
- const files = await readdir(this.epicFolder);
115
- const usFiles = files.filter((f) => f.startsWith('us-') && f.endsWith('.md'));
116
-
117
- const userStories: UserStory[] = [];
118
-
119
- for (const file of usFiles.sort()) {
120
- const filePath = path.join(this.epicFolder, file);
121
- const content = await readFile(filePath, 'utf-8');
122
- const match = content.match(/^---\n([\s\S]*?)\n---/);
123
-
124
- if (!match) {
125
- console.warn(` ⚠️ ${file} missing frontmatter, skipping`);
126
- continue;
127
- }
128
-
129
- const frontmatter = yaml.parse(match[1]) as UserStoryFrontmatter;
130
- const bodyContent = content.slice(match[0].length).trim();
131
-
132
- // Extract increment from Implementation section
133
- const incrementMatch = bodyContent.match(/\*\*Increment\*\*:\s*\[([^\]]+)\]/);
134
- const increment = incrementMatch ? incrementMatch[1] : null;
135
-
136
- // Extract acceptance criteria from AC section
137
- const acceptanceCriteria = this.extractAcceptanceCriteria(bodyContent);
138
-
139
- // Extract tasks from Implementation section
140
- const tasks = await this.extractTasksForUserStory(
141
- frontmatter.id,
142
- increment,
143
- bodyContent
144
- );
145
-
146
- userStories.push({
147
- id: frontmatter.id,
148
- title: frontmatter.title,
149
- status: this.normalizeStatus(frontmatter.status),
150
- increment,
151
- acceptanceCriteria,
152
- tasks,
153
- });
154
- }
155
-
156
- return userStories;
157
- }
158
-
159
- /**
160
- * Extract tasks for a user story from its Implementation section
161
- */
162
- private async extractTasksForUserStory(
163
- userStoryId: string,
164
- incrementId: string | null,
165
- content: string
166
- ): Promise<Task[]> {
167
- if (!incrementId) {
168
- return []; // No increment yet, no tasks
169
- }
170
-
171
- // Find increment folder
172
- const incrementFolder = path.join(
173
- this.projectRoot,
174
- '.specweave',
175
- 'increments',
176
- incrementId
177
- );
178
-
179
- if (!existsSync(incrementFolder)) {
180
- console.warn(` ⚠️ Increment folder not found: ${incrementId}`);
181
- return [];
182
- }
183
-
184
- const tasksPath = path.join(incrementFolder, 'tasks.md');
185
- if (!existsSync(tasksPath)) {
186
- console.warn(` ⚠️ tasks.md not found in ${incrementId}`);
187
- return [];
188
- }
189
-
190
- // Read tasks.md
191
- const tasksContent = await readFile(tasksPath, 'utf-8');
192
-
193
- // Extract task links from user story's Implementation section
194
- // Format: - [T-001: Title](link#t-001-title)
195
- const taskLinkPattern = /- \[([T-\d]+):\s*([^\]]+)\]/g;
196
- const taskLinks: Array<{ id: string; title: string }> = [];
197
-
198
- let match;
199
- while ((match = taskLinkPattern.exec(content)) !== null) {
200
- taskLinks.push({
201
- id: match[1], // e.g., "T-001"
202
- title: match[2].trim(),
203
- });
204
- }
205
-
206
- // Parse tasks from tasks.md to get completion status
207
- const tasks: Task[] = [];
208
-
209
- for (const taskLink of taskLinks) {
210
- // Find task in tasks.md by heading pattern: ### T-001: Title
211
- const taskPattern = new RegExp(
212
- `###\\s+${taskLink.id}:\\s*([^\\n]+)[\\s\\S]*?\\*\\*Status\\*\\*:\\s*\\[([x\\s])\\]`,
213
- 'i'
214
- );
215
- const taskMatch = tasksContent.match(taskPattern);
216
-
217
- const isCompleted = taskMatch ? taskMatch[2] === 'x' : false;
218
-
219
- tasks.push({
220
- id: taskLink.id,
221
- title: taskLink.title,
222
- status: isCompleted,
223
- userStoryId,
224
- });
225
- }
226
-
227
- return tasks;
228
- }
229
-
230
- /**
231
- * Extract acceptance criteria from user story content
232
- *
233
- * Parses the "## Acceptance Criteria" section and extracts all checkable items.
234
- * Format: - [x] **AC-US2-01**: Description...
235
- */
236
- private extractAcceptanceCriteria(content: string): AcceptanceCriteria[] {
237
- const acceptanceCriteria: AcceptanceCriteria[] = [];
238
-
239
- // Find Acceptance Criteria section
240
- const acSectionMatch = content.match(/##\s+Acceptance Criteria\s*\n([\s\S]*?)(?=\n##|\n---|\n$)/i);
241
-
242
- if (!acSectionMatch) {
243
- return acceptanceCriteria; // No AC section found
244
- }
245
-
246
- const acSection = acSectionMatch[1];
247
-
248
- // Parse each AC line: - [x] **AC-US2-01**: Description (P1, testable)
249
- // Pattern matches both checked [x] and unchecked [ ] boxes
250
- const acPattern = /^-\s+\[([x\s])\]\s+\*\*([^*]+)\*\*:\s*(.+)$/gm;
251
-
252
- let match;
253
- while ((match = acPattern.exec(acSection)) !== null) {
254
- const completed = match[1].trim().toLowerCase() === 'x';
255
- const acId = match[2].trim(); // e.g., "AC-US2-01"
256
- const description = match[3].trim(); // e.g., "Spec frontmatter includes linked_increments mapping (P1, testable)"
257
-
258
- acceptanceCriteria.push({
259
- id: acId,
260
- description,
261
- completed,
262
- });
263
- }
264
-
265
- return acceptanceCriteria;
266
- }
267
-
268
- /**
269
- * Build overview section
270
- */
271
- private buildOverviewSection(epic: EpicFrontmatter): string {
272
- return `# [${epic.id}] ${epic.title}\n\n**Status**: ${epic.status}\n**Created**: ${epic.created}\n**Last Updated**: ${epic.last_updated}`;
273
- }
274
-
275
- /**
276
- * Build User Stories section
277
- */
278
- private buildUserStoriesSection(userStories: UserStory[]): string {
279
- const total = userStories.length;
280
- const completed = userStories.filter((us) => us.status === 'complete').length;
281
- const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
282
-
283
- let section = `## User Stories\n\nProgress: ${completed}/${total} user stories complete (${percentage}%)\n\n`;
284
-
285
- for (const us of userStories) {
286
- const checkbox = us.status === 'complete' ? '[x]' : '[ ]';
287
- const statusEmoji = this.getStatusEmoji(us.status);
288
- const incrementLink = us.increment
289
- ? `[${us.increment}](../../increments/${us.increment}/)`
290
- : 'TBD';
291
-
292
- section += `- ${checkbox} **${us.id}: ${us.title}** (${statusEmoji} ${us.status} | Increment: ${incrementLink})\n`;
293
-
294
- // Add Acceptance Criteria checkboxes under each user story
295
- if (us.acceptanceCriteria.length > 0) {
296
- section += '\n **Acceptance Criteria**:\n';
297
- for (const ac of us.acceptanceCriteria) {
298
- const acCheckbox = ac.completed ? '[x]' : '[ ]';
299
- section += ` - ${acCheckbox} **${ac.id}**: ${ac.description}\n`;
300
- }
301
- section += '\n';
302
- }
303
- }
304
-
305
- return section;
306
- }
307
-
308
- /**
309
- * Build Tasks section (grouped by User Story)
310
- */
311
- private buildTasksSection(userStories: UserStory[]): string {
312
- const totalTasks = userStories.reduce((sum, us) => sum + us.tasks.length, 0);
313
- const completedTasks = userStories.reduce(
314
- (sum, us) => sum + us.tasks.filter((t) => t.status).length,
315
- 0
316
- );
317
- const percentage =
318
- totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
319
-
320
- let section = `## Tasks by User Story\n\nProgress: ${completedTasks}/${totalTasks} tasks complete (${percentage}%)\n\n`;
321
-
322
- for (const us of userStories) {
323
- if (us.tasks.length === 0) {
324
- continue; // Skip user stories with no tasks yet
325
- }
326
-
327
- const incrementLink = us.increment
328
- ? `[${us.increment}](../../increments/${us.increment}/tasks.md)`
329
- : 'TBD';
330
-
331
- section += `### ${us.id}: ${us.title} (Increment: ${incrementLink})\n\n`;
332
-
333
- for (const task of us.tasks) {
334
- const checkbox = task.status ? '[x]' : '[ ]';
335
- section += `- ${checkbox} ${task.id}: ${task.title}\n`;
336
- }
337
-
338
- section += '\n';
339
- }
340
-
341
- return section;
342
- }
343
-
344
- /**
345
- * Normalize status values
346
- */
347
- private normalizeStatus(
348
- status: string
349
- ): 'complete' | 'active' | 'planning' | 'not-started' {
350
- const normalized = status.toLowerCase();
351
- if (normalized === 'complete' || normalized === 'completed') return 'complete';
352
- if (normalized === 'active' || normalized === 'in-progress') return 'active';
353
- if (normalized === 'planning') return 'planning';
354
- return 'not-started';
355
- }
356
-
357
- /**
358
- * Get status emoji
359
- */
360
- private getStatusEmoji(
361
- status: 'complete' | 'active' | 'planning' | 'not-started'
362
- ): string {
363
- switch (status) {
364
- case 'complete':
365
- return '✅';
366
- case 'active':
367
- return '🚧';
368
- case 'planning':
369
- return '📋';
370
- case 'not-started':
371
- return '⏳';
372
- default:
373
- return '❓';
374
- }
375
- }
376
- }