projscan 0.2.0 → 0.3.1
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.
- package/README.md +84 -20
- package/dist/analyzers/architectureCheck.js +1 -0
- package/dist/analyzers/architectureCheck.js.map +1 -1
- package/dist/analyzers/securityCheck.js +16 -1
- package/dist/analyzers/securityCheck.js.map +1 -1
- package/dist/cli/index.js +107 -26
- package/dist/cli/index.js.map +1 -1
- package/dist/core/repositoryScanner.d.ts +4 -1
- package/dist/core/repositoryScanner.js +6 -3
- package/dist/core/repositoryScanner.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/reporters/sarifReporter.d.ts +61 -0
- package/dist/reporters/sarifReporter.js +102 -0
- package/dist/reporters/sarifReporter.js.map +1 -0
- package/dist/types.d.ts +24 -1
- package/dist/utils/banner.js +16 -7
- package/dist/utils/banner.js.map +1 -1
- package/dist/utils/changedFiles.d.ts +14 -0
- package/dist/utils/changedFiles.js +113 -0
- package/dist/utils/changedFiles.js.map +1 -0
- package/dist/utils/config.d.ts +8 -0
- package/dist/utils/config.js +117 -0
- package/dist/utils/config.js.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -60,16 +60,18 @@ npx projscan
|
|
|
60
60
|
Run inside any repository:
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
projscan
|
|
64
|
-
projscan doctor
|
|
65
|
-
projscan hotspots
|
|
66
|
-
projscan file <path>
|
|
67
|
-
projscan fix
|
|
68
|
-
projscan ci
|
|
69
|
-
projscan
|
|
70
|
-
projscan
|
|
71
|
-
projscan
|
|
72
|
-
projscan
|
|
63
|
+
projscan # Full project analysis
|
|
64
|
+
projscan doctor # Health check
|
|
65
|
+
projscan hotspots # Rank files by risk (churn × complexity × issues × ownership)
|
|
66
|
+
projscan file <path> # Drill into a file — purpose, risk, ownership, issues
|
|
67
|
+
projscan fix # Auto-fix detected issues
|
|
68
|
+
projscan ci # CI health gate (exits 1 on low score)
|
|
69
|
+
projscan ci --changed-only # Gate only on this PR's diff
|
|
70
|
+
projscan ci --format sarif # SARIF 2.1.0 for GitHub Code Scanning
|
|
71
|
+
projscan diff # Compare health + hotspot trends against a baseline
|
|
72
|
+
projscan diagram # Architecture visualization
|
|
73
|
+
projscan structure # Directory tree
|
|
74
|
+
projscan mcp # Run as an MCP server for AI coding agents
|
|
73
75
|
```
|
|
74
76
|
|
|
75
77
|
<img src="docs/npx%20projscan%20--help.png" alt="npx projscan --help" width="700">
|
|
@@ -85,7 +87,7 @@ For a comprehensive walkthrough, see the **[Full Guide](docs/GUIDE.md)**.
|
|
|
85
87
|
| `projscan hotspots` | Rank files by risk — churn × complexity × issues × ownership |
|
|
86
88
|
| `projscan file <path>` | Drill into a file — purpose, risk, ownership, related issues |
|
|
87
89
|
| `projscan fix` | Auto-fix issues (ESLint, Prettier, Vitest, .editorconfig) |
|
|
88
|
-
| `projscan ci` | CI
|
|
90
|
+
| `projscan ci` | CI health gate — SARIF output, `--changed-only` PR-diff mode, exits 1 if score below threshold |
|
|
89
91
|
| `projscan diff` | Compare current health **and hotspot trends** against a baseline |
|
|
90
92
|
| `projscan explain <file>` | Explain a file's purpose, imports, exports, and issues |
|
|
91
93
|
| `projscan diagram` | ASCII architecture diagram of your project |
|
|
@@ -139,15 +141,19 @@ All commands support `--format` for different output targets:
|
|
|
139
141
|
```bash
|
|
140
142
|
projscan analyze --format json # Machine-readable JSON
|
|
141
143
|
projscan doctor --format markdown # Markdown for docs/PRs
|
|
144
|
+
projscan ci --format sarif # SARIF 2.1.0 for GitHub Code Scanning
|
|
142
145
|
```
|
|
143
146
|
|
|
144
|
-
Formats: `console` (default), `json`, `markdown`
|
|
147
|
+
Formats: `console` (default), `json`, `markdown`, `sarif`
|
|
145
148
|
|
|
146
149
|
### Options
|
|
147
150
|
|
|
148
151
|
| Flag | Description |
|
|
149
152
|
|------|-------------|
|
|
150
|
-
| `--format <type>` | Output format: console, json, markdown |
|
|
153
|
+
| `--format <type>` | Output format: console, json, markdown, sarif |
|
|
154
|
+
| `--config <path>` | Path to a `.projscanrc` config file |
|
|
155
|
+
| `--changed-only` | Scope to files changed vs base ref (ci/analyze/doctor) |
|
|
156
|
+
| `--base-ref <ref>` | Git base ref for `--changed-only` (default: origin/main) |
|
|
151
157
|
| `--verbose` | Enable debug output |
|
|
152
158
|
| `--quiet` | Suppress non-essential output |
|
|
153
159
|
| `-V, --version` | Show version |
|
|
@@ -204,21 +210,79 @@ This outputs a [shields.io](https://shields.io) badge URL and markdown snippet y
|
|
|
204
210
|
Use `projscan ci` to gate your pipelines:
|
|
205
211
|
|
|
206
212
|
```bash
|
|
207
|
-
projscan ci --min-score 70
|
|
208
|
-
projscan ci --min-score 80 --format json
|
|
213
|
+
projscan ci --min-score 70 # Exits 1 if score < 70
|
|
214
|
+
projscan ci --min-score 80 --format json # JSON output for parsing
|
|
215
|
+
projscan ci --changed-only # Gate only on this PR's diff
|
|
216
|
+
projscan ci --format sarif > projscan.sarif # SARIF for Code Scanning
|
|
209
217
|
```
|
|
210
218
|
|
|
211
219
|
<img src="docs/npx%20projscan%20ci%20--min-score%2070.png" alt="npx projscan ci --min-score 70" width="700">
|
|
212
220
|
|
|
213
|
-
### GitHub
|
|
221
|
+
### GitHub Action (recommended)
|
|
222
|
+
|
|
223
|
+
projscan ships a first-party GitHub Action that installs, runs, and uploads SARIF to **GitHub Code Scanning** in one step:
|
|
224
|
+
|
|
225
|
+
```yaml
|
|
226
|
+
# .github/workflows/projscan.yml
|
|
227
|
+
name: ProjScan
|
|
228
|
+
on:
|
|
229
|
+
push: { branches: [main] }
|
|
230
|
+
pull_request: { branches: [main] }
|
|
231
|
+
|
|
232
|
+
permissions:
|
|
233
|
+
contents: read
|
|
234
|
+
security-events: write # required for SARIF upload
|
|
235
|
+
|
|
236
|
+
jobs:
|
|
237
|
+
scan:
|
|
238
|
+
runs-on: ubuntu-latest
|
|
239
|
+
steps:
|
|
240
|
+
- uses: actions/checkout@v4
|
|
241
|
+
with: { fetch-depth: 0 } # needed for --changed-only
|
|
242
|
+
- uses: actions/setup-node@v4
|
|
243
|
+
with: { node-version: 20 }
|
|
244
|
+
- uses: abhiyoheswaran1/projscan@v0.3.0
|
|
245
|
+
with:
|
|
246
|
+
min-score: '70'
|
|
247
|
+
changed-only: 'true'
|
|
248
|
+
```
|
|
214
249
|
|
|
215
|
-
|
|
250
|
+
Inputs: `min-score`, `changed-only`, `base-ref`, `config`, `sarif-file`, `upload-sarif`, `working-directory`, `version`. Outputs: `score`, `grade`.
|
|
216
251
|
|
|
217
|
-
|
|
218
|
-
|
|
252
|
+
Findings appear in the **Security → Code scanning** tab, annotated on files and lines. PRs get inline annotations on changed lines.
|
|
253
|
+
|
|
254
|
+
### Plain workflow (no SARIF upload)
|
|
255
|
+
|
|
256
|
+
If you'd rather not upload SARIF, [`.github/projscan-ci.yml`](.github/projscan-ci.yml) is a drop-in workflow that runs projscan and posts a markdown health report as a PR comment.
|
|
257
|
+
|
|
258
|
+
## Configuration (`.projscanrc`)
|
|
259
|
+
|
|
260
|
+
Drop a `.projscanrc.json` at your repo root to set defaults — CLI flags always win over config. A `"projscan"` key in `package.json` and plain `.projscanrc` are also supported.
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"minScore": 80,
|
|
265
|
+
"baseRef": "origin/main",
|
|
266
|
+
"ignore": ["**/fixtures/**", "**/generated/**"],
|
|
267
|
+
"disableRules": ["missing-editorconfig", "large-*"],
|
|
268
|
+
"severityOverrides": {
|
|
269
|
+
"missing-prettier": "info"
|
|
270
|
+
},
|
|
271
|
+
"hotspots": {
|
|
272
|
+
"limit": 20,
|
|
273
|
+
"since": "6 months ago"
|
|
274
|
+
}
|
|
275
|
+
}
|
|
219
276
|
```
|
|
220
277
|
|
|
221
|
-
|
|
278
|
+
Fields:
|
|
279
|
+
|
|
280
|
+
- `minScore` — default `ci` threshold (0–100)
|
|
281
|
+
- `baseRef` — default base ref for `--changed-only`
|
|
282
|
+
- `ignore` — extra glob patterns added to the built-in ignore list
|
|
283
|
+
- `disableRules` — silence rules by id; supports wildcard `prefix-*`
|
|
284
|
+
- `severityOverrides` — remap a rule's severity (`info` / `warning` / `error`)
|
|
285
|
+
- `hotspots.limit` / `hotspots.since` — defaults for the `hotspots` command
|
|
222
286
|
|
|
223
287
|
## Tracking Health Over Time
|
|
224
288
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"architectureCheck.js","sourceRoot":"","sources":["../../src/analyzers/architectureCheck.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAC/B,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AAE3D,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,QAAgB,EAAE,KAAkB;IAC9D,MAAM,MAAM,GAAY,EAAE,CAAC;IAE3B,sCAAsC;IACtC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAkB,CAAC;IAChD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC;gBAC3B,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,aAAa,EAAE,CAAC;QACzC,IAAI,KAAK,GAAG,mBAAmB,EAAE,CAAC;YAChC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YACnC,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,SAAS,OAAO,MAAM;gBAC1B,KAAK,EAAE,SAAS,OAAO,gBAAgB,KAAK,SAAS;gBACrD,WAAW,EAAE,OAAO,GAAG,wBAAwB,KAAK,kFAAkF;gBACtI,QAAQ,EAAE,SAAS;gBACnB,QAAQ,EAAE,cAAc;gBACxB,YAAY,EAAE,KAAK;
|
|
1
|
+
{"version":3,"file":"architectureCheck.js","sourceRoot":"","sources":["../../src/analyzers/architectureCheck.ts"],"names":[],"mappings":"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"}
|
|
@@ -38,6 +38,7 @@ export async function check(rootPath, files) {
|
|
|
38
38
|
severity: 'warning',
|
|
39
39
|
category: 'security',
|
|
40
40
|
fixAvailable: false,
|
|
41
|
+
locations: [{ file: file.relativePath, line: 1 }],
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
44
|
}
|
|
@@ -55,6 +56,7 @@ export async function check(rootPath, files) {
|
|
|
55
56
|
severity: 'error',
|
|
56
57
|
category: 'security',
|
|
57
58
|
fixAvailable: false,
|
|
59
|
+
locations: [{ file: file.relativePath, line: 1 }],
|
|
58
60
|
});
|
|
59
61
|
}
|
|
60
62
|
}
|
|
@@ -101,7 +103,9 @@ async function scanFileForSecrets(file) {
|
|
|
101
103
|
try {
|
|
102
104
|
const content = await fs.readFile(file.absolutePath, 'utf-8');
|
|
103
105
|
for (const { name, pattern } of SECRET_PATTERNS) {
|
|
104
|
-
|
|
106
|
+
const match = pattern.exec(content);
|
|
107
|
+
if (match) {
|
|
108
|
+
const line = lineNumberFor(content, match.index);
|
|
105
109
|
return {
|
|
106
110
|
id: 'hardcoded-secret',
|
|
107
111
|
title: `Potential ${name} detected in ${file.relativePath}`,
|
|
@@ -109,6 +113,7 @@ async function scanFileForSecrets(file) {
|
|
|
109
113
|
severity: 'error',
|
|
110
114
|
category: 'security',
|
|
111
115
|
fixAvailable: false,
|
|
116
|
+
locations: [{ file: file.relativePath, line }],
|
|
112
117
|
};
|
|
113
118
|
}
|
|
114
119
|
}
|
|
@@ -118,4 +123,14 @@ async function scanFileForSecrets(file) {
|
|
|
118
123
|
}
|
|
119
124
|
return null;
|
|
120
125
|
}
|
|
126
|
+
function lineNumberFor(content, index) {
|
|
127
|
+
if (index <= 0)
|
|
128
|
+
return 1;
|
|
129
|
+
let line = 1;
|
|
130
|
+
for (let i = 0; i < index && i < content.length; i++) {
|
|
131
|
+
if (content.charCodeAt(i) === 10)
|
|
132
|
+
line++;
|
|
133
|
+
}
|
|
134
|
+
return line;
|
|
135
|
+
}
|
|
121
136
|
//# sourceMappingURL=securityCheck.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"securityCheck.js","sourceRoot":"","sources":["../../src/analyzers/securityCheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,MAAM,gBAAgB,GAAG,gBAAgB,CAAC;AAC1C,MAAM,iBAAiB,GAAG,CAAC,UAAU,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;AAE/D,MAAM,iBAAiB,GAAG,CAAC,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AACzE,MAAM,sBAAsB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAEhE,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACnC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAC5C,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM;IAC3C,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM;IACzC,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO;IAC9C,MAAM,EAAE,MAAM;CACf,CAAC,CAAC;AAEH,MAAM,aAAa,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,SAAS;AAE3C,MAAM,eAAe,GAAwC;IAC3D,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,kBAAkB,EAAE;IACvD,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,0BAA0B,EAAE;IAC7D,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,0BAA0B,EAAE;IAC5D,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,+CAA+C,EAAE;IACjF;QACE,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,2EAA2E;KACrF;CACF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,QAAgB,EAAE,KAAkB;IAC9D,MAAM,MAAM,GAAY,EAAE,CAAC;IAE3B,oCAAoC;IACpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAClD,IAAI,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,0BAA0B;YAC5D,IAAI,MAAM,IAAI,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAAE,SAAS;YAE3D,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,oBAAoB;gBACxB,KAAK,EAAE,+BAA+B,IAAI,CAAC,YAAY,EAAE;gBACzD,WAAW,EAAE,aAAa,IAAI,CAAC,YAAY,8CAA8C;gBACzF,QAAQ,EAAE,SAAS;gBACnB,QAAQ,EAAE,UAAU;gBACpB,YAAY,EAAE,KAAK;
|
|
1
|
+
{"version":3,"file":"securityCheck.js","sourceRoot":"","sources":["../../src/analyzers/securityCheck.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/index.js
CHANGED
|
@@ -21,29 +21,67 @@ import { setLogLevel } from '../utils/logger.js';
|
|
|
21
21
|
import { calculateScore, badgeUrl, badgeMarkdown } from '../utils/scoreCalculator.js';
|
|
22
22
|
import { showBanner, showCompactBanner, showHelp } from '../utils/banner.js';
|
|
23
23
|
import { saveBaseline, loadBaseline, computeDiff } from '../utils/baseline.js';
|
|
24
|
+
import { loadConfig, applyConfigToIssues } from '../utils/config.js';
|
|
25
|
+
import { getChangedFiles } from '../utils/changedFiles.js';
|
|
24
26
|
import { runMcpServer } from '../mcp/server.js';
|
|
25
27
|
import { reportAnalysis, reportHealth, reportCi, reportDiff, reportDetectedIssues, reportExplanation, reportDiagram, reportStructure, reportDependencies, reportHotspots, reportFileInspection, } from '../reporters/consoleReporter.js';
|
|
26
28
|
import { reportAnalysisJson, reportHealthJson, reportCiJson, reportDiffJson, reportExplanationJson, reportDiagramJson, reportStructureJson, reportDependenciesJson, reportHotspotsJson, reportFileJson, } from '../reporters/jsonReporter.js';
|
|
27
29
|
import { reportAnalysisMarkdown, reportHealthMarkdown, reportCiMarkdown, reportDiffMarkdown, reportExplanationMarkdown, reportDiagramMarkdown, reportStructureMarkdown, reportDependenciesMarkdown, reportHotspotsMarkdown, reportFileMarkdown, } from '../reporters/markdownReporter.js';
|
|
30
|
+
import { reportAnalysisSarif, reportHealthSarif, reportCiSarif, } from '../reporters/sarifReporter.js';
|
|
28
31
|
// ── CLI Setup ─────────────────────────────────────────────
|
|
29
32
|
const program = new Command();
|
|
30
33
|
program
|
|
31
34
|
.name('projscan')
|
|
32
35
|
.description('Instant codebase insights — doctor, x-ray, and architecture map for any repository')
|
|
33
36
|
.version(pkg.version)
|
|
34
|
-
.option('--format <type>', 'output format: console, json, markdown', 'console')
|
|
37
|
+
.option('--format <type>', 'output format: console, json, markdown, sarif', 'console')
|
|
38
|
+
.option('--config <path>', 'path to .projscanrc config file')
|
|
35
39
|
.option('--verbose', 'enable verbose output')
|
|
36
40
|
.option('--quiet', 'suppress non-essential output');
|
|
37
41
|
function getFormat() {
|
|
38
42
|
const opts = program.opts();
|
|
39
43
|
const f = opts.format;
|
|
40
|
-
if (f === 'json' || f === 'markdown')
|
|
44
|
+
if (f === 'json' || f === 'markdown' || f === 'sarif')
|
|
41
45
|
return f;
|
|
42
46
|
return 'console';
|
|
43
47
|
}
|
|
44
48
|
function getRootPath() {
|
|
45
49
|
return process.cwd();
|
|
46
50
|
}
|
|
51
|
+
async function loadProjectConfig() {
|
|
52
|
+
const opts = program.opts();
|
|
53
|
+
const explicit = typeof opts.config === 'string' ? opts.config : undefined;
|
|
54
|
+
try {
|
|
55
|
+
const { config, source } = await loadConfig(getRootPath(), explicit);
|
|
56
|
+
if (source && !opts.quiet && getFormat() === 'console') {
|
|
57
|
+
console.error(chalk.dim(` [config: ${path.relative(getRootPath(), source) || source}]`));
|
|
58
|
+
}
|
|
59
|
+
return config;
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
63
|
+
console.error(chalk.red(` Config error: ${msg}`));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function filterIssuesByChangedFiles(issues, rootPath, baseRef) {
|
|
68
|
+
const result = await getChangedFiles(rootPath, baseRef);
|
|
69
|
+
if (!result.available) {
|
|
70
|
+
if (getFormat() === 'console' && !program.opts().quiet) {
|
|
71
|
+
console.error(chalk.yellow(` [--changed-only: ${result.reason ?? 'unavailable'} — reporting all issues]`));
|
|
72
|
+
}
|
|
73
|
+
return issues;
|
|
74
|
+
}
|
|
75
|
+
if (getFormat() === 'console' && !program.opts().quiet) {
|
|
76
|
+
console.error(chalk.dim(` [--changed-only: base=${result.baseRef}, ${result.files.length} file(s)]`));
|
|
77
|
+
}
|
|
78
|
+
const set = new Set(result.files);
|
|
79
|
+
return issues.filter((issue) => {
|
|
80
|
+
if (!issue.locations || issue.locations.length === 0)
|
|
81
|
+
return false;
|
|
82
|
+
return issue.locations.some((loc) => set.has(loc.file));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
47
85
|
function setupLogLevel() {
|
|
48
86
|
const opts = program.opts();
|
|
49
87
|
if (opts.verbose)
|
|
@@ -77,14 +115,17 @@ function maybeCompactBanner() {
|
|
|
77
115
|
program
|
|
78
116
|
.command('analyze', { isDefault: true })
|
|
79
117
|
.description('Analyze repository and show project report')
|
|
80
|
-
.
|
|
118
|
+
.option('--changed-only', 'only report issues on files changed vs base ref')
|
|
119
|
+
.option('--base-ref <ref>', 'git base ref for --changed-only (default: origin/main)')
|
|
120
|
+
.action(async (cmdOpts) => {
|
|
81
121
|
setupLogLevel();
|
|
82
122
|
maybeBanner();
|
|
83
123
|
const rootPath = getRootPath();
|
|
84
124
|
const format = getFormat();
|
|
125
|
+
const config = await loadProjectConfig();
|
|
85
126
|
const spinner = format === 'console' ? ora('Scanning repository...').start() : null;
|
|
86
127
|
try {
|
|
87
|
-
const scan = await scanRepository(rootPath);
|
|
128
|
+
const scan = await scanRepository(rootPath, { ignore: config.ignore });
|
|
88
129
|
if (spinner)
|
|
89
130
|
spinner.text = 'Detecting languages...';
|
|
90
131
|
const languages = detectLanguages(scan.files);
|
|
@@ -96,7 +137,11 @@ program
|
|
|
96
137
|
const dependencies = await analyzeDependencies(rootPath);
|
|
97
138
|
if (spinner)
|
|
98
139
|
spinner.text = 'Checking for issues...';
|
|
99
|
-
|
|
140
|
+
let issues = await collectIssues(rootPath, scan.files);
|
|
141
|
+
issues = applyConfigToIssues(issues, config);
|
|
142
|
+
if (cmdOpts.changedOnly) {
|
|
143
|
+
issues = await filterIssuesByChangedFiles(issues, rootPath, cmdOpts.baseRef ?? config.baseRef);
|
|
144
|
+
}
|
|
100
145
|
if (spinner)
|
|
101
146
|
spinner.stop();
|
|
102
147
|
const report = {
|
|
@@ -116,6 +161,9 @@ program
|
|
|
116
161
|
case 'markdown':
|
|
117
162
|
reportAnalysisMarkdown(report);
|
|
118
163
|
break;
|
|
164
|
+
case 'sarif':
|
|
165
|
+
reportAnalysisSarif(issues, pkg.version);
|
|
166
|
+
break;
|
|
119
167
|
default:
|
|
120
168
|
reportAnalysis(report);
|
|
121
169
|
}
|
|
@@ -131,15 +179,22 @@ program
|
|
|
131
179
|
program
|
|
132
180
|
.command('doctor')
|
|
133
181
|
.description('Evaluate project health and detect issues')
|
|
134
|
-
.
|
|
182
|
+
.option('--changed-only', 'only report issues on files changed vs base ref')
|
|
183
|
+
.option('--base-ref <ref>', 'git base ref for --changed-only (default: origin/main)')
|
|
184
|
+
.action(async (cmdOpts) => {
|
|
135
185
|
setupLogLevel();
|
|
136
186
|
maybeCompactBanner();
|
|
137
187
|
const rootPath = getRootPath();
|
|
138
188
|
const format = getFormat();
|
|
189
|
+
const config = await loadProjectConfig();
|
|
139
190
|
const spinner = format === 'console' ? ora('Running health checks...').start() : null;
|
|
140
191
|
try {
|
|
141
|
-
const scan = await scanRepository(rootPath);
|
|
142
|
-
|
|
192
|
+
const scan = await scanRepository(rootPath, { ignore: config.ignore });
|
|
193
|
+
let issues = await collectIssues(rootPath, scan.files);
|
|
194
|
+
issues = applyConfigToIssues(issues, config);
|
|
195
|
+
if (cmdOpts.changedOnly) {
|
|
196
|
+
issues = await filterIssuesByChangedFiles(issues, rootPath, cmdOpts.baseRef ?? config.baseRef);
|
|
197
|
+
}
|
|
143
198
|
if (spinner)
|
|
144
199
|
spinner.stop();
|
|
145
200
|
switch (format) {
|
|
@@ -149,6 +204,9 @@ program
|
|
|
149
204
|
case 'markdown':
|
|
150
205
|
reportHealthMarkdown(issues);
|
|
151
206
|
break;
|
|
207
|
+
case 'sarif':
|
|
208
|
+
reportHealthSarif(issues, pkg.version);
|
|
209
|
+
break;
|
|
152
210
|
default:
|
|
153
211
|
reportHealth(issues, scan.scanDurationMs);
|
|
154
212
|
}
|
|
@@ -164,16 +222,24 @@ program
|
|
|
164
222
|
program
|
|
165
223
|
.command('ci')
|
|
166
224
|
.description('Run health check for CI pipelines (exits 1 if score below threshold)')
|
|
167
|
-
.option('--min-score <score>', 'minimum passing score (0-100)'
|
|
225
|
+
.option('--min-score <score>', 'minimum passing score (0-100)')
|
|
226
|
+
.option('--changed-only', 'gate only on issues in files changed vs base ref')
|
|
227
|
+
.option('--base-ref <ref>', 'git base ref for --changed-only (default: origin/main)')
|
|
168
228
|
.action(async (cmdOpts) => {
|
|
169
229
|
setupLogLevel();
|
|
170
230
|
maybeCompactBanner();
|
|
171
231
|
const rootPath = getRootPath();
|
|
172
232
|
const format = getFormat();
|
|
233
|
+
const config = await loadProjectConfig();
|
|
173
234
|
try {
|
|
174
|
-
const scan = await scanRepository(rootPath);
|
|
175
|
-
|
|
176
|
-
|
|
235
|
+
const scan = await scanRepository(rootPath, { ignore: config.ignore });
|
|
236
|
+
let issues = await collectIssues(rootPath, scan.files);
|
|
237
|
+
issues = applyConfigToIssues(issues, config);
|
|
238
|
+
if (cmdOpts.changedOnly) {
|
|
239
|
+
issues = await filterIssuesByChangedFiles(issues, rootPath, cmdOpts.baseRef ?? config.baseRef);
|
|
240
|
+
}
|
|
241
|
+
const rawThreshold = cmdOpts.minScore ?? config.minScore ?? 70;
|
|
242
|
+
const threshold = Math.max(0, Math.min(100, typeof rawThreshold === 'string' ? parseInt(rawThreshold, 10) || 70 : rawThreshold));
|
|
177
243
|
const { score } = calculateScore(issues);
|
|
178
244
|
switch (format) {
|
|
179
245
|
case 'json':
|
|
@@ -182,6 +248,9 @@ program
|
|
|
182
248
|
case 'markdown':
|
|
183
249
|
reportCiMarkdown(issues, threshold);
|
|
184
250
|
break;
|
|
251
|
+
case 'sarif':
|
|
252
|
+
reportCiSarif(issues, pkg.version);
|
|
253
|
+
break;
|
|
185
254
|
default:
|
|
186
255
|
reportCi(issues, threshold);
|
|
187
256
|
}
|
|
@@ -205,9 +274,11 @@ program
|
|
|
205
274
|
maybeCompactBanner();
|
|
206
275
|
const rootPath = getRootPath();
|
|
207
276
|
const format = getFormat();
|
|
277
|
+
const config = await loadProjectConfig();
|
|
208
278
|
try {
|
|
209
|
-
const scan = await scanRepository(rootPath);
|
|
210
|
-
|
|
279
|
+
const scan = await scanRepository(rootPath, { ignore: config.ignore });
|
|
280
|
+
let issues = await collectIssues(rootPath, scan.files);
|
|
281
|
+
issues = applyConfigToIssues(issues, config);
|
|
211
282
|
const hotspotReport = await analyzeHotspots(rootPath, scan.files, issues, { limit: 20 });
|
|
212
283
|
if (cmdOpts.saveBaseline) {
|
|
213
284
|
const filePath = await saveBaseline(rootPath, issues, hotspotReport);
|
|
@@ -259,9 +330,11 @@ program
|
|
|
259
330
|
maybeCompactBanner();
|
|
260
331
|
const rootPath = getRootPath();
|
|
261
332
|
const spinner = ora('Detecting issues...').start();
|
|
333
|
+
const config = await loadProjectConfig();
|
|
262
334
|
try {
|
|
263
|
-
const scan = await scanRepository(rootPath);
|
|
264
|
-
|
|
335
|
+
const scan = await scanRepository(rootPath, { ignore: config.ignore });
|
|
336
|
+
let issues = await collectIssues(rootPath, scan.files);
|
|
337
|
+
issues = applyConfigToIssues(issues, config);
|
|
265
338
|
const fixes = getAllAvailableFixes(issues);
|
|
266
339
|
spinner.stop();
|
|
267
340
|
if (fixes.length === 0) {
|
|
@@ -387,9 +460,10 @@ program
|
|
|
387
460
|
maybeCompactBanner();
|
|
388
461
|
const rootPath = getRootPath();
|
|
389
462
|
const format = getFormat();
|
|
463
|
+
const config = await loadProjectConfig();
|
|
390
464
|
const spinner = format === 'console' ? ora('Analyzing architecture...').start() : null;
|
|
391
465
|
try {
|
|
392
|
-
const scan = await scanRepository(rootPath);
|
|
466
|
+
const scan = await scanRepository(rootPath, { ignore: config.ignore });
|
|
393
467
|
const frameworks = await detectFrameworks(rootPath, scan.files);
|
|
394
468
|
const layers = buildArchitectureLayers(scan.files, frameworks.frameworks.map((f) => f.name));
|
|
395
469
|
if (spinner)
|
|
@@ -421,9 +495,10 @@ program
|
|
|
421
495
|
maybeCompactBanner();
|
|
422
496
|
const rootPath = getRootPath();
|
|
423
497
|
const format = getFormat();
|
|
498
|
+
const config = await loadProjectConfig();
|
|
424
499
|
const spinner = format === 'console' ? ora('Scanning...').start() : null;
|
|
425
500
|
try {
|
|
426
|
-
const scan = await scanRepository(rootPath);
|
|
501
|
+
const scan = await scanRepository(rootPath, { ignore: config.ignore });
|
|
427
502
|
if (spinner)
|
|
428
503
|
spinner.stop();
|
|
429
504
|
switch (format) {
|
|
@@ -484,20 +559,24 @@ program
|
|
|
484
559
|
program
|
|
485
560
|
.command('hotspots')
|
|
486
561
|
.description('Rank files by risk (git churn × complexity × open issues)')
|
|
487
|
-
.option('--limit <n>', 'number of hotspots to show'
|
|
488
|
-
.option('--since <when>', 'git history window (e.g. "6 months ago", "2024-01-01")'
|
|
562
|
+
.option('--limit <n>', 'number of hotspots to show')
|
|
563
|
+
.option('--since <when>', 'git history window (e.g. "6 months ago", "2024-01-01")')
|
|
489
564
|
.action(async (cmdOpts) => {
|
|
490
565
|
setupLogLevel();
|
|
491
566
|
maybeCompactBanner();
|
|
492
567
|
const rootPath = getRootPath();
|
|
493
568
|
const format = getFormat();
|
|
569
|
+
const config = await loadProjectConfig();
|
|
494
570
|
const spinner = format === 'console' ? ora('Analyzing hotspots...').start() : null;
|
|
495
571
|
try {
|
|
496
|
-
const scan = await scanRepository(rootPath);
|
|
497
|
-
|
|
498
|
-
|
|
572
|
+
const scan = await scanRepository(rootPath, { ignore: config.ignore });
|
|
573
|
+
let issues = await collectIssues(rootPath, scan.files);
|
|
574
|
+
issues = applyConfigToIssues(issues, config);
|
|
575
|
+
const limitRaw = cmdOpts.limit ?? config.hotspots?.limit ?? 10;
|
|
576
|
+
const limit = Math.max(1, Math.min(100, typeof limitRaw === 'string' ? parseInt(limitRaw, 10) || 10 : limitRaw));
|
|
577
|
+
const since = cmdOpts.since ?? config.hotspots?.since ?? '12 months ago';
|
|
499
578
|
const report = await analyzeHotspots(rootPath, scan.files, issues, {
|
|
500
|
-
since
|
|
579
|
+
since,
|
|
501
580
|
limit,
|
|
502
581
|
});
|
|
503
582
|
if (spinner)
|
|
@@ -545,9 +624,11 @@ program
|
|
|
545
624
|
maybeCompactBanner();
|
|
546
625
|
const rootPath = getRootPath();
|
|
547
626
|
const spinner = ora('Calculating health score...').start();
|
|
627
|
+
const config = await loadProjectConfig();
|
|
548
628
|
try {
|
|
549
|
-
const scan = await scanRepository(rootPath);
|
|
550
|
-
|
|
629
|
+
const scan = await scanRepository(rootPath, { ignore: config.ignore });
|
|
630
|
+
let issues = await collectIssues(rootPath, scan.files);
|
|
631
|
+
issues = applyConfigToIssues(issues, config);
|
|
551
632
|
const { score, grade } = calculateScore(issues);
|
|
552
633
|
spinner.stop();
|
|
553
634
|
const gradeColor = grade === 'A' || grade === 'B' ? chalk.green : grade === 'C' ? chalk.yellow : chalk.red;
|