projscan 0.12.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +20 -14
  2. package/dist/analyzers/crossPackageImportCheck.d.ts +13 -0
  3. package/dist/analyzers/crossPackageImportCheck.js +136 -0
  4. package/dist/analyzers/crossPackageImportCheck.js.map +1 -0
  5. package/dist/analyzers/cycleCheck.d.ts +12 -0
  6. package/dist/analyzers/cycleCheck.js +65 -0
  7. package/dist/analyzers/cycleCheck.js.map +1 -0
  8. package/dist/cli/_shared.js +2 -2
  9. package/dist/cli/_shared.js.map +1 -1
  10. package/dist/cli/commands/audit.js +8 -2
  11. package/dist/cli/commands/audit.js.map +1 -1
  12. package/dist/cli/commands/coupling.js +4 -0
  13. package/dist/cli/commands/coupling.js.map +1 -1
  14. package/dist/cli/commands/dependencies.js +4 -3
  15. package/dist/cli/commands/dependencies.js.map +1 -1
  16. package/dist/cli/commands/doctor.js +4 -0
  17. package/dist/cli/commands/doctor.js.map +1 -1
  18. package/dist/cli/commands/explainIssue.d.ts +1 -0
  19. package/dist/cli/commands/explainIssue.js +49 -0
  20. package/dist/cli/commands/explainIssue.js.map +1 -0
  21. package/dist/cli/commands/fixSuggest.d.ts +1 -0
  22. package/dist/cli/commands/fixSuggest.js +71 -0
  23. package/dist/cli/commands/fixSuggest.js.map +1 -0
  24. package/dist/cli/commands/hotspots.js +4 -0
  25. package/dist/cli/commands/hotspots.js.map +1 -1
  26. package/dist/cli/commands/impact.d.ts +1 -0
  27. package/dist/cli/commands/impact.js +59 -0
  28. package/dist/cli/commands/impact.js.map +1 -0
  29. package/dist/cli/commands/review.d.ts +1 -0
  30. package/dist/cli/commands/review.js +70 -0
  31. package/dist/cli/commands/review.js.map +1 -0
  32. package/dist/cli/commands/search.js +6 -2
  33. package/dist/cli/commands/search.js.map +1 -1
  34. package/dist/cli/commands/watch.d.ts +1 -0
  35. package/dist/cli/commands/watch.js +58 -0
  36. package/dist/cli/commands/watch.js.map +1 -0
  37. package/dist/cli/index.js +10 -0
  38. package/dist/cli/index.js.map +1 -1
  39. package/dist/core/ast.d.ts +28 -0
  40. package/dist/core/ast.js +190 -0
  41. package/dist/core/ast.js.map +1 -1
  42. package/dist/core/auditRunner.d.ts +8 -0
  43. package/dist/core/auditRunner.js +50 -1
  44. package/dist/core/auditRunner.js.map +1 -1
  45. package/dist/core/codeGraph.d.ts +23 -1
  46. package/dist/core/codeGraph.js +198 -0
  47. package/dist/core/codeGraph.js.map +1 -1
  48. package/dist/core/dependencyAnalyzer.d.ts +15 -1
  49. package/dist/core/dependencyAnalyzer.js +115 -18
  50. package/dist/core/dependencyAnalyzer.js.map +1 -1
  51. package/dist/core/explainIssue.d.ts +9 -0
  52. package/dist/core/explainIssue.js +106 -0
  53. package/dist/core/explainIssue.js.map +1 -0
  54. package/dist/core/fileInspector.js +13 -0
  55. package/dist/core/fileInspector.js.map +1 -1
  56. package/dist/core/fixSuggest.d.ts +41 -0
  57. package/dist/core/fixSuggest.js +327 -0
  58. package/dist/core/fixSuggest.js.map +1 -0
  59. package/dist/core/impact.d.ts +27 -0
  60. package/dist/core/impact.js +169 -0
  61. package/dist/core/impact.js.map +1 -0
  62. package/dist/core/indexCache.js +5 -1
  63. package/dist/core/indexCache.js.map +1 -1
  64. package/dist/core/issueEngine.js +18 -0
  65. package/dist/core/issueEngine.js.map +1 -1
  66. package/dist/core/languages/goAdapter.js +5 -0
  67. package/dist/core/languages/goAdapter.js.map +1 -1
  68. package/dist/core/languages/goFunctions.d.ts +24 -0
  69. package/dist/core/languages/goFunctions.js +99 -0
  70. package/dist/core/languages/goFunctions.js.map +1 -0
  71. package/dist/core/languages/javaAdapter.js +5 -0
  72. package/dist/core/languages/javaAdapter.js.map +1 -1
  73. package/dist/core/languages/javaFunctions.d.ts +22 -0
  74. package/dist/core/languages/javaFunctions.js +87 -0
  75. package/dist/core/languages/javaFunctions.js.map +1 -0
  76. package/dist/core/languages/pythonAdapter.js +5 -0
  77. package/dist/core/languages/pythonAdapter.js.map +1 -1
  78. package/dist/core/languages/pythonFunctions.d.ts +23 -0
  79. package/dist/core/languages/pythonFunctions.js +87 -0
  80. package/dist/core/languages/pythonFunctions.js.map +1 -0
  81. package/dist/core/languages/rubyAdapter.js +5 -0
  82. package/dist/core/languages/rubyAdapter.js.map +1 -1
  83. package/dist/core/languages/rubyFunctions.d.ts +22 -0
  84. package/dist/core/languages/rubyFunctions.js +91 -0
  85. package/dist/core/languages/rubyFunctions.js.map +1 -0
  86. package/dist/core/review.d.ts +21 -0
  87. package/dist/core/review.js +457 -0
  88. package/dist/core/review.js.map +1 -0
  89. package/dist/core/searchIndex.d.ts +9 -0
  90. package/dist/core/searchIndex.js.map +1 -1
  91. package/dist/core/semanticSearch.d.ts +69 -2
  92. package/dist/core/semanticSearch.js +113 -55
  93. package/dist/core/semanticSearch.js.map +1 -1
  94. package/dist/core/watcher.d.ts +37 -0
  95. package/dist/core/watcher.js +126 -0
  96. package/dist/core/watcher.js.map +1 -0
  97. package/dist/index.d.ts +11 -2
  98. package/dist/index.js +7 -1
  99. package/dist/index.js.map +1 -1
  100. package/dist/mcp/tools/audit.js +7 -2
  101. package/dist/mcp/tools/audit.js.map +1 -1
  102. package/dist/mcp/tools/dependencies.js +11 -5
  103. package/dist/mcp/tools/dependencies.js.map +1 -1
  104. package/dist/mcp/tools/explainIssue.d.ts +2 -0
  105. package/dist/mcp/tools/explainIssue.js +30 -0
  106. package/dist/mcp/tools/explainIssue.js.map +1 -0
  107. package/dist/mcp/tools/file.js +1 -1
  108. package/dist/mcp/tools/file.js.map +1 -1
  109. package/dist/mcp/tools/fixSuggest.d.ts +2 -0
  110. package/dist/mcp/tools/fixSuggest.js +57 -0
  111. package/dist/mcp/tools/fixSuggest.js.map +1 -0
  112. package/dist/mcp/tools/hotspots.js +40 -1
  113. package/dist/mcp/tools/hotspots.js.map +1 -1
  114. package/dist/mcp/tools/impact.d.ts +2 -0
  115. package/dist/mcp/tools/impact.js +59 -0
  116. package/dist/mcp/tools/impact.js.map +1 -0
  117. package/dist/mcp/tools/review.d.ts +2 -0
  118. package/dist/mcp/tools/review.js +54 -0
  119. package/dist/mcp/tools/review.js.map +1 -0
  120. package/dist/mcp/tools/search.js +9 -2
  121. package/dist/mcp/tools/search.js.map +1 -1
  122. package/dist/mcp/tools.js +8 -0
  123. package/dist/mcp/tools.js.map +1 -1
  124. package/dist/reporters/consoleReporter.d.ts +10 -1
  125. package/dist/reporters/consoleReporter.js +218 -0
  126. package/dist/reporters/consoleReporter.js.map +1 -1
  127. package/dist/reporters/htmlReporter.d.ts +8 -0
  128. package/dist/reporters/htmlReporter.js +287 -0
  129. package/dist/reporters/htmlReporter.js.map +1 -0
  130. package/dist/reporters/jsonReporter.d.ts +10 -1
  131. package/dist/reporters/jsonReporter.js +12 -0
  132. package/dist/reporters/jsonReporter.js.map +1 -1
  133. package/dist/reporters/markdownReporter.d.ts +10 -1
  134. package/dist/reporters/markdownReporter.js +173 -0
  135. package/dist/reporters/markdownReporter.js.map +1 -1
  136. package/dist/tool-manifest.json +133 -8
  137. package/dist/types.d.ts +276 -1
  138. package/dist/utils/config.js +27 -0
  139. package/dist/utils/config.js.map +1 -1
  140. package/package.json +4 -2
@@ -0,0 +1,287 @@
1
+ import { calculateScore, badgeMarkdown } from '../utils/scoreCalculator.js';
2
+ /**
3
+ * Standalone HTML report (0.16.0+). Each top-level command has its own
4
+ * renderer; the output is a single self-contained HTML document with
5
+ * inline CSS, no external assets, no JS framework. Suitable for posting
6
+ * as a CI artifact or sharing in a PR comment.
7
+ *
8
+ * Style choices:
9
+ * - System font stack to render readably without webfonts.
10
+ * - Two-tone colour palette (neutral grey + one accent per severity).
11
+ * - Tables for data, no charts. Charts would need JS.
12
+ * - Accessibility: tables get `<th>`s with scope, severity has both
13
+ * colour AND a text glyph so colourblind readers don't lose info.
14
+ */
15
+ const STYLE = `
16
+ :root {
17
+ color-scheme: light dark;
18
+ }
19
+ * { box-sizing: border-box; }
20
+ body {
21
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
22
+ margin: 0;
23
+ padding: 2rem;
24
+ max-width: 1100px;
25
+ margin-inline: auto;
26
+ line-height: 1.5;
27
+ color: #222;
28
+ background: #fafafa;
29
+ }
30
+ @media (prefers-color-scheme: dark) {
31
+ body { color: #ddd; background: #181818; }
32
+ .card { background: #222; border-color: #333; }
33
+ table { background: #222; }
34
+ th { background: #2a2a2a; }
35
+ tr:nth-child(even) td { background: #1f1f1f; }
36
+ code { background: #2a2a2a; }
37
+ a { color: #6cb6ff; }
38
+ }
39
+ h1 { margin-top: 0; font-size: 1.6rem; }
40
+ h2 { margin-top: 2rem; font-size: 1.2rem; border-bottom: 1px solid #ccc; padding-bottom: 0.3rem; }
41
+ .card {
42
+ background: #fff;
43
+ border: 1px solid #ddd;
44
+ border-radius: 8px;
45
+ padding: 1rem 1.2rem;
46
+ margin-bottom: 1rem;
47
+ }
48
+ .score { font-size: 2.5rem; font-weight: 700; }
49
+ .grade-A, .grade-A\\+ { color: #1a8a4a; }
50
+ .grade-B { color: #4a8a1a; }
51
+ .grade-C { color: #c79100; }
52
+ .grade-D { color: #d6582d; }
53
+ .grade-F { color: #d63333; }
54
+ table { width: 100%; border-collapse: collapse; margin: 0.5rem 0; background: #fff; }
55
+ th, td { padding: 0.4rem 0.6rem; text-align: left; border-bottom: 1px solid #eee; }
56
+ th { background: #f3f3f3; font-weight: 600; }
57
+ tr:nth-child(even) td { background: #f9f9f9; }
58
+ code { background: #f0f0f0; padding: 0.05em 0.4em; border-radius: 3px; font-size: 0.95em; }
59
+ .severity-error { color: #d63333; }
60
+ .severity-warning { color: #c79100; }
61
+ .severity-info { color: #4a6a8a; }
62
+ .muted { color: #888; font-size: 0.9rem; }
63
+ .verdict-ok { color: #1a8a4a; font-weight: 700; }
64
+ .verdict-review { color: #c79100; font-weight: 700; }
65
+ .verdict-block { color: #d63333; font-weight: 700; }
66
+ .right { text-align: right; }
67
+ .tag { display: inline-block; padding: 0.05em 0.5em; border-radius: 3px; background: #e8e8e8; font-size: 0.85em; }
68
+ .tag-new { background: #ffd6d6; color: #8a0000; }
69
+ .tag-expanded { background: #fff3c4; color: #6a4a00; }
70
+ `.trim();
71
+ /** Wrap inner HTML in a complete document. Title is required for accessibility. */
72
+ export function htmlShell(title, bodyHtml) {
73
+ return `<!DOCTYPE html>
74
+ <html lang="en">
75
+ <head>
76
+ <meta charset="UTF-8">
77
+ <meta name="viewport" content="width=device-width, initial-scale=1">
78
+ <title>${escapeHtml(title)}</title>
79
+ <style>${STYLE}</style>
80
+ </head>
81
+ <body>
82
+ ${bodyHtml}
83
+ <footer class="muted" style="margin-top: 3rem; padding-top: 1rem; border-top: 1px solid #ccc;">
84
+ Generated by <a href="https://github.com/abhiyoheswaran1/projscan">projscan</a> at ${new Date().toISOString()}
85
+ </footer>
86
+ </body>
87
+ </html>`;
88
+ }
89
+ export function reportHealthHtml(issues) {
90
+ const { score, grade, errors, warnings, infos } = calculateScore(issues);
91
+ const sevTotals = `<span class="severity-error">${errors}</span> errors · <span class="severity-warning">${warnings}</span> warnings · <span class="severity-info">${infos}</span> info`;
92
+ const issuesHtml = issues.length === 0
93
+ ? '<p>No issues detected. Project looks healthy.</p>'
94
+ : `<table>
95
+ <thead><tr><th>Severity</th><th>Title</th><th>Description</th><th>Suggested action</th></tr></thead>
96
+ <tbody>${issues
97
+ .map((i) => `<tr>
98
+ <td class="severity-${i.severity}">${severityIcon(i.severity)} ${i.severity}</td>
99
+ <td><code>${escapeHtml(i.id)}</code><br>${escapeHtml(i.title)}</td>
100
+ <td class="muted">${escapeHtml(i.description)}</td>
101
+ <td>${i.suggestedAction ? `${escapeHtml(i.suggestedAction.summary)}<br><code>projscan fix-suggest ${escapeHtml(i.id)}</code>` : '<span class="muted">—</span>'}</td>
102
+ </tr>`)
103
+ .join('\n')}</tbody>
104
+ </table>`;
105
+ const body = `
106
+ <h1>Project health</h1>
107
+ <div class="card">
108
+ <div class="score grade-${grade.replace('+', '\\+')}">${grade}</div>
109
+ <div>${score} / 100 — ${sevTotals}</div>
110
+ <p class="muted" style="margin-bottom: 0;">${badgeMarkdown(grade)}</p>
111
+ </div>
112
+ <h2>Issues (${issues.length})</h2>
113
+ ${issuesHtml}
114
+ `;
115
+ console.log(htmlShell(`Health · ${grade} (${score}/100)`, body));
116
+ }
117
+ export function reportHotspotsHtml(report) {
118
+ if (!report.available) {
119
+ console.log(htmlShell('Hotspots — unavailable', `<h1>Hotspots</h1><p class="muted">${escapeHtml(report.reason ?? 'Hotspots unavailable.')}</p>`));
120
+ return;
121
+ }
122
+ const rows = report.hotspots
123
+ .map((h, i) => `<tr>
124
+ <td class="right">${i + 1}</td>
125
+ <td class="right">${h.riskScore.toFixed(1)}</td>
126
+ <td><code>${escapeHtml(h.relativePath)}</code></td>
127
+ <td class="right">${h.churn}</td>
128
+ <td class="right">${typeof h.cyclomaticComplexity === 'number' ? h.cyclomaticComplexity : '-'}</td>
129
+ <td class="right">${h.lineCount}</td>
130
+ <td class="right">${h.issueCount}</td>
131
+ <td class="muted">${escapeHtml(h.reasons.join(', '))}</td>
132
+ </tr>`)
133
+ .join('\n');
134
+ const body = `
135
+ <h1>Hotspots</h1>
136
+ <p class="muted">Window: <strong>${escapeHtml(String(report.window.since ?? 'all'))}</strong> · ${report.window.commitsScanned} commit(s) scanned · ${report.totalFilesRanked} file(s) ranked</p>
137
+ <table>
138
+ <thead><tr><th>#</th><th class="right">Score</th><th>File</th><th class="right">Churn</th><th class="right">CC</th><th class="right">Lines</th><th class="right">Issues</th><th>Reasons</th></tr></thead>
139
+ <tbody>${rows}</tbody>
140
+ </table>
141
+ `;
142
+ console.log(htmlShell('Hotspots', body));
143
+ }
144
+ export function reportCouplingHtml(report) {
145
+ const cyclesHtml = report.cycles.length === 0
146
+ ? '<p class="muted">No circular imports detected.</p>'
147
+ : `<table>
148
+ <thead><tr><th class="right">Size</th><th>Files</th></tr></thead>
149
+ <tbody>${report.cycles
150
+ .map((c) => `<tr><td class="right">${c.size}</td><td>${c.files.map((f) => `<code>${escapeHtml(f)}</code>`).join(' → ')}</td></tr>`)
151
+ .join('\n')}</tbody>
152
+ </table>`;
153
+ const filesRows = report.files
154
+ .slice(0, 100)
155
+ .map((f) => `<tr><td><code>${escapeHtml(f.relativePath)}</code></td><td class="right">${f.fanIn}</td><td class="right">${f.fanOut}</td><td class="right">${f.instability.toFixed(3)}</td></tr>`)
156
+ .join('\n');
157
+ const body = `
158
+ <h1>Coupling</h1>
159
+ <h2>Cycles (${report.cycles.length})</h2>
160
+ ${cyclesHtml}
161
+ <h2>Files (top 100 by fan-in)</h2>
162
+ <table>
163
+ <thead><tr><th>File</th><th class="right">Fan-in</th><th class="right">Fan-out</th><th class="right">Instability</th></tr></thead>
164
+ <tbody>${filesRows}</tbody>
165
+ </table>
166
+ `;
167
+ console.log(htmlShell('Coupling', body));
168
+ }
169
+ export function reportReviewHtml(report) {
170
+ if (!report.available) {
171
+ console.log(htmlShell('PR Review — unavailable', `<h1>PR Review</h1><p class="muted">${escapeHtml(report.reason ?? 'Review unavailable.')}</p>`));
172
+ return;
173
+ }
174
+ const verdictClass = `verdict-${report.verdict}`;
175
+ const verdictLabel = report.verdict === 'block' ? '🚫 BLOCK' : report.verdict === 'review' ? '👀 REVIEW' : '✅ OK';
176
+ const filesHtml = report.changedFiles.length === 0
177
+ ? '<p class="muted">No structural changes.</p>'
178
+ : `<table>
179
+ <thead><tr><th>File</th><th>Status</th><th class="right">Risk</th><th class="right">CC</th><th class="right">ΔCC</th></tr></thead>
180
+ <tbody>${report.changedFiles
181
+ .slice(0, 100)
182
+ .map((f) => `<tr>
183
+ <td><code>${escapeHtml(f.relativePath)}</code></td>
184
+ <td class="muted">${f.status}</td>
185
+ <td class="right">${f.riskScore !== null ? f.riskScore.toFixed(1) : '-'}</td>
186
+ <td class="right">${f.cyclomaticComplexity ?? '-'}</td>
187
+ <td class="right">${f.cyclomaticDelta === null ? '-' : signed(f.cyclomaticDelta)}</td>
188
+ </tr>`)
189
+ .join('\n')}</tbody>
190
+ </table>`;
191
+ const cyclesHtml = report.newCycles.length === 0
192
+ ? ''
193
+ : `<h2>New / expanded cycles</h2>
194
+ <ul>${report.newCycles
195
+ .map((c) => `<li><span class="tag tag-${c.classification}">${c.classification}</span> (${c.size} files): ${c.files.map((f) => `<code>${escapeHtml(f)}</code>`).join(' → ')}</li>`)
196
+ .join('\n')}</ul>`;
197
+ const fnsHtml = report.riskyFunctions.length === 0
198
+ ? ''
199
+ : `<h2>Risky functions</h2>
200
+ <table>
201
+ <thead><tr><th>Function</th><th>File</th><th class="right">CC</th><th>Reason</th><th>Δ</th></tr></thead>
202
+ <tbody>${report.riskyFunctions
203
+ .slice(0, 50)
204
+ .map((fn) => `<tr>
205
+ <td><code>${escapeHtml(fn.name)}</code></td>
206
+ <td><code>${escapeHtml(fn.file)}</code>:L${fn.line}</td>
207
+ <td class="right">${fn.cyclomaticComplexity}</td>
208
+ <td class="muted">${fn.reason}</td>
209
+ <td>${fn.baseCc === null ? 'new' : `${fn.baseCc} → ${fn.cyclomaticComplexity}`}</td>
210
+ </tr>`)
211
+ .join('\n')}</tbody>
212
+ </table>`;
213
+ const depsHtml = report.dependencyChanges.length === 0
214
+ ? ''
215
+ : `<h2>Dependency changes</h2>${report.dependencyChanges
216
+ .map((d) => `
217
+ <h3>${escapeHtml(d.manifestFile)}${d.workspace ? ` <span class="muted">(${escapeHtml(d.workspace)})</span>` : ''}</h3>
218
+ <ul>
219
+ ${d.added.map((a) => `<li>➕ <code>${escapeHtml(a.name)}@${escapeHtml(a.version)}</code> <span class="muted">(${a.kind})</span></li>`).join('\n')}
220
+ ${d.removed.map((r) => `<li>➖ <code>${escapeHtml(r.name)}@${escapeHtml(r.version)}</code> <span class="muted">(${r.kind})</span></li>`).join('\n')}
221
+ ${d.bumped.map((b) => `<li>🔄 <code>${escapeHtml(b.name)}</code>: ${escapeHtml(b.from)} → ${escapeHtml(b.to)} <span class="muted">(${b.kind})</span></li>`).join('\n')}
222
+ </ul>`)
223
+ .join('\n')}`;
224
+ const body = `
225
+ <h1>PR Review</h1>
226
+ <div class="card">
227
+ <p class="muted">base <code>${escapeHtml(report.base.ref)}</code> (${report.base.resolvedSha?.slice(0, 7) ?? '?'}) → head <code>${escapeHtml(report.head.ref)}</code> (${report.head.resolvedSha?.slice(0, 7) ?? '?'})</p>
228
+ <p class="${verdictClass}" style="font-size: 1.4rem; margin: 0.5rem 0;">${verdictLabel}</p>
229
+ <ul>${report.summary.map((s) => `<li>${escapeHtml(s)}</li>`).join('\n')}</ul>
230
+ </div>
231
+ <h2>Changed files</h2>
232
+ ${filesHtml}
233
+ ${cyclesHtml}
234
+ ${fnsHtml}
235
+ ${depsHtml}
236
+ `;
237
+ console.log(htmlShell(`PR Review (${verdictLabel.replace(/[^A-Za-z]/g, '').trim()})`, body));
238
+ }
239
+ export function reportImpactHtml(report) {
240
+ if (!report.available) {
241
+ console.log(htmlShell('Impact — unavailable', `<h1>Impact</h1><p class="muted">${escapeHtml(report.reason ?? 'Impact unavailable.')}</p>`));
242
+ return;
243
+ }
244
+ const defs = report.definitionFiles.length === 0
245
+ ? ''
246
+ : `<h2>Defined in</h2><ul>${report.definitionFiles.map((f) => `<li><code>${escapeHtml(f)}</code></li>`).join('\n')}</ul>`;
247
+ const reachable = report.reachable.length === 0
248
+ ? '<p class="muted">No reachable files.</p>'
249
+ : `<table>
250
+ <thead><tr><th class="right">Distance</th><th>File</th></tr></thead>
251
+ <tbody>${report.reachable
252
+ .slice(0, 200)
253
+ .map((n) => `<tr><td class="right">${n.distance}</td><td><code>${escapeHtml(n.file)}</code></td></tr>`)
254
+ .join('\n')}</tbody>
255
+ </table>`;
256
+ const body = `
257
+ <h1>Impact: ${escapeHtml(report.target.kind)} <code>${escapeHtml(report.target.value)}</code></h1>
258
+ <p class="muted">${report.totalReachable} file(s) reachable within distance ${report.maxDistance}${report.truncated ? ' (truncated; more files exist beyond)' : ''}.</p>
259
+ ${defs}
260
+ <h2>Reachable</h2>
261
+ ${reachable}
262
+ `;
263
+ console.log(htmlShell(`Impact: ${report.target.value}`, body));
264
+ }
265
+ // ── helpers ───────────────────────────────────────────────
266
+ function escapeHtml(s) {
267
+ return s
268
+ .replace(/&/g, '&amp;')
269
+ .replace(/</g, '&lt;')
270
+ .replace(/>/g, '&gt;')
271
+ .replace(/"/g, '&quot;')
272
+ .replace(/'/g, '&#39;');
273
+ }
274
+ function severityIcon(s) {
275
+ switch (s) {
276
+ case 'error':
277
+ return '✕';
278
+ case 'warning':
279
+ return '!';
280
+ default:
281
+ return 'i';
282
+ }
283
+ }
284
+ function signed(n) {
285
+ return n >= 0 ? `+${n}` : String(n);
286
+ }
287
+ //# sourceMappingURL=htmlReporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"htmlReporter.js","sourceRoot":"","sources":["../../src/reporters/htmlReporter.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { AnalysisReport, AuditReport, CoverageJoinedReport, CouplingReport, Issue, FileExplanation, FileInspection, ArchitectureLayer, DirectoryNode, DependencyReport, DiffResult, HotspotReport, OutdatedReport, PrDiffReport, UpgradePreview, WorkspaceInfo } from '../types.js';
1
+ import type { AnalysisReport, AuditReport, CoverageJoinedReport, CouplingReport, Issue, FileExplanation, FileInspection, ArchitectureLayer, DirectoryNode, DependencyReport, DiffResult, HotspotReport, OutdatedReport, PrDiffReport, ReviewReport, FixSuggestion, ImpactReport, IssueExplanation, UpgradePreview, WorkspaceInfo } from '../types.js';
2
2
  export declare function reportAnalysisJson(report: AnalysisReport): void;
3
3
  export declare function reportHealthJson(issues: Issue[]): void;
4
4
  export declare function reportCiJson(issues: Issue[], threshold: number): void;
@@ -15,4 +15,13 @@ export declare function reportUpgradeJson(preview: UpgradePreview): void;
15
15
  export declare function reportCoverageJson(report: CoverageJoinedReport): void;
16
16
  export declare function reportCouplingJson(report: CouplingReport): void;
17
17
  export declare function reportPrDiffJson(report: PrDiffReport): void;
18
+ export declare function reportReviewJson(report: ReviewReport): void;
19
+ export declare function reportFixSuggestJson(result: {
20
+ matched: boolean;
21
+ fix?: FixSuggestion;
22
+ reason?: string;
23
+ synthetic?: boolean;
24
+ }): void;
25
+ export declare function reportExplainIssueJson(explanation: IssueExplanation): void;
26
+ export declare function reportImpactJson(report: ImpactReport): void;
18
27
  export declare function reportWorkspacesJson(info: WorkspaceInfo): void;
@@ -71,6 +71,18 @@ export function reportCouplingJson(report) {
71
71
  export function reportPrDiffJson(report) {
72
72
  console.log(JSON.stringify({ prDiff: report }, null, 2));
73
73
  }
74
+ export function reportReviewJson(report) {
75
+ console.log(JSON.stringify({ review: report }, null, 2));
76
+ }
77
+ export function reportFixSuggestJson(result) {
78
+ console.log(JSON.stringify({ fixSuggest: result }, null, 2));
79
+ }
80
+ export function reportExplainIssueJson(explanation) {
81
+ console.log(JSON.stringify({ issueExplanation: explanation }, null, 2));
82
+ }
83
+ export function reportImpactJson(report) {
84
+ console.log(JSON.stringify({ impact: report }, null, 2));
85
+ }
74
86
  export function reportWorkspacesJson(info) {
75
87
  console.log(JSON.stringify({ workspaces: info }, null, 2));
76
88
  }
@@ -1 +1 @@
1
- {"version":3,"file":"jsonReporter.js","sourceRoot":"","sources":["../../src/reporters/jsonReporter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"jsonReporter.js","sourceRoot":"","sources":["../../src/reporters/jsonReporter.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { AnalysisReport, AuditReport, CoverageJoinedReport, CouplingReport, Issue, FileExplanation, FileInspection, ArchitectureLayer, DirectoryNode, DependencyReport, DiffResult, HotspotReport, OutdatedReport, PrDiffReport, UpgradePreview, WorkspaceInfo } from '../types.js';
1
+ import type { AnalysisReport, AuditReport, CoverageJoinedReport, CouplingReport, Issue, FileExplanation, FileInspection, ArchitectureLayer, DirectoryNode, DependencyReport, DiffResult, HotspotReport, OutdatedReport, PrDiffReport, ReviewReport, FixSuggestion, ImpactReport, IssueExplanation, UpgradePreview, WorkspaceInfo } from '../types.js';
2
2
  export declare function reportAnalysisMarkdown(report: AnalysisReport): void;
3
3
  export declare function reportHealthMarkdown(issues: Issue[]): void;
4
4
  export declare function reportCiMarkdown(issues: Issue[], threshold: number): void;
@@ -11,6 +11,15 @@ export declare function reportFileMarkdown(insp: FileInspection): void;
11
11
  export declare function reportHotspotsMarkdown(report: HotspotReport): void;
12
12
  export declare function reportCouplingMarkdown(report: CouplingReport): void;
13
13
  export declare function reportPrDiffMarkdown(report: PrDiffReport): void;
14
+ export declare function reportImpactMarkdown(report: ImpactReport): void;
15
+ export declare function reportFixSuggestMarkdown(result: {
16
+ matched: boolean;
17
+ fix?: FixSuggestion;
18
+ reason?: string;
19
+ synthetic?: boolean;
20
+ }): void;
21
+ export declare function reportExplainIssueMarkdown(e: IssueExplanation): void;
22
+ export declare function reportReviewMarkdown(report: ReviewReport): void;
14
23
  export declare function reportWorkspacesMarkdown(info: WorkspaceInfo): void;
15
24
  export declare function reportOutdatedMarkdown(report: OutdatedReport): void;
16
25
  export declare function reportAuditMarkdown(report: AuditReport): void;
@@ -56,6 +56,9 @@ export function reportHealthMarkdown(issues) {
56
56
  for (const issue of issues) {
57
57
  const icon = issue.severity === 'error' ? '❌' : issue.severity === 'warning' ? '⚠️' : 'ℹ️';
58
58
  lines.push(`- ${icon} **${issue.title}** - ${issue.description}`);
59
+ if (issue.suggestedAction) {
60
+ lines.push(` - **Action:** ${issue.suggestedAction.summary} _(\`projscan fix-suggest ${issue.id}\`)_`);
61
+ }
59
62
  }
60
63
  }
61
64
  console.log(lines.join('\n'));
@@ -258,6 +261,18 @@ export function reportFileMarkdown(insp) {
258
261
  lines.push(`- \`${exp.name}\` (${exp.type})`);
259
262
  }
260
263
  }
264
+ if (insp.functions && insp.functions.length > 0) {
265
+ lines.push('', '## Functions (top by CC)', '');
266
+ lines.push('| CC | Fan-in | Name | Lines |');
267
+ lines.push('| ---: | ---: | --- | --- |');
268
+ for (const fn of insp.functions.slice(0, 20)) {
269
+ const fi = typeof fn.fanIn === 'number' ? String(fn.fanIn) : '-';
270
+ lines.push(`| ${fn.cyclomaticComplexity} | ${fi} | \`${fn.name}\` | L${fn.line}-${fn.endLine} |`);
271
+ }
272
+ if (insp.functions.length > 20) {
273
+ lines.push('', `_... and ${insp.functions.length - 20} more_`);
274
+ }
275
+ }
261
276
  console.log(lines.join('\n'));
262
277
  }
263
278
  export function reportHotspotsMarkdown(report) {
@@ -363,6 +378,164 @@ export function reportPrDiffMarkdown(report) {
363
378
  function signed(n) {
364
379
  return n >= 0 ? `+${n}` : String(n);
365
380
  }
381
+ export function reportImpactMarkdown(report) {
382
+ const lines = [`# Impact: ${report.target.kind} \`${report.target.value}\``, ''];
383
+ if (!report.available) {
384
+ lines.push(`> ${report.reason ?? 'Impact unavailable.'}`);
385
+ console.log(lines.join('\n'));
386
+ return;
387
+ }
388
+ if (report.target.kind === 'symbol') {
389
+ lines.push(`_definitions: ${report.definitionFiles.length} · direct callers: ${report.directCallers.length}_`, '');
390
+ if (report.definitionFiles.length > 0) {
391
+ lines.push('## Defined in', '');
392
+ for (const f of report.definitionFiles)
393
+ lines.push(`- \`${f}\``);
394
+ lines.push('');
395
+ }
396
+ }
397
+ lines.push(`**${report.totalReachable}** file(s) reachable within distance ${report.maxDistance}${report.truncated ? ' (truncated; more files exist beyond)' : ''}.`, '');
398
+ if (report.reachable.length === 0) {
399
+ lines.push('_No reachable files._');
400
+ console.log(lines.join('\n'));
401
+ return;
402
+ }
403
+ lines.push('| Distance | File |', '| ---: | --- |');
404
+ for (const n of report.reachable.slice(0, 200)) {
405
+ lines.push(`| ${n.distance} | \`${n.file}\` |`);
406
+ }
407
+ if (report.reachable.length > 200) {
408
+ lines.push('', `_... and ${report.reachable.length - 200} more_`);
409
+ }
410
+ console.log(lines.join('\n'));
411
+ }
412
+ export function reportFixSuggestMarkdown(result) {
413
+ const lines = ['# Fix Suggestion', ''];
414
+ if (!result.matched || !result.fix) {
415
+ lines.push(`> ${result.reason ?? 'No suggestion available.'}`);
416
+ console.log(lines.join('\n'));
417
+ return;
418
+ }
419
+ const fix = result.fix;
420
+ lines.push(`**${fix.headline}**`, '');
421
+ lines.push(`_severity: ${fix.severity} · category: ${fix.category} · issue: \`${fix.issueId}\`${result.synthetic ? ' (synthetic)' : ''}_`, '');
422
+ lines.push('## Why', '', fix.why, '');
423
+ if (fix.where.length > 0) {
424
+ lines.push('## Where', '');
425
+ for (const w of fix.where) {
426
+ const loc = w.line ? `${w.file}:${w.line}` : w.file;
427
+ lines.push(`- \`${loc}\``);
428
+ }
429
+ lines.push('');
430
+ }
431
+ lines.push('## Action', '', fix.instruction, '');
432
+ if (fix.suggestedTest)
433
+ lines.push('## Verify', '', fix.suggestedTest, '');
434
+ if (fix.relatedFiles && fix.relatedFiles.length > 0) {
435
+ lines.push('## Related files', '');
436
+ for (const f of fix.relatedFiles)
437
+ lines.push(`- \`${f}\``);
438
+ lines.push('');
439
+ }
440
+ if (fix.references && fix.references.length > 0) {
441
+ lines.push('## References', '');
442
+ for (const r of fix.references)
443
+ lines.push(`- ${r}`);
444
+ lines.push('');
445
+ }
446
+ console.log(lines.join('\n'));
447
+ }
448
+ export function reportExplainIssueMarkdown(e) {
449
+ const lines = [`# Issue: ${e.title}`, ''];
450
+ lines.push(`_severity: ${e.severity} · category: ${e.category} · id: \`${e.issueId}\`_`, '');
451
+ lines.push(`**${e.headline}**`, '');
452
+ if (e.excerpt) {
453
+ lines.push(`## Code (\`${e.excerpt.file}\` L${e.excerpt.startLine}-${e.excerpt.endLine})`, '', '```');
454
+ for (const l of e.excerpt.lines)
455
+ lines.push(l);
456
+ lines.push('```', '');
457
+ }
458
+ if (e.relatedIssues.length > 0) {
459
+ lines.push('## Related issues in the same area', '');
460
+ for (const r of e.relatedIssues)
461
+ lines.push(`- \`${r.id}\`: ${r.title}`);
462
+ lines.push('');
463
+ }
464
+ if (e.similarFixes.length > 0) {
465
+ lines.push('## Past commits referencing this rule', '');
466
+ for (const f of e.similarFixes)
467
+ lines.push(`- ${f.sha.slice(0, 7)} (${f.date}) ${f.subject}`);
468
+ lines.push('');
469
+ }
470
+ if (e.fix) {
471
+ lines.push('## Suggested action', '', e.fix.instruction, '');
472
+ if (e.fix.suggestedTest)
473
+ lines.push('**Verify:** ' + e.fix.suggestedTest, '');
474
+ }
475
+ console.log(lines.join('\n'));
476
+ }
477
+ export function reportReviewMarkdown(report) {
478
+ const lines = ['# PR Review', ''];
479
+ if (!report.available) {
480
+ lines.push(`> ${report.reason ?? 'Review unavailable.'}`);
481
+ console.log(lines.join('\n'));
482
+ return;
483
+ }
484
+ const verdictBadge = report.verdict === 'block' ? '🚫 BLOCK' : report.verdict === 'review' ? '👀 REVIEW' : '✅ OK';
485
+ lines.push(`_base **${report.base.ref}** (${report.base.resolvedSha?.slice(0, 7) ?? '?'}) → head **${report.head.ref}** (${report.head.resolvedSha?.slice(0, 7) ?? '?'})_`, '', `**Verdict:** ${verdictBadge}`, '');
486
+ if (report.summary.length > 0) {
487
+ for (const s of report.summary)
488
+ lines.push(`- ${s}`);
489
+ lines.push('');
490
+ }
491
+ if (report.changedFiles.length > 0) {
492
+ lines.push('## Changed files', '');
493
+ lines.push('| File | Status | Risk | CC | ΔCC |');
494
+ lines.push('| --- | --- | ---: | ---: | ---: |');
495
+ for (const f of report.changedFiles.slice(0, 50)) {
496
+ const risk = f.riskScore !== null ? f.riskScore.toFixed(1) : '-';
497
+ const cc = f.cyclomaticComplexity !== null ? String(f.cyclomaticComplexity) : '-';
498
+ const dcc = f.cyclomaticDelta === null ? '-' : signed(f.cyclomaticDelta);
499
+ lines.push(`| \`${f.relativePath}\` | ${f.status} | ${risk} | ${cc} | ${dcc} |`);
500
+ }
501
+ if (report.changedFiles.length > 50) {
502
+ lines.push('', `_... and ${report.changedFiles.length - 50} more files_`);
503
+ }
504
+ lines.push('');
505
+ }
506
+ if (report.newCycles.length > 0) {
507
+ lines.push('## New / expanded import cycles', '');
508
+ for (const c of report.newCycles) {
509
+ lines.push(`- **${c.classification}** (${c.size} files): ${c.files.map((f) => `\`${f}\``).join(' → ')}`);
510
+ }
511
+ lines.push('');
512
+ }
513
+ if (report.riskyFunctions.length > 0) {
514
+ lines.push('## Risky functions', '');
515
+ lines.push('| Function | File | CC | Reason | Δ from base |');
516
+ lines.push('| --- | --- | ---: | --- | --- |');
517
+ for (const fn of report.riskyFunctions.slice(0, 30)) {
518
+ const baseInfo = fn.baseCc === null ? 'new' : `${fn.baseCc} → ${fn.cyclomaticComplexity}`;
519
+ lines.push(`| \`${fn.name}\` | \`${fn.file}\`:L${fn.line} | ${fn.cyclomaticComplexity} | ${fn.reason} | ${baseInfo} |`);
520
+ }
521
+ lines.push('');
522
+ }
523
+ if (report.dependencyChanges.length > 0) {
524
+ lines.push('## Dependency changes', '');
525
+ for (const d of report.dependencyChanges) {
526
+ const wsLabel = d.workspace ? ` (${d.workspace})` : '';
527
+ lines.push(`### \`${d.manifestFile}\`${wsLabel}`, '');
528
+ for (const a of d.added)
529
+ lines.push(`- ➕ \`${a.name}@${a.version}\` (${a.kind})`);
530
+ for (const r of d.removed)
531
+ lines.push(`- ➖ \`${r.name}@${r.version}\` (${r.kind})`);
532
+ for (const b of d.bumped)
533
+ lines.push(`- 🔄 \`${b.name}\`: \`${b.from}\` → \`${b.to}\` (${b.kind})`);
534
+ lines.push('');
535
+ }
536
+ }
537
+ console.log(lines.join('\n'));
538
+ }
366
539
  export function reportWorkspacesMarkdown(info) {
367
540
  const lines = ['# Workspaces', ''];
368
541
  lines.push(`_kind: **${info.kind}**${info.source ? ` · source: ${info.source}` : ''} · ${info.packages.length} package(s)_`, '');