specweave 0.22.11 → 0.22.12

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 (27) hide show
  1. package/CLAUDE.md +77 -0
  2. package/dist/src/cli/commands/init.d.ts.map +1 -1
  3. package/dist/src/cli/commands/init.js +42 -0
  4. package/dist/src/cli/commands/init.js.map +1 -1
  5. package/dist/src/cli/helpers/init/initial-increment-generator.d.ts +26 -0
  6. package/dist/src/cli/helpers/init/initial-increment-generator.d.ts.map +1 -0
  7. package/dist/src/cli/helpers/init/initial-increment-generator.js +240 -0
  8. package/dist/src/cli/helpers/init/initial-increment-generator.js.map +1 -0
  9. package/dist/src/generators/spec/spec-parser.d.ts +79 -0
  10. package/dist/src/generators/spec/spec-parser.d.ts.map +1 -0
  11. package/dist/src/generators/spec/spec-parser.js +185 -0
  12. package/dist/src/generators/spec/spec-parser.js.map +1 -0
  13. package/dist/src/generators/spec/task-parser.d.ts +95 -0
  14. package/dist/src/generators/spec/task-parser.d.ts.map +1 -0
  15. package/dist/src/generators/spec/task-parser.js +301 -0
  16. package/dist/src/generators/spec/task-parser.js.map +1 -0
  17. package/dist/src/validators/ac-coverage-validator.d.ts +106 -0
  18. package/dist/src/validators/ac-coverage-validator.d.ts.map +1 -0
  19. package/dist/src/validators/ac-coverage-validator.js +250 -0
  20. package/dist/src/validators/ac-coverage-validator.js.map +1 -0
  21. package/package.json +1 -1
  22. package/plugins/specweave/commands/specweave-done.md +58 -0
  23. package/plugins/specweave/commands/specweave-validate.md +36 -6
  24. package/plugins/specweave/lib/hooks/sync-living-docs.js +21 -0
  25. package/plugins/specweave/lib/hooks/sync-us-tasks.js +290 -0
  26. package/src/templates/AGENTS.md.template +16 -0
  27. package/src/templates/CLAUDE.md.template +18 -0
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Task Parser with US-Task Linkage Support
3
+ *
4
+ * Parses tasks.md files to extract task metadata including new US linkage fields:
5
+ * - userStory: US-ID this task implements (e.g., "US-001")
6
+ * - satisfiesACs: List of AC-IDs this task satisfies (e.g., ["AC-US1-01", "AC-US1-02"])
7
+ *
8
+ * Supports hierarchical task structure grouped by User Story.
9
+ */
10
+ import { readFileSync } from 'fs';
11
+ /**
12
+ * Parse tasks.md and extract all tasks with US linkage
13
+ *
14
+ * @param tasksPath - Path to tasks.md file
15
+ * @returns Map of User Story ID → Tasks
16
+ * @throws Error if tasks.md cannot be read or is malformed
17
+ */
18
+ export function parseTasksWithUSLinks(tasksPath) {
19
+ try {
20
+ const content = readFileSync(tasksPath, 'utf-8');
21
+ const tasks = {};
22
+ // Split content into lines for line number tracking
23
+ const lines = content.split('\n');
24
+ // Regex patterns for task parsing
25
+ const taskHeaderRegex = /^###\s+(T-\d{3}):\s*(.+)$/;
26
+ const userStoryRegex = /^\*\*User Story\*\*:\s*(US-\d{3})/;
27
+ const satisfiesACsRegex = /^\*\*Satisfies ACs\*\*:\s*(AC-US\d+-\d{2}(?:,\s*AC-US\d+-\d{2})*)/;
28
+ const statusRegex = /^\*\*Status\*\*:\s*\[([x ])\]\s*(\w+)/;
29
+ const priorityRegex = /^\*\*Priority\*\*:\s*(.+)/;
30
+ const estimatedEffortRegex = /^\*\*Estimated Effort\*\*:\s*(.+)/;
31
+ const dependenciesRegex = /^\*\*Dependencies\*\*:\s*(.+)/;
32
+ let currentTask = null;
33
+ let currentDescription = [];
34
+ let currentSection = 'none';
35
+ for (let i = 0; i < lines.length; i++) {
36
+ const line = lines[i];
37
+ const lineNumber = i + 1;
38
+ // Check for task header (### T-XXX: Title)
39
+ const taskHeaderMatch = line.match(taskHeaderRegex);
40
+ if (taskHeaderMatch) {
41
+ // Save previous task if exists
42
+ if (currentTask) {
43
+ saveTask(tasks, currentTask, currentDescription);
44
+ }
45
+ // Start new task
46
+ currentTask = {
47
+ id: taskHeaderMatch[1],
48
+ title: taskHeaderMatch[2],
49
+ status: 'pending',
50
+ lineNumber
51
+ };
52
+ currentDescription = [];
53
+ currentSection = 'none';
54
+ continue;
55
+ }
56
+ // Skip if no current task
57
+ if (!currentTask)
58
+ continue;
59
+ // Parse task fields
60
+ const userStoryMatch = line.match(userStoryRegex);
61
+ if (userStoryMatch) {
62
+ currentTask.userStory = userStoryMatch[1];
63
+ continue;
64
+ }
65
+ const satisfiesACsMatch = line.match(satisfiesACsRegex);
66
+ if (satisfiesACsMatch) {
67
+ // Split comma-separated AC-IDs and trim whitespace
68
+ currentTask.satisfiesACs = satisfiesACsMatch[1]
69
+ .split(',')
70
+ .map(ac => ac.trim());
71
+ continue;
72
+ }
73
+ const statusMatch = line.match(statusRegex);
74
+ if (statusMatch) {
75
+ const checkbox = statusMatch[1];
76
+ const statusText = statusMatch[2].toLowerCase();
77
+ // Map checkbox and status text to TaskStatus
78
+ if (checkbox === 'x') {
79
+ currentTask.status = 'completed';
80
+ }
81
+ else if (statusText.includes('progress')) {
82
+ currentTask.status = 'in_progress';
83
+ }
84
+ else if (statusText.includes('transfer')) {
85
+ currentTask.status = 'transferred';
86
+ }
87
+ else if (statusText.includes('cancel')) {
88
+ currentTask.status = 'canceled';
89
+ }
90
+ else {
91
+ currentTask.status = 'pending';
92
+ }
93
+ continue;
94
+ }
95
+ const priorityMatch = line.match(priorityRegex);
96
+ if (priorityMatch) {
97
+ currentTask.priority = priorityMatch[1];
98
+ continue;
99
+ }
100
+ const estimatedEffortMatch = line.match(estimatedEffortRegex);
101
+ if (estimatedEffortMatch) {
102
+ currentTask.estimatedEffort = estimatedEffortMatch[1];
103
+ continue;
104
+ }
105
+ const dependenciesMatch = line.match(dependenciesRegex);
106
+ if (dependenciesMatch) {
107
+ // Parse dependencies (T-001, T-002, etc.)
108
+ currentTask.dependencies = dependenciesMatch[1]
109
+ .split(',')
110
+ .map(dep => dep.trim())
111
+ .filter(dep => dep.match(/^T-\d{3}$/));
112
+ continue;
113
+ }
114
+ // Track sections for description parsing
115
+ if (line.startsWith('**Description**:')) {
116
+ currentSection = 'description';
117
+ continue;
118
+ }
119
+ if (line.startsWith('**Implementation Steps**:')) {
120
+ currentSection = 'implementation';
121
+ continue;
122
+ }
123
+ if (line.startsWith('**Test Plan**:')) {
124
+ currentSection = 'test';
125
+ continue;
126
+ }
127
+ if (line.startsWith('**Files Affected**:')) {
128
+ currentSection = 'files';
129
+ currentTask.filesAffected = [];
130
+ continue;
131
+ }
132
+ // Collect description lines
133
+ if (currentSection === 'description' && line.trim() && !line.startsWith('**')) {
134
+ currentDescription.push(line.trim());
135
+ }
136
+ // Collect files affected
137
+ if (currentSection === 'files' && line.trim().startsWith('- `')) {
138
+ const filePath = line.trim().replace(/^- `(.+)`/, '$1');
139
+ if (currentTask.filesAffected) {
140
+ currentTask.filesAffected.push(filePath);
141
+ }
142
+ }
143
+ }
144
+ // Save last task
145
+ if (currentTask) {
146
+ saveTask(tasks, currentTask, currentDescription);
147
+ }
148
+ return tasks;
149
+ }
150
+ catch (error) {
151
+ throw new Error(`Failed to parse tasks.md at ${tasksPath}: ${error}`);
152
+ }
153
+ }
154
+ /**
155
+ * Helper: Save task to tasks map, grouped by User Story
156
+ */
157
+ function saveTask(tasks, task, description) {
158
+ // Set description
159
+ if (description.length > 0) {
160
+ task.description = description.join(' ');
161
+ }
162
+ // Group by User Story (or "unassigned" if no userStory field)
163
+ const usId = task.userStory || 'unassigned';
164
+ if (!tasks[usId]) {
165
+ tasks[usId] = [];
166
+ }
167
+ tasks[usId].push(task);
168
+ }
169
+ /**
170
+ * Validate task US and AC linkage
171
+ *
172
+ * @param task - Task to validate
173
+ * @param validUSIds - List of valid US-IDs from spec.md
174
+ * @param validACIds - List of valid AC-IDs from spec.md
175
+ * @returns Array of validation errors (empty if valid)
176
+ */
177
+ export function validateTaskLinkage(task, validUSIds, validACIds) {
178
+ const errors = [];
179
+ // Validate userStory field
180
+ if (task.userStory) {
181
+ // Check format (US-XXX)
182
+ if (!task.userStory.match(/^US-\d{3}$/)) {
183
+ errors.push({
184
+ taskId: task.id,
185
+ field: 'userStory',
186
+ value: task.userStory,
187
+ message: `Invalid US-ID format: "${task.userStory}" (expected format: US-001)`,
188
+ suggestedFix: 'Use format: US-XXX where XXX is a 3-digit number'
189
+ });
190
+ }
191
+ // Check if US exists in spec.md
192
+ else if (!validUSIds.includes(task.userStory)) {
193
+ errors.push({
194
+ taskId: task.id,
195
+ field: 'userStory',
196
+ value: task.userStory,
197
+ message: `User Story ${task.userStory} not found in spec.md`,
198
+ suggestedFix: `Valid User Stories: ${validUSIds.join(', ')}`
199
+ });
200
+ }
201
+ }
202
+ // Validate satisfiesACs field
203
+ if (task.satisfiesACs && task.satisfiesACs.length > 0) {
204
+ for (const acId of task.satisfiesACs) {
205
+ // Check format (AC-USXX-YY)
206
+ if (!acId.match(/^AC-US\d+-\d{2}$/)) {
207
+ errors.push({
208
+ taskId: task.id,
209
+ field: 'satisfiesACs',
210
+ value: acId,
211
+ message: `Invalid AC-ID format: "${acId}" (expected format: AC-US1-01)`,
212
+ suggestedFix: 'Use format: AC-USXX-YY where XX is US number, YY is AC number'
213
+ });
214
+ continue;
215
+ }
216
+ // Check if AC exists in spec.md
217
+ if (!validACIds.includes(acId)) {
218
+ errors.push({
219
+ taskId: task.id,
220
+ field: 'satisfiesACs',
221
+ value: acId,
222
+ message: `Acceptance Criteria ${acId} not found in spec.md`,
223
+ suggestedFix: `Check spec.md for valid AC-IDs in this User Story`
224
+ });
225
+ continue;
226
+ }
227
+ // Check if AC belongs to correct User Story
228
+ if (task.userStory) {
229
+ const acUSNumber = extractUSNumberFromACId(acId);
230
+ const taskUSNumber = extractUSNumber(task.userStory);
231
+ if (acUSNumber !== taskUSNumber) {
232
+ errors.push({
233
+ taskId: task.id,
234
+ field: 'satisfiesACs',
235
+ value: acId,
236
+ message: `AC ${acId} belongs to US-${String(acUSNumber).padStart(3, '0')}, but task is linked to ${task.userStory}`,
237
+ suggestedFix: `Either link task to US-${String(acUSNumber).padStart(3, '0')} or use different AC-ID`
238
+ });
239
+ }
240
+ }
241
+ }
242
+ }
243
+ return errors;
244
+ }
245
+ /**
246
+ * Extract US number from AC-ID (AC-US1-01 → 1)
247
+ */
248
+ function extractUSNumberFromACId(acId) {
249
+ const match = acId.match(/^AC-US(\d+)-\d{2}$/);
250
+ return match ? parseInt(match[1], 10) : -1;
251
+ }
252
+ /**
253
+ * Extract US number from US-ID (US-001 → 1)
254
+ */
255
+ function extractUSNumber(usId) {
256
+ const match = usId.match(/^US-(\d{3})$/);
257
+ return match ? parseInt(match[1], 10) : -1;
258
+ }
259
+ /**
260
+ * Get all tasks (flattened, not grouped by US)
261
+ *
262
+ * @param tasksByUS - Tasks grouped by User Story
263
+ * @returns Array of all tasks
264
+ */
265
+ export function getAllTasks(tasksByUS) {
266
+ return Object.values(tasksByUS).flat();
267
+ }
268
+ /**
269
+ * Count tasks by status
270
+ *
271
+ * @param tasksByUS - Tasks grouped by User Story
272
+ * @returns Map of status → count
273
+ */
274
+ export function countTasksByStatus(tasksByUS) {
275
+ const counts = {
276
+ pending: 0,
277
+ in_progress: 0,
278
+ completed: 0,
279
+ transferred: 0,
280
+ canceled: 0
281
+ };
282
+ const allTasks = getAllTasks(tasksByUS);
283
+ allTasks.forEach(task => {
284
+ counts[task.status]++;
285
+ });
286
+ return counts;
287
+ }
288
+ /**
289
+ * Calculate completion percentage
290
+ *
291
+ * @param tasksByUS - Tasks grouped by User Story
292
+ * @returns Completion percentage (0-100)
293
+ */
294
+ export function calculateCompletionPercentage(tasksByUS) {
295
+ const allTasks = getAllTasks(tasksByUS);
296
+ if (allTasks.length === 0)
297
+ return 0;
298
+ const completedTasks = allTasks.filter(t => t.status === 'completed').length;
299
+ return Math.round((completedTasks / allTasks.length) * 100);
300
+ }
301
+ //# sourceMappingURL=task-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-parser.js","sourceRoot":"","sources":["../../../../src/generators/spec/task-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * AC Coverage Validator
3
+ *
4
+ * Validates Acceptance Criteria coverage by tasks:
5
+ * - Detects uncovered ACs (ACs with no implementing tasks)
6
+ * - Detects orphan tasks (tasks with no AC linkage)
7
+ * - Builds traceability matrix (AC ↔ Task mapping)
8
+ * - Calculates coverage metrics
9
+ *
10
+ * Used by `/specweave:validate` and `/specweave:done` commands to ensure
11
+ * all acceptance criteria are covered before increment closure.
12
+ */
13
+ /**
14
+ * AC Coverage Report
15
+ */
16
+ export interface ACCoverageReport {
17
+ /** Total number of Acceptance Criteria in spec.md */
18
+ totalACs: number;
19
+ /** Number of ACs covered by at least one task */
20
+ coveredACs: number;
21
+ /** AC-IDs with no implementing tasks */
22
+ uncoveredACs: string[];
23
+ /** Task IDs with no satisfiesACs field or invalid AC-IDs */
24
+ orphanTasks: string[];
25
+ /** Coverage percentage (0-100) */
26
+ coveragePercentage: number;
27
+ /** Traceability matrix: AC-ID → Task IDs */
28
+ acToTasksMap: Map<string, string[]>;
29
+ /** Reverse mapping: Task ID → AC-IDs */
30
+ taskToACsMap: Map<string, string[]>;
31
+ /** Per-User Story coverage breakdown */
32
+ coverageByUS: Map<string, USCoverageStats>;
33
+ /** Validation timestamp */
34
+ timestamp: string;
35
+ }
36
+ /**
37
+ * Coverage statistics per User Story
38
+ */
39
+ export interface USCoverageStats {
40
+ /** User Story ID */
41
+ usId: string;
42
+ /** User Story title */
43
+ title: string;
44
+ /** Total ACs for this US */
45
+ totalACs: number;
46
+ /** Covered ACs for this US */
47
+ coveredACs: number;
48
+ /** Coverage percentage for this US */
49
+ coveragePercentage: number;
50
+ /** Uncovered AC-IDs for this US */
51
+ uncoveredACs: string[];
52
+ }
53
+ /**
54
+ * Validation options
55
+ */
56
+ export interface ValidationOptions {
57
+ /** Allow orphan tasks (default: false) */
58
+ allowOrphans?: boolean;
59
+ /** Minimum coverage percentage required (default: 100) */
60
+ minCoveragePercentage?: number;
61
+ /** Logger for output messages */
62
+ logger?: {
63
+ log: (msg: string) => void;
64
+ error: (msg: string, err?: Error) => void;
65
+ warn: (msg: string) => void;
66
+ };
67
+ }
68
+ /**
69
+ * Validate AC coverage for an increment
70
+ *
71
+ * @param incrementPath - Path to increment directory (e.g., .specweave/increments/0047-us-task-linkage)
72
+ * @param options - Validation options
73
+ * @returns AC Coverage Report
74
+ * @throws Error if spec.md or tasks.md cannot be read
75
+ */
76
+ export declare function validateACCoverage(incrementPath: string, options?: ValidationOptions): ACCoverageReport;
77
+ /**
78
+ * Check if coverage report passes validation
79
+ *
80
+ * @param report - Coverage report
81
+ * @param options - Validation options
82
+ * @returns True if validation passes, false otherwise
83
+ */
84
+ export declare function isCoveragePassing(report: ACCoverageReport, options?: ValidationOptions): boolean;
85
+ /**
86
+ * Print detailed coverage report to console
87
+ *
88
+ * @param report - Coverage report
89
+ * @param options - Validation options
90
+ */
91
+ export declare function printCoverageReport(report: ACCoverageReport, options?: ValidationOptions): void;
92
+ /**
93
+ * Export coverage report to JSON file
94
+ *
95
+ * @param report - Coverage report
96
+ * @param outputPath - Path to output JSON file
97
+ */
98
+ export declare function exportCoverageReportJSON(report: ACCoverageReport, outputPath: string): Promise<void>;
99
+ /**
100
+ * Get recommended actions based on coverage report
101
+ *
102
+ * @param report - Coverage report
103
+ * @returns Array of recommended actions
104
+ */
105
+ export declare function getRecommendedActions(report: ACCoverageReport): string[];
106
+ //# sourceMappingURL=ac-coverage-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ac-coverage-validator.d.ts","sourceRoot":"","sources":["../../../src/validators/ac-coverage-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAYH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;IAEjB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IAEnB,wCAAwC;IACxC,YAAY,EAAE,MAAM,EAAE,CAAC;IAEvB,4DAA4D;IAC5D,WAAW,EAAE,MAAM,EAAE,CAAC;IAEtB,kCAAkC;IAClC,kBAAkB,EAAE,MAAM,CAAC;IAE3B,4CAA4C;IAC5C,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAEpC,wCAAwC;IACxC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAEpC,wCAAwC;IACxC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAE3C,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IAEb,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC;IAEd,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IAEjB,8BAA8B;IAC9B,UAAU,EAAE,MAAM,CAAC;IAEnB,sCAAsC;IACtC,kBAAkB,EAAE,MAAM,CAAC;IAE3B,mCAAmC;IACnC,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,0CAA0C;IAC1C,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB,0DAA0D;IAC1D,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B,iCAAiC;IACjC,MAAM,CAAC,EAAE;QACP,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAC3B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;QAC1C,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;KAC7B,CAAC;CACH;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CAwGlB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAeT;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE,iBAAsB,GAC9B,IAAI,CAqEN;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE,CA4BxE"}
@@ -0,0 +1,250 @@
1
+ /**
2
+ * AC Coverage Validator
3
+ *
4
+ * Validates Acceptance Criteria coverage by tasks:
5
+ * - Detects uncovered ACs (ACs with no implementing tasks)
6
+ * - Detects orphan tasks (tasks with no AC linkage)
7
+ * - Builds traceability matrix (AC ↔ Task mapping)
8
+ * - Calculates coverage metrics
9
+ *
10
+ * Used by `/specweave:validate` and `/specweave:done` commands to ensure
11
+ * all acceptance criteria are covered before increment closure.
12
+ */
13
+ import path from 'path';
14
+ import { existsSync } from 'fs';
15
+ import { parseSpecMd } from '../generators/spec/spec-parser.js';
16
+ import { parseTasksWithUSLinks, getAllTasks } from '../generators/spec/task-parser.js';
17
+ /**
18
+ * Validate AC coverage for an increment
19
+ *
20
+ * @param incrementPath - Path to increment directory (e.g., .specweave/increments/0047-us-task-linkage)
21
+ * @param options - Validation options
22
+ * @returns AC Coverage Report
23
+ * @throws Error if spec.md or tasks.md cannot be read
24
+ */
25
+ export function validateACCoverage(incrementPath, options = {}) {
26
+ const logger = options.logger ?? console;
27
+ // Validate paths
28
+ const specPath = path.join(incrementPath, 'spec.md');
29
+ const tasksPath = path.join(incrementPath, 'tasks.md');
30
+ if (!existsSync(specPath)) {
31
+ throw new Error(`spec.md not found at ${specPath}`);
32
+ }
33
+ if (!existsSync(tasksPath)) {
34
+ throw new Error(`tasks.md not found at ${tasksPath}`);
35
+ }
36
+ // Parse spec.md to get all ACs
37
+ logger.log('šŸ“‹ Parsing spec.md to extract Acceptance Criteria...');
38
+ const specMetadata = parseSpecMd(specPath);
39
+ logger.log(` Found ${specMetadata.allACIds.length} Acceptance Criteria across ${specMetadata.userStories.length} User Stories`);
40
+ // Parse tasks.md to get all tasks
41
+ logger.log('šŸ“ Parsing tasks.md to extract tasks...');
42
+ const tasksByUS = parseTasksWithUSLinks(tasksPath);
43
+ const allTasks = getAllTasks(tasksByUS);
44
+ logger.log(` Found ${allTasks.length} tasks`);
45
+ // Build traceability matrices
46
+ logger.log('šŸ”— Building traceability matrix...');
47
+ const acToTasksMap = new Map();
48
+ const taskToACsMap = new Map();
49
+ const orphanTasks = [];
50
+ // Initialize AC map with empty arrays
51
+ specMetadata.allACIds.forEach(acId => {
52
+ acToTasksMap.set(acId, []);
53
+ });
54
+ // Populate mappings from tasks
55
+ allTasks.forEach(task => {
56
+ if (!task.satisfiesACs || task.satisfiesACs.length === 0) {
57
+ // Task has no AC linkage - mark as orphan
58
+ orphanTasks.push(task.id);
59
+ return;
60
+ }
61
+ // Store task → ACs mapping
62
+ taskToACsMap.set(task.id, task.satisfiesACs);
63
+ // Store AC → tasks mapping
64
+ task.satisfiesACs.forEach(acId => {
65
+ // Only count if AC exists in spec.md (valid AC-ID)
66
+ if (acToTasksMap.has(acId)) {
67
+ const tasks = acToTasksMap.get(acId);
68
+ tasks.push(task.id);
69
+ }
70
+ });
71
+ });
72
+ // Calculate coverage
73
+ const uncoveredACs = [];
74
+ acToTasksMap.forEach((tasks, acId) => {
75
+ if (tasks.length === 0) {
76
+ uncoveredACs.push(acId);
77
+ }
78
+ });
79
+ const coveredACs = specMetadata.allACIds.length - uncoveredACs.length;
80
+ const coveragePercentage = specMetadata.allACIds.length > 0
81
+ ? Math.round((coveredACs / specMetadata.allACIds.length) * 100)
82
+ : 100;
83
+ // Calculate per-US coverage
84
+ const coverageByUS = new Map();
85
+ specMetadata.userStories.forEach(us => {
86
+ const usACs = us.acceptanceCriteria;
87
+ const usUncoveredACs = usACs.filter(acId => uncoveredACs.includes(acId));
88
+ const usCoveredACs = usACs.length - usUncoveredACs.length;
89
+ const usCoveragePercentage = usACs.length > 0
90
+ ? Math.round((usCoveredACs / usACs.length) * 100)
91
+ : 100;
92
+ coverageByUS.set(us.id, {
93
+ usId: us.id,
94
+ title: us.title,
95
+ totalACs: usACs.length,
96
+ coveredACs: usCoveredACs,
97
+ coveragePercentage: usCoveragePercentage,
98
+ uncoveredACs: usUncoveredACs
99
+ });
100
+ });
101
+ logger.log(` Coverage: ${coveredACs}/${specMetadata.allACIds.length} ACs (${coveragePercentage}%)`);
102
+ logger.log(` Orphan tasks: ${orphanTasks.length}`);
103
+ return {
104
+ totalACs: specMetadata.allACIds.length,
105
+ coveredACs,
106
+ uncoveredACs,
107
+ orphanTasks,
108
+ coveragePercentage,
109
+ acToTasksMap,
110
+ taskToACsMap,
111
+ coverageByUS,
112
+ timestamp: new Date().toISOString()
113
+ };
114
+ }
115
+ /**
116
+ * Check if coverage report passes validation
117
+ *
118
+ * @param report - Coverage report
119
+ * @param options - Validation options
120
+ * @returns True if validation passes, false otherwise
121
+ */
122
+ export function isCoveragePassing(report, options = {}) {
123
+ const minCoverage = options.minCoveragePercentage ?? 100;
124
+ const allowOrphans = options.allowOrphans ?? false;
125
+ // Check coverage percentage
126
+ if (report.coveragePercentage < minCoverage) {
127
+ return false;
128
+ }
129
+ // Check orphan tasks
130
+ if (!allowOrphans && report.orphanTasks.length > 0) {
131
+ return false;
132
+ }
133
+ return true;
134
+ }
135
+ /**
136
+ * Print detailed coverage report to console
137
+ *
138
+ * @param report - Coverage report
139
+ * @param options - Validation options
140
+ */
141
+ export function printCoverageReport(report, options = {}) {
142
+ const logger = options.logger ?? console;
143
+ logger.log('\n' + '='.repeat(80));
144
+ logger.log('šŸ“Š Acceptance Criteria Coverage Report');
145
+ logger.log('='.repeat(80));
146
+ // Summary
147
+ logger.log('\nšŸ“ˆ Summary:');
148
+ logger.log(` Total ACs: ${report.totalACs}`);
149
+ logger.log(` Covered ACs: ${report.coveredACs}`);
150
+ logger.log(` Uncovered ACs: ${report.uncoveredACs.length}`);
151
+ logger.log(` Coverage: ${report.coveragePercentage}%`);
152
+ logger.log(` Orphan Tasks: ${report.orphanTasks.length}`);
153
+ // Overall status
154
+ const passing = isCoveragePassing(report, options);
155
+ if (passing) {
156
+ logger.log('\nāœ… VALIDATION PASSED');
157
+ }
158
+ else {
159
+ logger.log('\nāŒ VALIDATION FAILED');
160
+ }
161
+ // Per-User Story breakdown
162
+ if (report.coverageByUS.size > 0) {
163
+ logger.log('\nšŸ“‹ Coverage by User Story:');
164
+ report.coverageByUS.forEach(stats => {
165
+ const icon = stats.coveragePercentage === 100 ? 'āœ…' : 'āš ļø';
166
+ logger.log(` ${icon} ${stats.usId}: ${stats.coveredACs}/${stats.totalACs} ACs (${stats.coveragePercentage}%)`);
167
+ });
168
+ }
169
+ // Uncovered ACs
170
+ if (report.uncoveredACs.length > 0) {
171
+ logger.log('\nāš ļø Uncovered Acceptance Criteria:');
172
+ report.coverageByUS.forEach(stats => {
173
+ if (stats.uncoveredACs.length > 0) {
174
+ logger.log(` ${stats.usId} - ${stats.title}:`);
175
+ stats.uncoveredACs.forEach(acId => {
176
+ logger.log(` - ${acId}`);
177
+ });
178
+ }
179
+ });
180
+ logger.log('\nšŸ’” Suggestion: Create tasks that satisfy these ACs or mark them as not applicable');
181
+ }
182
+ // Orphan tasks
183
+ if (report.orphanTasks.length > 0) {
184
+ logger.log('\nāš ļø Orphan Tasks (no AC linkage):');
185
+ report.orphanTasks.forEach(taskId => {
186
+ logger.log(` - ${taskId}`);
187
+ });
188
+ logger.log('\nšŸ’” Suggestion: Add **Satisfies ACs** field to these tasks');
189
+ }
190
+ // Traceability matrix (only show if requested via debug mode)
191
+ if (process.env.DEBUG) {
192
+ logger.log('\nšŸ”— Traceability Matrix:');
193
+ report.acToTasksMap.forEach((tasks, acId) => {
194
+ if (tasks.length > 0) {
195
+ logger.log(` ${acId} ← ${tasks.join(', ')}`);
196
+ }
197
+ });
198
+ }
199
+ logger.log('\n' + '='.repeat(80));
200
+ logger.log(`Generated: ${report.timestamp}\n`);
201
+ }
202
+ /**
203
+ * Export coverage report to JSON file
204
+ *
205
+ * @param report - Coverage report
206
+ * @param outputPath - Path to output JSON file
207
+ */
208
+ export async function exportCoverageReportJSON(report, outputPath) {
209
+ const fs = await import('fs-extra');
210
+ // Convert Maps to plain objects for JSON serialization
211
+ const serializable = {
212
+ totalACs: report.totalACs,
213
+ coveredACs: report.coveredACs,
214
+ uncoveredACs: report.uncoveredACs,
215
+ orphanTasks: report.orphanTasks,
216
+ coveragePercentage: report.coveragePercentage,
217
+ acToTasksMap: Object.fromEntries(report.acToTasksMap),
218
+ taskToACsMap: Object.fromEntries(report.taskToACsMap),
219
+ coverageByUS: Object.fromEntries(report.coverageByUS),
220
+ timestamp: report.timestamp
221
+ };
222
+ await fs.writeJSON(outputPath, serializable, { spaces: 2 });
223
+ }
224
+ /**
225
+ * Get recommended actions based on coverage report
226
+ *
227
+ * @param report - Coverage report
228
+ * @returns Array of recommended actions
229
+ */
230
+ export function getRecommendedActions(report) {
231
+ const actions = [];
232
+ if (report.uncoveredACs.length > 0) {
233
+ actions.push(`Create tasks to satisfy ${report.uncoveredACs.length} uncovered AC(s): ${report.uncoveredACs.slice(0, 3).join(', ')}${report.uncoveredACs.length > 3 ? '...' : ''}`);
234
+ }
235
+ if (report.orphanTasks.length > 0) {
236
+ actions.push(`Add **Satisfies ACs** field to ${report.orphanTasks.length} orphan task(s): ${report.orphanTasks.slice(0, 3).join(', ')}${report.orphanTasks.length > 3 ? '...' : ''}`);
237
+ }
238
+ if (report.coveragePercentage < 100) {
239
+ const gap = 100 - report.coveragePercentage;
240
+ actions.push(`Increase coverage by ${gap}% to reach 100% target`);
241
+ }
242
+ // Check for User Stories with low coverage
243
+ report.coverageByUS.forEach(stats => {
244
+ if (stats.coveragePercentage < 80) {
245
+ actions.push(`User Story ${stats.usId} has low coverage (${stats.coveragePercentage}%) - prioritize tasks for this US`);
246
+ }
247
+ });
248
+ return actions;
249
+ }
250
+ //# sourceMappingURL=ac-coverage-validator.js.map