specweave 0.34.4 → 0.34.6

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 (76) hide show
  1. package/bin/fix-marketplace-errors.sh +55 -7
  2. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
  3. package/dist/plugins/specweave-github/lib/github-feature-sync.js +3 -1
  4. package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
  5. package/dist/src/cli/commands/init.d.ts.map +1 -1
  6. package/dist/src/cli/commands/init.js +11 -2
  7. package/dist/src/cli/commands/init.js.map +1 -1
  8. package/dist/src/cli/commands/living-docs.js +4 -4
  9. package/dist/src/cli/commands/living-docs.js.map +1 -1
  10. package/dist/src/cli/helpers/init/brownfield-analysis.js +15 -15
  11. package/dist/src/cli/helpers/init/brownfield-analysis.js.map +1 -1
  12. package/dist/src/cli/helpers/init/living-docs-preflight.js +7 -7
  13. package/dist/src/cli/helpers/init/living-docs-preflight.js.map +1 -1
  14. package/dist/src/cli/helpers/init/plugin-installer.d.ts.map +1 -1
  15. package/dist/src/cli/helpers/init/plugin-installer.js +61 -9
  16. package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
  17. package/dist/src/core/background/types.d.ts +3 -0
  18. package/dist/src/core/background/types.d.ts.map +1 -1
  19. package/dist/src/core/living-docs/delivery/delivery-generator.d.ts +58 -0
  20. package/dist/src/core/living-docs/delivery/delivery-generator.d.ts.map +1 -0
  21. package/dist/src/core/living-docs/delivery/delivery-generator.js +501 -0
  22. package/dist/src/core/living-docs/delivery/delivery-generator.js.map +1 -0
  23. package/dist/src/core/living-docs/delivery/index.d.ts +8 -0
  24. package/dist/src/core/living-docs/delivery/index.d.ts.map +1 -0
  25. package/dist/src/core/living-docs/delivery/index.js +7 -0
  26. package/dist/src/core/living-docs/delivery/index.js.map +1 -0
  27. package/dist/src/core/living-docs/diagrams/index.d.ts +8 -0
  28. package/dist/src/core/living-docs/diagrams/index.d.ts.map +1 -0
  29. package/dist/src/core/living-docs/diagrams/index.js +7 -0
  30. package/dist/src/core/living-docs/diagrams/index.js.map +1 -0
  31. package/dist/src/core/living-docs/diagrams/mermaid-generator.d.ts +103 -0
  32. package/dist/src/core/living-docs/diagrams/mermaid-generator.d.ts.map +1 -0
  33. package/dist/src/core/living-docs/diagrams/mermaid-generator.js +515 -0
  34. package/dist/src/core/living-docs/diagrams/mermaid-generator.js.map +1 -0
  35. package/dist/src/core/living-docs/enterprise/enterprise-generator.d.ts +85 -0
  36. package/dist/src/core/living-docs/enterprise/enterprise-generator.d.ts.map +1 -0
  37. package/dist/src/core/living-docs/enterprise/enterprise-generator.js +556 -0
  38. package/dist/src/core/living-docs/enterprise/enterprise-generator.js.map +1 -0
  39. package/dist/src/core/living-docs/enterprise/history-analyzer.d.ts +91 -0
  40. package/dist/src/core/living-docs/enterprise/history-analyzer.d.ts.map +1 -0
  41. package/dist/src/core/living-docs/enterprise/history-analyzer.js +321 -0
  42. package/dist/src/core/living-docs/enterprise/history-analyzer.js.map +1 -0
  43. package/dist/src/core/living-docs/enterprise/index.d.ts +18 -0
  44. package/dist/src/core/living-docs/enterprise/index.d.ts.map +1 -0
  45. package/dist/src/core/living-docs/enterprise/index.js +14 -0
  46. package/dist/src/core/living-docs/enterprise/index.js.map +1 -0
  47. package/dist/src/core/living-docs/enterprise/relationship-mapper.d.ts +58 -0
  48. package/dist/src/core/living-docs/enterprise/relationship-mapper.d.ts.map +1 -0
  49. package/dist/src/core/living-docs/enterprise/relationship-mapper.js +227 -0
  50. package/dist/src/core/living-docs/enterprise/relationship-mapper.js.map +1 -0
  51. package/dist/src/core/living-docs/enterprise/spec-loader.d.ts +161 -0
  52. package/dist/src/core/living-docs/enterprise/spec-loader.d.ts.map +1 -0
  53. package/dist/src/core/living-docs/enterprise/spec-loader.js +470 -0
  54. package/dist/src/core/living-docs/enterprise/spec-loader.js.map +1 -0
  55. package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.d.ts +31 -1
  56. package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.d.ts.map +1 -1
  57. package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.js +626 -14
  58. package/dist/src/core/living-docs/intelligent-analyzer/inconsistency-detector.js.map +1 -1
  59. package/dist/src/core/living-docs/intelligent-analyzer/index.d.ts +8 -0
  60. package/dist/src/core/living-docs/intelligent-analyzer/index.d.ts.map +1 -1
  61. package/dist/src/core/living-docs/intelligent-analyzer/index.js +87 -4
  62. package/dist/src/core/living-docs/intelligent-analyzer/index.js.map +1 -1
  63. package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts +3 -1
  64. package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts.map +1 -1
  65. package/dist/src/core/living-docs/operations/index.d.ts +8 -0
  66. package/dist/src/core/living-docs/operations/index.d.ts.map +1 -0
  67. package/dist/src/core/living-docs/operations/index.js +7 -0
  68. package/dist/src/core/living-docs/operations/index.js.map +1 -0
  69. package/dist/src/core/living-docs/operations/ops-generator.d.ts +53 -0
  70. package/dist/src/core/living-docs/operations/ops-generator.d.ts.map +1 -0
  71. package/dist/src/core/living-docs/operations/ops-generator.js +462 -0
  72. package/dist/src/core/living-docs/operations/ops-generator.js.map +1 -0
  73. package/package.json +1 -1
  74. package/plugins/specweave/commands/living-docs.md +168 -39
  75. package/plugins/specweave-github/lib/github-feature-sync.js +1 -1
  76. package/plugins/specweave-github/lib/github-feature-sync.ts +3 -1
@@ -3,33 +3,487 @@
3
3
  *
4
4
  * Finds inconsistencies, duplicates, ownership gaps,
5
5
  * and generates questions for CTO/PO.
6
+ *
7
+ * Enhanced with:
8
+ * - Severity categorization (P0-P3)
9
+ * - Broken link detection
10
+ * - Spec-code gap detection
11
+ * - Orphaned document detection
6
12
  */
7
13
  import * as fs from 'fs';
8
14
  import * as path from 'path';
9
- export async function detectInconsistencies(repoAnalyses, orgResult, llmProvider, onProgress, log) {
10
- log('PHASE E: Inconsistency Detection');
15
+ import { glob } from 'glob';
16
+ export async function detectInconsistencies(repoAnalyses, orgResult, llmProvider, onProgress, log, projectPath) {
17
+ log('PHASE E: Inconsistency Detection (Enhanced)');
11
18
  onProgress('inconsistency', 0, 100, 'Analyzing patterns');
12
19
  const issues = [];
20
+ const enhancedIssues = [];
13
21
  const techDebt = [];
14
- onProgress('inconsistency', 20, 100, 'Checking pattern consistency');
22
+ const byPriority = new Map([
23
+ ['P0', []],
24
+ ['P1', []],
25
+ ['P2', []],
26
+ ['P3', []],
27
+ ]);
28
+ // Phase E.1: Pattern Inconsistencies
29
+ onProgress('inconsistency', 10, 100, 'Checking pattern consistency');
15
30
  const patternIssues = detectPatternInconsistencies(repoAnalyses);
16
31
  issues.push(...patternIssues);
32
+ const enhancedPatterns = patternIssues.map(i => enhanceIssue(i, 'inconsistency'));
33
+ enhancedIssues.push(...enhancedPatterns);
17
34
  log(' Found ' + patternIssues.length + ' pattern inconsistencies');
18
- onProgress('inconsistency', 40, 100, 'Finding duplicates');
35
+ // Phase E.2: Potential Duplicates
36
+ onProgress('inconsistency', 20, 100, 'Finding duplicates');
19
37
  const duplicates = detectPotentialDuplicates(repoAnalyses);
20
38
  issues.push(...duplicates);
39
+ const enhancedDups = duplicates.map(i => enhanceIssue(i, 'duplicate'));
40
+ enhancedIssues.push(...enhancedDups);
21
41
  log(' Found ' + duplicates.length + ' potential duplicates');
22
- onProgress('inconsistency', 60, 100, 'Checking ownership');
42
+ // Phase E.3: Ownership Gaps
43
+ onProgress('inconsistency', 30, 100, 'Checking ownership');
23
44
  const ownershipIssues = detectOwnershipGaps(repoAnalyses, orgResult);
24
45
  issues.push(...ownershipIssues);
46
+ const enhancedOwn = ownershipIssues.map(i => enhanceIssue(i, 'ownership'));
47
+ enhancedIssues.push(...enhancedOwn);
25
48
  log(' Found ' + ownershipIssues.length + ' ownership issues');
26
- onProgress('inconsistency', 80, 100, 'Cataloging tech debt');
49
+ // Phase E.4: Broken Links (new)
50
+ let brokenLinks = [];
51
+ if (projectPath) {
52
+ onProgress('inconsistency', 40, 100, 'Detecting broken links');
53
+ brokenLinks = await detectBrokenLinks(projectPath);
54
+ log(' Found ' + brokenLinks.length + ' broken links');
55
+ // Convert to issues
56
+ if (brokenLinks.length > 0) {
57
+ const brokenLinkIssue = createBrokenLinkIssue(brokenLinks);
58
+ issues.push(brokenLinkIssue);
59
+ enhancedIssues.push(enhanceIssue(brokenLinkIssue, 'broken-link'));
60
+ }
61
+ }
62
+ // Phase E.5: Spec-Code Gaps (new)
63
+ let specCodeGaps = [];
64
+ if (projectPath) {
65
+ onProgress('inconsistency', 55, 100, 'Detecting spec-code gaps');
66
+ specCodeGaps = await detectSpecCodeGaps(projectPath);
67
+ log(' Found ' + specCodeGaps.length + ' spec-code gaps');
68
+ // Convert to issues
69
+ for (const gap of specCodeGaps) {
70
+ const gapIssue = createSpecCodeGapIssue(gap);
71
+ issues.push(gapIssue);
72
+ enhancedIssues.push(enhanceIssue(gapIssue, 'spec-gap'));
73
+ }
74
+ }
75
+ // Phase E.6: Orphaned Documents (new)
76
+ let orphanedDocs = [];
77
+ if (projectPath) {
78
+ onProgress('inconsistency', 70, 100, 'Detecting orphaned documents');
79
+ orphanedDocs = await detectOrphanedDocs(projectPath);
80
+ log(' Found ' + orphanedDocs.length + ' orphaned documents');
81
+ // Convert to issue
82
+ if (orphanedDocs.length > 0) {
83
+ const orphanIssue = createOrphanedDocsIssue(orphanedDocs);
84
+ issues.push(orphanIssue);
85
+ enhancedIssues.push(enhanceIssue(orphanIssue, 'orphaned'));
86
+ }
87
+ }
88
+ // Phase E.7: Tech Debt
89
+ onProgress('inconsistency', 85, 100, 'Cataloging tech debt');
27
90
  const debt = extractTechDebt(repoAnalyses);
28
91
  techDebt.push(...debt);
29
92
  log(' Identified ' + debt.length + ' tech debt items');
93
+ // Categorize by priority
94
+ for (const issue of enhancedIssues) {
95
+ byPriority.get(issue.priority).push(issue);
96
+ }
30
97
  const questionsForCTO = generateCTOQuestions(issues, techDebt, orgResult);
31
98
  const questionsForPO = generatePOQuestions(issues, repoAnalyses);
32
- return { issues, techDebt, questionsForCTO, questionsForPO };
99
+ onProgress('inconsistency', 100, 100, 'Detection complete');
100
+ return {
101
+ issues,
102
+ enhancedIssues,
103
+ techDebt,
104
+ questionsForCTO,
105
+ questionsForPO,
106
+ byPriority,
107
+ brokenLinks,
108
+ specCodeGaps,
109
+ orphanedDocs,
110
+ };
111
+ }
112
+ /**
113
+ * Enhance a basic issue with priority, remediation, and category
114
+ */
115
+ function enhanceIssue(issue, category) {
116
+ const priority = determinePriority(issue, category);
117
+ const remediation = generateRemediation(issue, category);
118
+ return {
119
+ ...issue,
120
+ priority,
121
+ remediation,
122
+ category,
123
+ };
124
+ }
125
+ /**
126
+ * Determine issue priority based on type and severity
127
+ */
128
+ function determinePriority(issue, category) {
129
+ // P0: Critical - blocks production or security risk
130
+ if (category === 'spec-gap' && issue.description.includes('ghost_completion')) {
131
+ return 'P0'; // Ghost completions are critical - shows we think something is done but isn't
132
+ }
133
+ if (category === 'broken-link' && issue.description.includes('critical')) {
134
+ return 'P0';
135
+ }
136
+ // P1: High - significant issues needing attention
137
+ if (issue.severity === 'critical')
138
+ return 'P1';
139
+ if (category === 'ownership' && issue.evidence && issue.evidence.length > 5) {
140
+ return 'P1'; // Many repos without owners is serious
141
+ }
142
+ if (category === 'duplicate' && issue.evidence && issue.evidence.length > 3) {
143
+ return 'P1'; // Many duplicates suggest bigger architecture issue
144
+ }
145
+ // P2: Medium - should fix but not urgent
146
+ if (issue.severity === 'important')
147
+ return 'P2';
148
+ if (category === 'inconsistency')
149
+ return 'P2';
150
+ if (category === 'orphaned')
151
+ return 'P2';
152
+ // P3: Low - nice to have
153
+ return 'P3';
154
+ }
155
+ /**
156
+ * Generate actionable remediation steps
157
+ */
158
+ function generateRemediation(issue, category) {
159
+ const steps = [];
160
+ switch (category) {
161
+ case 'inconsistency':
162
+ steps.push('1. Review the conflicting patterns across repositories');
163
+ steps.push('2. Create an ADR documenting the standard pattern to use');
164
+ steps.push('3. Create migration tickets for non-compliant repos');
165
+ steps.push('4. Update linting rules to enforce the standard');
166
+ break;
167
+ case 'duplicate':
168
+ steps.push('1. Compare functionality of both services/repos');
169
+ steps.push('2. Identify which is the canonical source');
170
+ steps.push('3. Plan consolidation or document why both exist');
171
+ steps.push('4. Update documentation to clarify boundaries');
172
+ break;
173
+ case 'ownership':
174
+ steps.push('1. Review each unassigned repository');
175
+ steps.push('2. Determine appropriate team based on domain');
176
+ steps.push('3. Update CODEOWNERS file');
177
+ steps.push('4. Add ownership to team charter documents');
178
+ break;
179
+ case 'broken-link':
180
+ steps.push('1. Identify source files with broken links');
181
+ steps.push('2. Update or remove broken references');
182
+ steps.push('3. Add link validation to CI/CD pipeline');
183
+ steps.push('4. Consider using relative links where possible');
184
+ break;
185
+ case 'spec-gap':
186
+ steps.push('1. Compare spec completion claims with actual code');
187
+ steps.push('2. Update spec status to reflect reality');
188
+ steps.push('3. Create tickets for missing implementations');
189
+ steps.push('4. Review completion verification process');
190
+ break;
191
+ case 'orphaned':
192
+ steps.push('1. Review each orphaned document');
193
+ steps.push('2. Archive if no longer relevant');
194
+ steps.push('3. Link to appropriate parent if still relevant');
195
+ steps.push('4. Update documentation structure');
196
+ break;
197
+ }
198
+ return steps;
199
+ }
200
+ /**
201
+ * Detect broken links in documentation
202
+ */
203
+ async function detectBrokenLinks(projectPath) {
204
+ const brokenLinks = [];
205
+ const docsPath = path.join(projectPath, '.specweave/docs');
206
+ if (!fs.existsSync(docsPath))
207
+ return brokenLinks;
208
+ const mdFiles = await glob('**/*.md', { cwd: docsPath, absolute: true });
209
+ // Link patterns to detect
210
+ const linkPatterns = [
211
+ /\[([^\]]+)\]\(([^)]+)\)/g, // Markdown links [text](url)
212
+ /\[\[([^\]]+)\]\]/g, // Wiki-style links [[page]]
213
+ /href="([^"]+)"/g, // HTML href
214
+ ];
215
+ for (const file of mdFiles) {
216
+ try {
217
+ const content = fs.readFileSync(file, 'utf-8');
218
+ const lines = content.split('\n');
219
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
220
+ const line = lines[lineNum];
221
+ for (const pattern of linkPatterns) {
222
+ let match;
223
+ pattern.lastIndex = 0; // Reset regex
224
+ while ((match = pattern.exec(line)) !== null) {
225
+ const link = match[2] || match[1];
226
+ // Skip external URLs and anchors
227
+ if (link.startsWith('http') || link.startsWith('#') || link.startsWith('mailto:')) {
228
+ continue;
229
+ }
230
+ // Check if local file exists
231
+ const brokenReason = checkLinkValidity(file, link, projectPath);
232
+ if (brokenReason) {
233
+ brokenLinks.push({
234
+ file: path.relative(projectPath, file),
235
+ line: lineNum + 1,
236
+ link,
237
+ reason: brokenReason,
238
+ });
239
+ }
240
+ }
241
+ }
242
+ }
243
+ }
244
+ catch {
245
+ // Skip files that can't be read
246
+ }
247
+ }
248
+ return brokenLinks;
249
+ }
250
+ /**
251
+ * Check if a link is valid
252
+ */
253
+ function checkLinkValidity(sourceFile, link, projectPath) {
254
+ // Remove anchor from link
255
+ const linkPath = link.split('#')[0];
256
+ if (!linkPath)
257
+ return null; // Pure anchor link
258
+ // Resolve relative to source file or project root
259
+ let targetPath;
260
+ if (linkPath.startsWith('/')) {
261
+ targetPath = path.join(projectPath, linkPath);
262
+ }
263
+ else {
264
+ targetPath = path.resolve(path.dirname(sourceFile), linkPath);
265
+ }
266
+ // Check if target exists
267
+ if (!fs.existsSync(targetPath)) {
268
+ // Try with .md extension
269
+ if (!fs.existsSync(targetPath + '.md')) {
270
+ return 'File not found: ' + linkPath;
271
+ }
272
+ }
273
+ return null;
274
+ }
275
+ /**
276
+ * Detect gaps between specifications and code
277
+ */
278
+ async function detectSpecCodeGaps(projectPath) {
279
+ const gaps = [];
280
+ const specsPath = path.join(projectPath, '.specweave/docs/internal/specs');
281
+ const incrementsPath = path.join(projectPath, '.specweave/increments');
282
+ if (!fs.existsSync(specsPath))
283
+ return gaps;
284
+ // Find all spec files
285
+ const specFiles = await glob('**/spec.md', { cwd: incrementsPath, absolute: true });
286
+ for (const specFile of specFiles) {
287
+ try {
288
+ const content = fs.readFileSync(specFile, 'utf-8');
289
+ const incrementFolder = path.dirname(specFile);
290
+ const metadataPath = path.join(incrementFolder, 'metadata.json');
291
+ if (!fs.existsSync(metadataPath))
292
+ continue;
293
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
294
+ // Check for ghost completions: status = completed but ACs unchecked
295
+ if (metadata.status === 'completed') {
296
+ const acPattern = /- \[ \] \*\*AC-/g;
297
+ const uncheckedACs = content.match(acPattern);
298
+ if (uncheckedACs && uncheckedACs.length > 0) {
299
+ gaps.push({
300
+ specId: metadata.id || path.basename(incrementFolder),
301
+ specFile: path.relative(projectPath, specFile),
302
+ type: 'ghost_completion',
303
+ confidence: 0.9,
304
+ details: `Increment marked complete but ${uncheckedACs.length} ACs unchecked`,
305
+ });
306
+ }
307
+ }
308
+ // Check for missing implementations: status = active but no recent activity
309
+ if (metadata.status === 'active') {
310
+ const lastActivity = metadata.updatedAt ? new Date(metadata.updatedAt) : null;
311
+ const daysSinceActivity = lastActivity
312
+ ? (Date.now() - lastActivity.getTime()) / (1000 * 60 * 60 * 24)
313
+ : 999;
314
+ if (daysSinceActivity > 30) {
315
+ gaps.push({
316
+ specId: metadata.id || path.basename(incrementFolder),
317
+ specFile: path.relative(projectPath, specFile),
318
+ type: 'missing_impl',
319
+ confidence: 0.7,
320
+ details: `Active increment with no activity for ${Math.floor(daysSinceActivity)} days`,
321
+ });
322
+ }
323
+ }
324
+ // Check for outdated specs: tasks completed but spec not updated
325
+ const tasksPath = path.join(incrementFolder, 'tasks.md');
326
+ if (fs.existsSync(tasksPath)) {
327
+ const tasksContent = fs.readFileSync(tasksPath, 'utf-8');
328
+ const completedTasks = (tasksContent.match(/\[x\]/gi) || []).length;
329
+ const totalTasks = (tasksContent.match(/### T-\d+/g) || []).length;
330
+ if (completedTasks > 0 && totalTasks > 0) {
331
+ const specCompletedACs = (content.match(/- \[x\] \*\*AC-/g) || []).length;
332
+ const specTotalACs = (content.match(/- \[[ x]\] \*\*AC-/g) || []).length;
333
+ // If tasks are mostly done but ACs aren't updated
334
+ if (completedTasks / totalTasks > 0.7 && specTotalACs > 0 && specCompletedACs / specTotalACs < 0.3) {
335
+ gaps.push({
336
+ specId: metadata.id || path.basename(incrementFolder),
337
+ specFile: path.relative(projectPath, specFile),
338
+ type: 'outdated_spec',
339
+ confidence: 0.8,
340
+ details: `${completedTasks}/${totalTasks} tasks done but only ${specCompletedACs}/${specTotalACs} ACs checked`,
341
+ });
342
+ }
343
+ }
344
+ }
345
+ }
346
+ catch {
347
+ // Skip files that can't be processed
348
+ }
349
+ }
350
+ return gaps;
351
+ }
352
+ /**
353
+ * Detect orphaned documents without proper links
354
+ */
355
+ async function detectOrphanedDocs(projectPath) {
356
+ const orphaned = [];
357
+ const docsPath = path.join(projectPath, '.specweave/docs');
358
+ if (!fs.existsSync(docsPath))
359
+ return orphaned;
360
+ const mdFiles = await glob('**/*.md', { cwd: docsPath, absolute: true });
361
+ // Build a map of all documents and their references
362
+ const docRefs = new Map();
363
+ const allDocs = new Set();
364
+ for (const file of mdFiles) {
365
+ const relativePath = path.relative(docsPath, file);
366
+ allDocs.add(relativePath);
367
+ docRefs.set(relativePath, new Set());
368
+ try {
369
+ const content = fs.readFileSync(file, 'utf-8');
370
+ // Find all internal links
371
+ const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
372
+ let match;
373
+ while ((match = linkPattern.exec(content)) !== null) {
374
+ const link = match[2];
375
+ if (!link.startsWith('http') && !link.startsWith('#')) {
376
+ // Normalize link path
377
+ const linkPath = link.split('#')[0];
378
+ if (linkPath) {
379
+ const absPath = path.resolve(path.dirname(file), linkPath);
380
+ const relPath = path.relative(docsPath, absPath);
381
+ docRefs.get(relativePath).add(relPath);
382
+ }
383
+ }
384
+ }
385
+ }
386
+ catch {
387
+ // Skip unreadable files
388
+ }
389
+ }
390
+ // Find documents that are never referenced (except by themselves)
391
+ const referencedDocs = new Set();
392
+ for (const [source, refs] of docRefs) {
393
+ for (const ref of refs) {
394
+ if (ref !== source) {
395
+ referencedDocs.add(ref);
396
+ }
397
+ }
398
+ }
399
+ // Index files and special files are not orphaned
400
+ const specialPatterns = [
401
+ /^index\.md$/i,
402
+ /^readme\.md$/i,
403
+ /^_/,
404
+ /overview/i,
405
+ /^internal\/specs\//, // Specs are linked from increments
406
+ ];
407
+ for (const doc of allDocs) {
408
+ if (!referencedDocs.has(doc)) {
409
+ // Check if it's a special file
410
+ const isSpecial = specialPatterns.some(p => p.test(doc));
411
+ if (!isSpecial) {
412
+ orphaned.push(doc);
413
+ }
414
+ }
415
+ }
416
+ return orphaned;
417
+ }
418
+ /**
419
+ * Create an issue for broken links
420
+ */
421
+ function createBrokenLinkIssue(brokenLinks) {
422
+ return {
423
+ id: 'BLK-001',
424
+ type: 'broken-link',
425
+ severity: brokenLinks.length > 10 ? 'critical' : 'important',
426
+ title: brokenLinks.length + ' broken links in documentation',
427
+ description: 'Found broken internal links that need to be fixed:\n' +
428
+ brokenLinks.slice(0, 10).map(l => `- ${l.file}:${l.line}: ${l.link} (${l.reason})`).join('\n') +
429
+ (brokenLinks.length > 10 ? `\n... and ${brokenLinks.length - 10} more` : ''),
430
+ evidence: brokenLinks.slice(0, 5).map(l => ({
431
+ repo: 'docs',
432
+ files: [l.file],
433
+ details: l.reason,
434
+ })),
435
+ suggestedAction: 'Fix broken links in documentation',
436
+ targetAudience: 'team',
437
+ };
438
+ }
439
+ /**
440
+ * Create an issue for spec-code gaps
441
+ */
442
+ function createSpecCodeGapIssue(gap) {
443
+ const typeLabels = {
444
+ ghost_completion: 'Ghost Completion',
445
+ missing_impl: 'Missing Implementation',
446
+ outdated_spec: 'Outdated Specification',
447
+ };
448
+ return {
449
+ id: 'GAP-' + gap.specId,
450
+ type: 'spec-gap',
451
+ severity: gap.type === 'ghost_completion' ? 'critical' : 'important',
452
+ title: typeLabels[gap.type] + ': ' + gap.specId,
453
+ description: gap.details,
454
+ evidence: [{
455
+ repo: 'increments',
456
+ files: [gap.specFile],
457
+ details: `Confidence: ${Math.round(gap.confidence * 100)}%`,
458
+ }],
459
+ suggestedAction: gap.type === 'ghost_completion'
460
+ ? 'Review and update increment status or complete missing work'
461
+ : gap.type === 'missing_impl'
462
+ ? 'Resume work on stalled increment or archive if no longer needed'
463
+ : 'Update spec.md to reflect completed work',
464
+ targetAudience: 'team',
465
+ };
466
+ }
467
+ /**
468
+ * Create an issue for orphaned documents
469
+ */
470
+ function createOrphanedDocsIssue(orphanedDocs) {
471
+ return {
472
+ id: 'ORP-001',
473
+ type: 'orphaned',
474
+ severity: 'minor',
475
+ title: orphanedDocs.length + ' orphaned documents without references',
476
+ description: 'These documents are not linked from anywhere:\n' +
477
+ orphanedDocs.slice(0, 15).map(d => `- ${d}`).join('\n') +
478
+ (orphanedDocs.length > 15 ? `\n... and ${orphanedDocs.length - 15} more` : ''),
479
+ evidence: orphanedDocs.slice(0, 5).map(d => ({
480
+ repo: 'docs',
481
+ files: [d],
482
+ details: 'No incoming links',
483
+ })),
484
+ suggestedAction: 'Review and either link, archive, or delete orphaned documents',
485
+ targetAudience: 'team',
486
+ };
33
487
  }
34
488
  function detectPatternInconsistencies(analyses) {
35
489
  const issues = [];
@@ -185,6 +639,7 @@ export async function saveReviewNeeded(projectPath, result) {
185
639
  const savedFiles = [];
186
640
  const reviewPath = path.join(projectPath, '.specweave/docs/internal/review-needed');
187
641
  fs.mkdirSync(reviewPath, { recursive: true });
642
+ // Save questions for CTO
188
643
  if (result.questionsForCTO.length > 0) {
189
644
  const ctoFile = path.join(reviewPath, 'questions-for-cto.md');
190
645
  fs.writeFileSync(ctoFile, [
@@ -196,6 +651,7 @@ export async function saveReviewNeeded(projectPath, result) {
196
651
  ].join('\n'));
197
652
  savedFiles.push(ctoFile);
198
653
  }
654
+ // Save questions for PO
199
655
  if (result.questionsForPO.length > 0) {
200
656
  const poFile = path.join(reviewPath, 'questions-for-po.md');
201
657
  fs.writeFileSync(poFile, [
@@ -207,35 +663,191 @@ export async function saveReviewNeeded(projectPath, result) {
207
663
  ].join('\n'));
208
664
  savedFiles.push(poFile);
209
665
  }
210
- const inconsistencies = result.issues.filter(i => i.type === 'inconsistency');
666
+ // Save CRITICAL-ISSUES.md (P0 and P1 only)
667
+ const criticalIssues = result.enhancedIssues.filter(i => i.priority === 'P0' || i.priority === 'P1');
668
+ if (criticalIssues.length > 0) {
669
+ const critFile = path.join(reviewPath, 'CRITICAL-ISSUES.md');
670
+ const lines = [
671
+ '# 🚨 Critical Issues',
672
+ '',
673
+ '*Auto-generated by Intelligent Analyzer*',
674
+ '',
675
+ `> **${criticalIssues.filter(i => i.priority === 'P0').length} P0** and **${criticalIssues.filter(i => i.priority === 'P1').length} P1** issues require immediate attention.`,
676
+ '',
677
+ ];
678
+ // P0 first
679
+ const p0Issues = criticalIssues.filter(i => i.priority === 'P0');
680
+ if (p0Issues.length > 0) {
681
+ lines.push('## P0 - Critical (Immediate Action Required)', '');
682
+ for (const issue of p0Issues) {
683
+ lines.push('### ' + issue.id + ': ' + issue.title, '', '**Category**: ' + issue.category, '', issue.description, '', '**Remediation Steps**:', ...issue.remediation, '');
684
+ }
685
+ }
686
+ // P1 next
687
+ const p1Issues = criticalIssues.filter(i => i.priority === 'P1');
688
+ if (p1Issues.length > 0) {
689
+ lines.push('## P1 - High Priority', '');
690
+ for (const issue of p1Issues) {
691
+ lines.push('### ' + issue.id + ': ' + issue.title, '', '**Category**: ' + issue.category, '', issue.description, '', '**Remediation Steps**:', ...issue.remediation, '');
692
+ }
693
+ }
694
+ fs.writeFileSync(critFile, lines.join('\n'));
695
+ savedFiles.push(critFile);
696
+ }
697
+ // Save inconsistencies.md
698
+ const inconsistencies = result.enhancedIssues.filter(i => i.category === 'inconsistency');
211
699
  if (inconsistencies.length > 0) {
212
700
  const incFile = path.join(reviewPath, 'inconsistencies.md');
213
- const lines = ['# Inconsistencies', '', '*Auto-generated by Intelligent Analyzer*', ''];
701
+ const lines = ['# Pattern Inconsistencies', '', '*Auto-generated by Intelligent Analyzer*', ''];
214
702
  for (const issue of inconsistencies) {
215
- lines.push('## ' + issue.id + ': ' + issue.title, '', issue.description, '', '**Suggested action**: ' + issue.suggestedAction, '');
703
+ lines.push('## ' + issue.id + ': ' + issue.title, '', '**Priority**: ' + issue.priority, '', issue.description, '', '**Remediation**:', ...issue.remediation, '');
216
704
  }
217
705
  fs.writeFileSync(incFile, lines.join('\n'));
218
706
  savedFiles.push(incFile);
219
707
  }
220
- const duplicates = result.issues.filter(i => i.type === 'duplicate');
708
+ // Save potential-duplicates.md
709
+ const duplicates = result.enhancedIssues.filter(i => i.category === 'duplicate');
221
710
  if (duplicates.length > 0) {
222
711
  const dupFile = path.join(reviewPath, 'potential-duplicates.md');
223
712
  const lines = ['# Potential Duplicates', '', '*Auto-generated by Intelligent Analyzer*', ''];
224
713
  for (const issue of duplicates) {
225
- lines.push('## ' + issue.id + ': ' + issue.title, '', issue.description, '', '**Suggested action**: ' + issue.suggestedAction, '');
714
+ lines.push('## ' + issue.id + ': ' + issue.title, '', '**Priority**: ' + issue.priority, '', issue.description, '', '**Remediation**:', ...issue.remediation, '');
226
715
  }
227
716
  fs.writeFileSync(dupFile, lines.join('\n'));
228
717
  savedFiles.push(dupFile);
229
718
  }
719
+ // Save BROKEN-LINKS.md (new)
720
+ if (result.brokenLinks.length > 0) {
721
+ const blFile = path.join(reviewPath, 'BROKEN-LINKS.md');
722
+ const lines = [
723
+ '# Broken Links',
724
+ '',
725
+ '*Auto-generated by Intelligent Analyzer*',
726
+ '',
727
+ `Found **${result.brokenLinks.length}** broken links in documentation.`,
728
+ '',
729
+ '| File | Line | Link | Reason |',
730
+ '|------|------|------|--------|',
731
+ ...result.brokenLinks.map(l => `| ${l.file} | ${l.line} | \`${l.link}\` | ${l.reason} |`),
732
+ '',
733
+ '## How to Fix',
734
+ '',
735
+ '1. Update each broken link to point to the correct file',
736
+ '2. Remove links to deleted content',
737
+ '3. Use relative paths where possible',
738
+ '4. Run `/sw:organize-docs` to regenerate navigation indexes',
739
+ ];
740
+ fs.writeFileSync(blFile, lines.join('\n'));
741
+ savedFiles.push(blFile);
742
+ }
743
+ // Save SPEC-CODE-GAPS.md (new)
744
+ if (result.specCodeGaps.length > 0) {
745
+ const gapFile = path.join(reviewPath, 'SPEC-CODE-GAPS.md');
746
+ const lines = [
747
+ '# Spec-Code Gaps',
748
+ '',
749
+ '*Auto-generated by Intelligent Analyzer*',
750
+ '',
751
+ 'Detected gaps between specifications and actual implementation status.',
752
+ '',
753
+ ];
754
+ // Group by type
755
+ const ghostCompletions = result.specCodeGaps.filter(g => g.type === 'ghost_completion');
756
+ const missingImpl = result.specCodeGaps.filter(g => g.type === 'missing_impl');
757
+ const outdatedSpecs = result.specCodeGaps.filter(g => g.type === 'outdated_spec');
758
+ if (ghostCompletions.length > 0) {
759
+ lines.push('## 🚨 Ghost Completions (Critical)', '', 'These increments are marked as completed but have unchecked acceptance criteria:', '', '| Spec ID | File | Details | Confidence |', '|---------|------|---------|------------|', ...ghostCompletions.map(g => `| ${g.specId} | ${g.specFile} | ${g.details} | ${Math.round(g.confidence * 100)}% |`), '');
760
+ }
761
+ if (missingImpl.length > 0) {
762
+ lines.push('## ⚠️ Missing Implementations', '', 'These active increments have had no recent activity:', '', '| Spec ID | File | Details | Confidence |', '|---------|------|---------|------------|', ...missingImpl.map(g => `| ${g.specId} | ${g.specFile} | ${g.details} | ${Math.round(g.confidence * 100)}% |`), '');
763
+ }
764
+ if (outdatedSpecs.length > 0) {
765
+ lines.push('## 📝 Outdated Specifications', '', 'These specs have completed tasks but haven\'t updated their acceptance criteria:', '', '| Spec ID | File | Details | Confidence |', '|---------|------|---------|------------|', ...outdatedSpecs.map(g => `| ${g.specId} | ${g.specFile} | ${g.details} | ${Math.round(g.confidence * 100)}% |`), '');
766
+ }
767
+ fs.writeFileSync(gapFile, lines.join('\n'));
768
+ savedFiles.push(gapFile);
769
+ }
770
+ // Save ORPHANED-DOCS.md (new)
771
+ if (result.orphanedDocs.length > 0) {
772
+ const orphanFile = path.join(reviewPath, 'ORPHANED-DOCS.md');
773
+ const lines = [
774
+ '# Orphaned Documents',
775
+ '',
776
+ '*Auto-generated by Intelligent Analyzer*',
777
+ '',
778
+ `Found **${result.orphanedDocs.length}** documents with no incoming links.`,
779
+ '',
780
+ '## Documents to Review',
781
+ '',
782
+ ...result.orphanedDocs.map(d => `- ${d}`),
783
+ '',
784
+ '## Recommended Actions',
785
+ '',
786
+ '1. **Link** - Add links from parent documents if still relevant',
787
+ '2. **Archive** - Move to archive if no longer needed',
788
+ '3. **Delete** - Remove if completely obsolete',
789
+ '4. **Consolidate** - Merge with related documents',
790
+ ];
791
+ fs.writeFileSync(orphanFile, lines.join('\n'));
792
+ savedFiles.push(orphanFile);
793
+ }
794
+ // Save tech-debt.md
230
795
  if (result.techDebt.length > 0) {
231
796
  const debtFile = path.join(reviewPath, 'tech-debt.md');
232
- const lines = ['# Tech Debt', '', '*Auto-generated by Intelligent Analyzer*', ''];
797
+ const lines = ['# Tech Debt Catalog', '', '*Auto-generated by Intelligent Analyzer*', ''];
798
+ // Group by severity
799
+ const bySeverity = new Map();
233
800
  for (const item of result.techDebt) {
234
- lines.push('## ' + item.id + ': ' + item.title, '', item.description, '', '- **Severity**: ' + item.severity, '- **Effort**: ' + item.effort, '- **Repos**: ' + item.repos.join(', '), '');
801
+ if (!bySeverity.has(item.severity))
802
+ bySeverity.set(item.severity, []);
803
+ bySeverity.get(item.severity).push(item);
804
+ }
805
+ for (const severity of ['high', 'medium', 'low']) {
806
+ const items = bySeverity.get(severity);
807
+ if (items && items.length > 0) {
808
+ lines.push('## ' + severity.charAt(0).toUpperCase() + severity.slice(1) + ' Priority', '');
809
+ for (const item of items) {
810
+ lines.push('### ' + item.id + ': ' + item.title, '', item.description, '', '- **Effort**: ' + item.effort, '- **Category**: ' + item.category, '- **Repos**: ' + item.repos.join(', '), '');
811
+ }
812
+ }
235
813
  }
236
814
  fs.writeFileSync(debtFile, lines.join('\n'));
237
815
  savedFiles.push(debtFile);
238
816
  }
817
+ // Save summary index
818
+ const indexFile = path.join(reviewPath, 'index.md');
819
+ const summary = [
820
+ '# Review Needed - Summary',
821
+ '',
822
+ '*Auto-generated by Intelligent Analyzer*',
823
+ '',
824
+ '## Overview',
825
+ '',
826
+ '| Category | Count | Priority |',
827
+ '|----------|-------|----------|',
828
+ `| Critical Issues | ${result.enhancedIssues.filter(i => i.priority === 'P0' || i.priority === 'P1').length} | P0/P1 |`,
829
+ `| Pattern Inconsistencies | ${result.enhancedIssues.filter(i => i.category === 'inconsistency').length} | P2 |`,
830
+ `| Potential Duplicates | ${result.enhancedIssues.filter(i => i.category === 'duplicate').length} | P2 |`,
831
+ `| Broken Links | ${result.brokenLinks.length} | P2/P3 |`,
832
+ `| Spec-Code Gaps | ${result.specCodeGaps.length} | P0-P2 |`,
833
+ `| Orphaned Docs | ${result.orphanedDocs.length} | P3 |`,
834
+ `| Tech Debt Items | ${result.techDebt.length} | Various |`,
835
+ '',
836
+ '## Priority Breakdown',
837
+ '',
838
+ `- **P0 (Critical)**: ${result.byPriority.get('P0')?.length || 0} issues`,
839
+ `- **P1 (High)**: ${result.byPriority.get('P1')?.length || 0} issues`,
840
+ `- **P2 (Medium)**: ${result.byPriority.get('P2')?.length || 0} issues`,
841
+ `- **P3 (Low)**: ${result.byPriority.get('P3')?.length || 0} issues`,
842
+ '',
843
+ '## Documents',
844
+ '',
845
+ savedFiles.length > 0
846
+ ? savedFiles.map(f => `- [${path.basename(f)}](./${path.basename(f)})`).join('\n')
847
+ : '*No issues found*',
848
+ ];
849
+ fs.writeFileSync(indexFile, summary.join('\n'));
850
+ savedFiles.push(indexFile);
239
851
  return savedFiles;
240
852
  }
241
853
  //# sourceMappingURL=inconsistency-detector.js.map