projscan 0.9.0 → 0.9.2

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 (53) hide show
  1. package/README.md +72 -72
  2. package/dist/analyzers/deadCodeCheck.d.ts +2 -2
  3. package/dist/analyzers/deadCodeCheck.js +4 -4
  4. package/dist/analyzers/unusedDependencyCheck.js +1 -1
  5. package/dist/cli/index.js +11 -11
  6. package/dist/core/ast.d.ts +1 -1
  7. package/dist/core/ast.js +2 -2
  8. package/dist/core/auditRunner.d.ts +1 -1
  9. package/dist/core/auditRunner.js +6 -6
  10. package/dist/core/coverageJoin.d.ts +1 -1
  11. package/dist/core/coverageJoin.js +1 -1
  12. package/dist/core/coverageParser.js +3 -3
  13. package/dist/core/dependencyAnalyzer.js +6 -6
  14. package/dist/core/embeddings.d.ts +1 -1
  15. package/dist/core/embeddings.js +2 -2
  16. package/dist/core/fileInspector.js +6 -6
  17. package/dist/core/hotspotAnalyzer.js +2 -2
  18. package/dist/core/importGraph.d.ts +1 -1
  19. package/dist/core/importGraph.js +1 -1
  20. package/dist/core/indexCache.d.ts +2 -2
  21. package/dist/core/indexCache.js +4 -4
  22. package/dist/core/outdatedDetector.d.ts +1 -1
  23. package/dist/core/outdatedDetector.js +1 -1
  24. package/dist/core/searchIndex.js +2 -2
  25. package/dist/core/semanticSearch.d.ts +1 -1
  26. package/dist/core/semanticSearch.js +2 -2
  27. package/dist/core/upgradePreview.d.ts +12 -0
  28. package/dist/core/upgradePreview.js +55 -3
  29. package/dist/core/upgradePreview.js.map +1 -1
  30. package/dist/fixes/prettierFix.js +1 -1
  31. package/dist/fixes/testFix.js +1 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.js +1 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/mcp/chunker.d.ts +1 -1
  36. package/dist/mcp/chunker.js +1 -1
  37. package/dist/mcp/pagination.d.ts +2 -2
  38. package/dist/mcp/pagination.js +2 -2
  39. package/dist/mcp/progress.d.ts +1 -1
  40. package/dist/mcp/prompts.js +3 -3
  41. package/dist/mcp/server.js +1 -1
  42. package/dist/mcp/tokenBudget.d.ts +1 -1
  43. package/dist/mcp/tokenBudget.js +2 -2
  44. package/dist/mcp/tools.js +8 -8
  45. package/dist/reporters/consoleReporter.js +11 -11
  46. package/dist/reporters/markdownReporter.js +14 -14
  47. package/dist/reporters/sarifReporter.js +1 -1
  48. package/dist/utils/banner.d.ts +3 -3
  49. package/dist/utils/banner.js +9 -9
  50. package/dist/utils/config.js +1 -1
  51. package/dist/utils/packageJsonLocator.d.ts +1 -1
  52. package/dist/utils/packageJsonLocator.js +1 -1
  53. package/package.json +2 -2
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![license](https://img.shields.io/npm/l/projscan.svg)](https://github.com/abhiyoheswaran1/projscan/blob/main/LICENSE)
7
7
  [![node](https://img.shields.io/node/v/projscan.svg)](https://nodejs.org)
8
8
 
9
- **Agent-first code intelligence.** An MCP server that lets AI coding agents (Claude Code, Cursor, Windsurf) query your codebase with a CLI for humans on the side.
9
+ **Agent-first code intelligence.** An MCP server that lets AI coding agents (Claude Code, Cursor, Windsurf) query your codebase - with a CLI for humans on the side.
10
10
 
11
11
  [AI Agent Quick Start](#ai-agent-integration-mcp) · [CLI Quick Start](#quick-start) · [Commands](#commands) · [Full Guide](docs/GUIDE.md) · [Roadmap](docs/ROADMAP.md)
12
12
 
@@ -18,9 +18,9 @@
18
18
 
19
19
  ## Why?
20
20
 
21
- AI coding agents are becoming the primary interface to code. Today, when you ask your agent *"which files implement auth?"* or *"what breaks if I bump React from 18 to 19?"* it either guesses from names, or it shells out to grep and reads raw output not built for it.
21
+ AI coding agents are becoming the primary interface to code. Today, when you ask your agent *"which files implement auth?"* or *"what breaks if I bump React from 18 to 19?"* - it either guesses from names, or it shells out to grep and reads raw output not built for it.
22
22
 
23
- **projscan is the first code-intelligence tool built for agents, not for humans.** Your agent gets a fast, AST-accurate, context-budget-aware view of your codebase through 13 structured MCP tools. It can query the import graph, find symbol definitions, preview upgrades, rank hotspots without loading the file tree into its context.
23
+ **projscan is the first code-intelligence tool built for agents, not for humans.** Your agent gets a fast, AST-accurate, context-budget-aware view of your codebase through 13 structured MCP tools. It can query the import graph, find symbol definitions, preview upgrades, rank hotspots - without loading the file tree into its context.
24
24
 
25
25
  Humans get the same thing through the CLI.
26
26
 
@@ -61,7 +61,7 @@ projscan # Full project analysis
61
61
  projscan doctor # Health check
62
62
  projscan hotspots # Rank files by risk (churn × complexity × issues × ownership)
63
63
  projscan search <query> # BM25-ranked search (content + symbols + path)
64
- projscan file <path> # Drill into a file purpose, risk, ownership, issues
64
+ projscan file <path> # Drill into a file - purpose, risk, ownership, issues
65
65
  projscan fix # Auto-fix detected issues
66
66
  projscan ci # CI health gate (exits 1 on low score)
67
67
  projscan ci --changed-only # Gate only on this PR's diff
@@ -69,7 +69,7 @@ projscan ci --format sarif # SARIF 2.1.0 for GitHub Code Scanning
69
69
  projscan outdated # Declared-vs-installed drift (offline)
70
70
  projscan audit # npm audit, normalized + SARIF-ready
71
71
  projscan upgrade <pkg> # Preview upgrade impact (local CHANGELOG + importers)
72
- projscan coverage # Coverage × hotspots scariest untested files
72
+ projscan coverage # Coverage × hotspots - scariest untested files
73
73
  projscan diff # Compare health + hotspot trends against a baseline
74
74
  projscan diagram # Architecture visualization
75
75
  projscan structure # Directory tree
@@ -84,22 +84,22 @@ For a comprehensive walkthrough, see the **[Full Guide](docs/GUIDE.md)**.
84
84
 
85
85
  | Command | Description |
86
86
  |---------|-------------|
87
- | `projscan analyze` | Full analysis languages, frameworks, dependencies, issues |
88
- | `projscan doctor` | Health check missing tooling, architecture smells, security risks |
89
- | `projscan hotspots` | Rank files by risk churn × complexity × issues × ownership |
90
- | `projscan search <query>` | **BM25-ranked search** content + symbols + path, with excerpts |
91
- | `projscan file <path>` | Drill into a file purpose, risk, ownership, related issues |
87
+ | `projscan analyze` | Full analysis - languages, frameworks, dependencies, issues |
88
+ | `projscan doctor` | Health check - missing tooling, architecture smells, security risks |
89
+ | `projscan hotspots` | Rank files by risk - churn × complexity × issues × ownership |
90
+ | `projscan search <query>` | **BM25-ranked search** - content + symbols + path, with excerpts |
91
+ | `projscan file <path>` | Drill into a file - purpose, risk, ownership, related issues |
92
92
  | `projscan fix` | Auto-fix issues (ESLint, Prettier, Vitest, .editorconfig) |
93
- | `projscan ci` | CI health gate SARIF output, `--changed-only` PR-diff mode, exits 1 if score below threshold |
93
+ | `projscan ci` | CI health gate - SARIF output, `--changed-only` PR-diff mode, exits 1 if score below threshold |
94
94
  | `projscan diff` | Compare current health **and hotspot trends** against a baseline |
95
95
  | `projscan explain <file>` | Explain a file's purpose, imports, exports, and issues |
96
96
  | `projscan diagram` | ASCII architecture diagram of your project |
97
97
  | `projscan structure` | Directory tree with file counts |
98
- | `projscan dependencies` | Dependency analysis counts, risks, recommendations |
98
+ | `projscan dependencies` | Dependency analysis - counts, risks, recommendations |
99
99
  | `projscan outdated` | Declared-vs-installed drift check (offline) |
100
- | `projscan audit` | `npm audit`-powered vulnerability report SARIF-ready for Code Scanning |
101
- | `projscan upgrade <pkg>` | Preview upgrade impact local CHANGELOG + importer list, offline |
102
- | `projscan coverage` | **Coverage × hotspots rank the scariest untested files** |
100
+ | `projscan audit` | `npm audit`-powered vulnerability report - SARIF-ready for Code Scanning |
101
+ | `projscan upgrade <pkg>` | Preview upgrade impact - local CHANGELOG + importer list, offline |
102
+ | `projscan coverage` | **Coverage × hotspots - rank the scariest untested files** |
103
103
  | `projscan badge` | Generate a health score badge for your README |
104
104
  | `projscan mcp` | Run as an MCP server for AI coding agents (Claude Code, Cursor, …) |
105
105
 
@@ -112,31 +112,31 @@ projscan --help
112
112
  ### Command Screenshots
113
113
 
114
114
  <details>
115
- <summary><strong>projscan structure</strong> Directory tree with file counts</summary>
115
+ <summary><strong>projscan structure</strong> - Directory tree with file counts</summary>
116
116
 
117
117
  <img src="docs/npx%20projscan%20structure.png" alt="npx projscan structure" width="700">
118
118
  </details>
119
119
 
120
120
  <details>
121
- <summary><strong>projscan diagram</strong> Architecture visualization</summary>
121
+ <summary><strong>projscan diagram</strong> - Architecture visualization</summary>
122
122
 
123
123
  <img src="docs/npx%20projscan%20diagram.png" alt="npx projscan diagram" width="700">
124
124
  </details>
125
125
 
126
126
  <details>
127
- <summary><strong>projscan dependencies</strong> Dependency analysis</summary>
127
+ <summary><strong>projscan dependencies</strong> - Dependency analysis</summary>
128
128
 
129
129
  <img src="docs/npx%20projscan%20dependencies.png" alt="npx projscan dependencies" width="700">
130
130
  </details>
131
131
 
132
132
  <details>
133
- <summary><strong>projscan explain</strong> File explanation</summary>
133
+ <summary><strong>projscan explain</strong> - File explanation</summary>
134
134
 
135
135
  <img src="docs/npx%20projscan%20explain.png" alt="npx projscan explain" width="700">
136
136
  </details>
137
137
 
138
138
  <details>
139
- <summary><strong>projscan badge</strong> Health badge generation</summary>
139
+ <summary><strong>projscan badge</strong> - Health badge generation</summary>
140
140
 
141
141
  <img src="docs/npx%20projscan%20badge.png" alt="npx projscan badge" width="700">
142
142
  </details>
@@ -172,11 +172,11 @@ Every `projscan doctor` run calculates a health score (0–100) and letter grade
172
172
 
173
173
  | Grade | Score | Meaning |
174
174
  |-------|-------|---------|
175
- | A | 90–100 | Excellent project follows best practices |
176
- | B | 80–89 | Good minor improvements possible |
177
- | C | 70–79 | Fair several issues to address |
178
- | D | 60–69 | Poor significant issues found |
179
- | F | < 60 | Critical major issues need attention |
175
+ | A | 90–100 | Excellent - project follows best practices |
176
+ | B | 80–89 | Good - minor improvements possible |
177
+ | C | 70–79 | Fair - several issues to address |
178
+ | D | 60–69 | Poor - significant issues found |
179
+ | F | < 60 | Critical - major issues need attention |
180
180
 
181
181
  Generate a badge for your README:
182
182
 
@@ -202,15 +202,15 @@ This outputs a [shields.io](https://shields.io) badge URL and markdown snippet y
202
202
  - Excessive, deprecated, or wildcard-versioned dependencies
203
203
  - Missing lockfile
204
204
  - Committed `.env` files and private keys (security)
205
- - Hardcoded secrets AWS keys, GitHub tokens, Slack tokens, generic passwords (security)
205
+ - Hardcoded secrets - AWS keys, GitHub tokens, Slack tokens, generic passwords (security)
206
206
  - Missing `.env` in `.gitignore` (security)
207
207
 
208
208
  ## Performance
209
209
 
210
210
  - **5,000 files** analyzed in under 1.5 seconds
211
211
  - **20,000 files** analyzed in under 3 seconds
212
- - **Zero network requests** everything runs locally
213
- - **4 runtime dependencies** minimal footprint
212
+ - **Zero network requests** - everything runs locally
213
+ - **4 runtime dependencies** - minimal footprint
214
214
 
215
215
  ## CI/CD Integration
216
216
 
@@ -264,7 +264,7 @@ If you'd rather not upload SARIF, [`.github/projscan-ci.yml`](.github/projscan-c
264
264
 
265
265
  ## Configuration (`.projscanrc`)
266
266
 
267
- 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.
267
+ 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.
268
268
 
269
269
  ```json
270
270
  {
@@ -284,12 +284,12 @@ Drop a `.projscanrc.json` at your repo root to set defaults — CLI flags always
284
284
 
285
285
  Fields:
286
286
 
287
- - `minScore` default `ci` threshold (0–100)
288
- - `baseRef` default base ref for `--changed-only`
289
- - `ignore` extra glob patterns added to the built-in ignore list
290
- - `disableRules` silence rules by id; supports wildcard `prefix-*`
291
- - `severityOverrides` remap a rule's severity (`info` / `warning` / `error`)
292
- - `hotspots.limit` / `hotspots.since` defaults for the `hotspots` command
287
+ - `minScore` - default `ci` threshold (0–100)
288
+ - `baseRef` - default base ref for `--changed-only`
289
+ - `ignore` - extra glob patterns added to the built-in ignore list
290
+ - `disableRules` - silence rules by id; supports wildcard `prefix-*`
291
+ - `severityOverrides` - remap a rule's severity (`info` / `warning` / `error`)
292
+ - `hotspots.limit` / `hotspots.since` - defaults for the `hotspots` command
293
293
 
294
294
  ## Tracking Health Over Time
295
295
 
@@ -304,9 +304,9 @@ projscan diff --format markdown # Markdown diff for PRs
304
304
 
305
305
  <img src="docs/npx%20projscan%20diff%20--save-baseline.png" alt="npx projscan diff --save-baseline" width="700">
306
306
 
307
- ## Hotspots Where to Fix First
307
+ ## Hotspots - Where to Fix First
308
308
 
309
- A flat health score doesn't tell you what to do. **`projscan hotspots`** combines `git log` churn, file complexity, open issues, recency, and **ownership** into a single risk score per file so you know where refactoring or review will actually pay off.
309
+ A flat health score doesn't tell you what to do. **`projscan hotspots`** combines `git log` churn, file complexity, open issues, recency, and **ownership** into a single risk score per file - so you know where refactoring or review will actually pay off.
310
310
 
311
311
  ```bash
312
312
  projscan hotspots # Top 10 hotspots
@@ -324,7 +324,7 @@ Hotspot ranking follows the classic Feathers "churn × complexity" heuristic wit
324
324
  projscan file src/cli/index.ts
325
325
  ```
326
326
 
327
- Combines the file's purpose, imports, exports, hotspot risk, ownership, and every open issue that references it the natural follow-up to `projscan hotspots`.
327
+ Combines the file's purpose, imports, exports, hotspot risk, ownership, and every open issue that references it - the natural follow-up to `projscan hotspots`.
328
328
 
329
329
  ### Track Trends Over Time
330
330
 
@@ -338,7 +338,7 @@ The baseline file now captures top hotspots too, so `diff` surfaces files that a
338
338
 
339
339
  ## Dependency Health
340
340
 
341
- projscan ships three focused commands for keeping your dependency graph healthy all **offline** by default, no registry calls.
341
+ projscan ships three focused commands for keeping your dependency graph healthy - all **offline** by default, no registry calls.
342
342
 
343
343
  ```bash
344
344
  projscan outdated # Which declared deps drift from what's installed?
@@ -351,9 +351,9 @@ projscan upgrade chalk --format markdown # Paste-ready review comment
351
351
 
352
352
  ### What each one tells you
353
353
 
354
- - **`outdated`** reads `package.json` and `node_modules/<pkg>/package.json` to classify drift (`major` / `minor` / `patch` / `same` / `unknown`). No network.
355
- - **`audit`** wraps `npm audit --json`, normalizes the output, and emits SARIF with per-finding rules anchored to `package.json`. Graceful fallback message for yarn/pnpm projects.
356
- - **`upgrade <pkg>`** reads `node_modules/<pkg>/CHANGELOG.md`, slices the section between your installed version and the previous one, flags `BREAKING CHANGE` / `deprecated` / `removed support` markers, and lists every file in your repo that imports the package. All offline.
354
+ - **`outdated`** - reads `package.json` and `node_modules/<pkg>/package.json` to classify drift (`major` / `minor` / `patch` / `same` / `unknown`). No network.
355
+ - **`audit`** - wraps `npm audit --json`, normalizes the output, and emits SARIF with per-finding rules anchored to `package.json`. Graceful fallback message for yarn/pnpm projects.
356
+ - **`upgrade <pkg>`** - reads `node_modules/<pkg>/CHANGELOG.md`, slices the section between your installed version and the previous one, flags `BREAKING CHANGE` / `deprecated` / `removed support` markers, and lists every file in your repo that imports the package. All offline.
357
357
 
358
358
  ### Unused dependencies (automatic in `doctor`)
359
359
 
@@ -361,9 +361,9 @@ projscan upgrade chalk --format markdown # Paste-ready review comment
361
361
 
362
362
  Implicit-use packages (typescript, eslint/prettier plugins, `@types/*`, and anything invoked from a `package.json` script) are allowlisted. Override via `.projscanrc` → `disableRules` if projscan flags something that is used but not imported.
363
363
 
364
- ## Coverage × Hotspots Scariest Untested Files
364
+ ## Coverage × Hotspots - Scariest Untested Files
365
365
 
366
- `projscan coverage` joins your test coverage with the hotspot ranking. A file with high churn and low coverage is where a bug is most likely to bite you so that's where you want tests first.
366
+ `projscan coverage` joins your test coverage with the hotspot ranking. A file with high churn and low coverage is where a bug is most likely to bite you - so that's where you want tests first.
367
367
 
368
368
  ```bash
369
369
  projscan coverage # Top 30 scariest untested files
@@ -371,23 +371,23 @@ projscan coverage --format markdown # Paste into a tech-debt ticket
371
371
  projscan coverage --format json # Machine-readable for dashboards
372
372
  ```
373
373
 
374
- **How it decides "scariest":** `priority = riskScore × (0.3 + 0.7 × uncoveredFraction)` so a file with 50 risk and 10% coverage outranks a file with 50 risk and 95% coverage.
374
+ **How it decides "scariest":** `priority = riskScore × (0.3 + 0.7 × uncoveredFraction)` - so a file with 50 risk and 10% coverage outranks a file with 50 risk and 95% coverage.
375
375
 
376
376
  **Which coverage files are supported:**
377
377
 
378
- - `coverage/lcov.info` (lcov Vitest, Jest, c8)
378
+ - `coverage/lcov.info` (lcov - Vitest, Jest, c8)
379
379
  - `coverage/coverage-final.json` (Istanbul per-file detail)
380
380
  - `coverage/coverage-summary.json` (Istanbul summary)
381
381
 
382
- Coverage is also automatically joined into `projscan hotspots` when one of those files exists no flag needed. Uncovered churning files get a score bump and a `low coverage (X%)` reason tag.
382
+ Coverage is also automatically joined into `projscan hotspots` when one of those files exists - no flag needed. Uncovered churning files get a score bump and a `low coverage (X%)` reason tag.
383
383
 
384
384
  ### Dead-code detection (automatic in `doctor`)
385
385
 
386
- `projscan doctor` now flags source files whose exports nothing imports dead code left over from refactors or utilities that were never wired up. Respects `package.json` public entry points (`main`, `exports`, `bin`, `types`), skips test files and barrel (`index`) files.
386
+ `projscan doctor` now flags source files whose exports nothing imports - dead code left over from refactors or utilities that were never wired up. Respects `package.json` public entry points (`main`, `exports`, `bin`, `types`), skips test files and barrel (`index`) files.
387
387
 
388
388
  ## AI Agent Integration (MCP)
389
389
 
390
- **This is the primary way to use projscan.** `projscan mcp` starts an [MCP](https://modelcontextprotocol.io) server over stdio so AI coding agents can query your codebase with real structural accuracy not regex, not grep.
390
+ **This is the primary way to use projscan.** `projscan mcp` starts an [MCP](https://modelcontextprotocol.io) server over stdio so AI coding agents can query your codebase with real structural accuracy - not regex, not grep.
391
391
 
392
392
  ### Claude Code
393
393
 
@@ -419,28 +419,28 @@ claude mcp add projscan -- npx projscan mcp
419
419
 
420
420
  ### The 13 MCP tools
421
421
 
422
- **Structural (0.6.0 new, agent-native):**
423
- - **`projscan_graph`** query the AST-based code graph. Directions: `imports`, `exports`, `importers`, `symbol_defs`, `package_importers`. Millisecond responses on a warm cache.
424
- - **`projscan_search`** fast search across `symbols` (exported names), `files` (path substring), or `content` (source substring with line + excerpt). Replaces the temptation to shell out to grep.
422
+ **Structural (0.6.0 - new, agent-native):**
423
+ - **`projscan_graph`** - query the AST-based code graph. Directions: `imports`, `exports`, `importers`, `symbol_defs`, `package_importers`. Millisecond responses on a warm cache.
424
+ - **`projscan_search`** - fast search across `symbols` (exported names), `files` (path substring), or `content` (source substring with line + excerpt). Replaces the temptation to shell out to grep.
425
425
 
426
426
  **Analysis:**
427
- - `projscan_analyze` full project report
428
- - `projscan_doctor` health score + issues
429
- - `projscan_hotspots` risk-ranked files (churn × complexity × issues × ownership × coverage)
430
- - `projscan_file` per-file risk + ownership + related issues
431
- - `projscan_explain` per-file purpose, imports, exports, smells
432
- - `projscan_structure` directory tree
433
- - `projscan_coverage` scariest untested files (coverage × hotspots)
427
+ - `projscan_analyze` - full project report
428
+ - `projscan_doctor` - health score + issues
429
+ - `projscan_hotspots` - risk-ranked files (churn × complexity × issues × ownership × coverage)
430
+ - `projscan_file` - per-file risk + ownership + related issues
431
+ - `projscan_explain` - per-file purpose, imports, exports, smells
432
+ - `projscan_structure` - directory tree
433
+ - `projscan_coverage` - scariest untested files (coverage × hotspots)
434
434
 
435
435
  **Dependencies:**
436
- - `projscan_dependencies` declared deps, risks
437
- - `projscan_outdated` declared-vs-installed drift (offline)
438
- - `projscan_audit` normalized `npm audit`
439
- - `projscan_upgrade` upgrade preview (CHANGELOG + importers, offline)
436
+ - `projscan_dependencies` - declared deps, risks
437
+ - `projscan_outdated` - declared-vs-installed drift (offline)
438
+ - `projscan_audit` - normalized `npm audit`
439
+ - `projscan_upgrade` - upgrade preview (CHANGELOG + importers, offline)
440
440
 
441
441
  ### Context-window budgeting
442
442
 
443
- **Every MCP tool accepts an optional `max_tokens` argument.** Set it and projscan serializes the result, and if over budget truncates the largest array field record-by-record until it fits. Responses include a `_budget` sidecar when truncated so your agent knows it got a partial view.
443
+ **Every MCP tool accepts an optional `max_tokens` argument.** Set it and projscan serializes the result, and - if over budget - truncates the largest array field record-by-record until it fits. Responses include a `_budget` sidecar when truncated so your agent knows it got a partial view.
444
444
 
445
445
  ```json
446
446
  { "name": "projscan_hotspots", "arguments": { "limit": 100, "max_tokens": 800 } }
@@ -448,7 +448,7 @@ claude mcp add projscan -- npx projscan mcp
448
448
 
449
449
  ### Semantic search (0.9.0+, opt-in)
450
450
 
451
- projscan ships with BM25-ranked lexical search by default. To unlock **true semantic search** embeddings over file content so queries like *"which file implements auth"* hit files that don't literally contain the word "auth" install the optional peer:
451
+ projscan ships with BM25-ranked lexical search by default. To unlock **true semantic search** - embeddings over file content so queries like *"which file implements auth"* hit files that don't literally contain the word "auth" - install the optional peer:
452
452
 
453
453
  ```bash
454
454
  npm install @xenova/transformers
@@ -461,11 +461,11 @@ Or via the MCP tool:
461
461
  ```
462
462
 
463
463
  Modes on `projscan_search`:
464
- - `lexical` (default) BM25 over content + symbol + path boosts. No peer needed.
465
- - `semantic` cosine similarity on `Xenova/all-MiniLM-L6-v2` embeddings. Requires peer.
466
- - `hybrid` both, fused via Reciprocal Rank Fusion. Requires peer.
464
+ - `lexical` (default) - BM25 over content + symbol + path boosts. No peer needed.
465
+ - `semantic` - cosine similarity on `Xenova/all-MiniLM-L6-v2` embeddings. Requires peer.
466
+ - `hybrid` - both, fused via Reciprocal Rank Fusion. Requires peer.
467
467
 
468
- Semantic embeddings are cached at `.projscan-cache/embeddings.bin` keyed by `(model, mtime, content hash)` invalidates automatically on file change. All offline after the first-run model download (~25MB).
468
+ Semantic embeddings are cached at `.projscan-cache/embeddings.bin` keyed by `(model, mtime, content hash)` - invalidates automatically on file change. All offline after the first-run model download (~25MB).
469
469
 
470
470
  ### Pagination, progress, and streaming (0.8.0+)
471
471
 
@@ -475,15 +475,15 @@ Large responses can be walked incrementally:
475
475
  - **Progress notifications**: set `_meta.progressToken` on the tool-call request. The server emits `notifications/progress` at coarse milestones (scanning → analyzing → ranking → done) so your agent can display progress or cancel.
476
476
  - **Response chunking**: set `stream: true` in arguments to split large arrays into multiple `content` blocks (header + N chunks of ~20 records each).
477
477
 
478
- All opt-in default behavior is unchanged.
478
+ All opt-in - default behavior is unchanged.
479
479
 
480
480
  ### Incremental index cache
481
481
 
482
482
  projscan caches parsed ASTs at `.projscan-cache/graph.json` (auto-gitignored). First run populates it; subsequent runs re-parse only files whose `mtime` changed. Agent queries on a warm cache are milliseconds, not seconds.
483
483
 
484
484
  ### Prompts (2, parameterized with live project data)
485
- - `prioritize_refactoring` ranked plan grounded in current hotspots
486
- - `investigate_file` senior-engineer brief for a specific file
485
+ - `prioritize_refactoring` - ranked plan grounded in current hotspots
486
+ - `investigate_file` - senior-engineer brief for a specific file
487
487
 
488
488
  ### Resources (3, readable on demand)
489
489
  - `projscan://health` · `projscan://hotspots` · `projscan://structure`
@@ -6,12 +6,12 @@ import type { FileEntry, Issue } from '../types.js';
6
6
  *
7
7
  * Does NOT flag:
8
8
  * - files listed as the package's public entry points (main, exports, types, bin)
9
- * - default exports (too many false positives framework conventions)
9
+ * - default exports (too many false positives - framework conventions)
10
10
  * - test files (they're not supposed to export)
11
11
  * - index files (barrels re-export for public use)
12
12
  *
13
13
  * False-positive guard: if a file is the target of at least one import, we treat
14
- * all its exports as "possibly used" the regex-based graph can't tell which
14
+ * all its exports as "possibly used" - the regex-based graph can't tell which
15
15
  * named export is imported via `import { ... } from './barrel'`. This keeps
16
16
  * noise low at the cost of missing some dead exports that live in used files.
17
17
  */
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import { buildImportGraph } from '../core/importGraph.js';
4
4
  import { extractExports } from '../core/fileInspector.js';
5
5
  const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts']);
6
- // Never flag these they're public API by definition
6
+ // Never flag these - they're public API by definition
7
7
  const PUBLIC_PATH_PREFIXES = ['src/index', 'index.'];
8
8
  /**
9
9
  * Flag exports that are never imported anywhere in the project. This catches:
@@ -12,12 +12,12 @@ const PUBLIC_PATH_PREFIXES = ['src/index', 'index.'];
12
12
  *
13
13
  * Does NOT flag:
14
14
  * - files listed as the package's public entry points (main, exports, types, bin)
15
- * - default exports (too many false positives framework conventions)
15
+ * - default exports (too many false positives - framework conventions)
16
16
  * - test files (they're not supposed to export)
17
17
  * - index files (barrels re-export for public use)
18
18
  *
19
19
  * False-positive guard: if a file is the target of at least one import, we treat
20
- * all its exports as "possibly used" the regex-based graph can't tell which
20
+ * all its exports as "possibly used" - the regex-based graph can't tell which
21
21
  * named export is imported via `import { ... } from './barrel'`. This keeps
22
22
  * noise low at the cost of missing some dead exports that live in used files.
23
23
  */
@@ -143,7 +143,7 @@ async function loadPublicEntries(rootPath) {
143
143
  collectExports(pkg.exports, entries);
144
144
  }
145
145
  catch {
146
- // package.json missing/unreadable don't guard, every file is a candidate
146
+ // package.json missing/unreadable - don't guard, every file is a candidate
147
147
  }
148
148
  return entries;
149
149
  }
@@ -101,7 +101,7 @@ export async function check(rootPath, files) {
101
101
  continue;
102
102
  if (scriptUsedBinaries.has(name))
103
103
  continue;
104
- // skip scoped bin lookups (e.g., "npx some-tool") covered by scriptUsedBinaries
104
+ // skip scoped bin lookups (e.g., "npx some-tool") - covered by scriptUsedBinaries
105
105
  const isDev = name in devDependencies;
106
106
  const line = locations?.lineOfDependency.get(name);
107
107
  unused.push({
package/dist/cli/index.js CHANGED
@@ -42,7 +42,7 @@ import { reportAnalysisSarif, reportHealthSarif, reportCiSarif, issuesToSarif, }
42
42
  const program = new Command();
43
43
  program
44
44
  .name('projscan')
45
- .description('Instant codebase insights doctor, x-ray, and architecture map for any repository')
45
+ .description('Instant codebase insights - doctor, x-ray, and architecture map for any repository')
46
46
  .version(pkg.version)
47
47
  .option('--format <type>', 'output format: console, json, markdown, sarif', 'console')
48
48
  .option('--config <path>', 'path to .projscanrc config file')
@@ -78,7 +78,7 @@ async function filterIssuesByChangedFiles(issues, rootPath, baseRef) {
78
78
  const result = await getChangedFiles(rootPath, baseRef);
79
79
  if (!result.available) {
80
80
  if (getFormat() === 'console' && !program.opts().quiet) {
81
- console.error(chalk.yellow(` [--changed-only: ${result.reason ?? 'unavailable'} reporting all issues]`));
81
+ console.error(chalk.yellow(` [--changed-only: ${result.reason ?? 'unavailable'} - reporting all issues]`));
82
82
  }
83
83
  return issues;
84
84
  }
@@ -411,7 +411,7 @@ program
411
411
  // ── Command: file ─────────────────────────────────────────
412
412
  program
413
413
  .command('file <file>')
414
- .description('Drill into a file purpose, risk, ownership, related issues')
414
+ .description('Drill into a file - purpose, risk, ownership, related issues')
415
415
  .action(async (filePath) => {
416
416
  setupLogLevel();
417
417
  maybeCompactBanner();
@@ -447,7 +447,7 @@ program
447
447
  // ── Command: explain ──────────────────────────────────────
448
448
  program
449
449
  .command('explain <file>')
450
- .description('Explain a file its purpose, dependencies, and exports')
450
+ .description('Explain a file - its purpose, dependencies, and exports')
451
451
  .action(async (filePath) => {
452
452
  setupLogLevel();
453
453
  maybeCompactBanner();
@@ -630,7 +630,7 @@ program
630
630
  // ── Command: outdated ─────────────────────────────────────
631
631
  program
632
632
  .command('outdated')
633
- .description('Detect outdated dependencies (offline compares declared vs installed)')
633
+ .description('Detect outdated dependencies (offline - compares declared vs installed)')
634
634
  .action(async () => {
635
635
  setupLogLevel();
636
636
  maybeCompactBanner();
@@ -702,7 +702,7 @@ program
702
702
  // ── Command: upgrade ──────────────────────────────────────
703
703
  program
704
704
  .command('upgrade <package>')
705
- .description('Preview the impact of upgrading a package (offline reads local CHANGELOG + importers)')
705
+ .description('Preview the impact of upgrading a package (offline - reads local CHANGELOG + importers)')
706
706
  .action(async (pkgName) => {
707
707
  setupLogLevel();
708
708
  maybeCompactBanner();
@@ -736,7 +736,7 @@ program
736
736
  // ── Command: search ───────────────────────────────────────
737
737
  program
738
738
  .command('search <query...>')
739
- .description('Ranked search BM25 by default, semantic or hybrid when @xenova/transformers peer is installed')
739
+ .description('Ranked search - BM25 by default, semantic or hybrid when @xenova/transformers peer is installed')
740
740
  .option('--scope <scope>', 'auto | content | symbols | files', 'auto')
741
741
  .option('--mode <mode>', 'lexical | semantic | hybrid (content/auto scope only)', 'lexical')
742
742
  .option('--semantic', 'shortcut for --mode semantic')
@@ -881,7 +881,7 @@ program
881
881
  }
882
882
  if (format === 'markdown') {
883
883
  const r = results;
884
- console.log(`# Search \`${r.query}\` (${r.scope})\n`);
884
+ console.log(`# Search - \`${r.query}\` (${r.scope})\n`);
885
885
  if (r.matches.length === 0) {
886
886
  console.log('_No matches._');
887
887
  return;
@@ -890,7 +890,7 @@ program
890
890
  if ('symbol' in m)
891
891
  console.log(`- \`${m.symbol}\` (${m.kind}) → \`${m.file}:${m.line}\``);
892
892
  else if ('score' in m)
893
- console.log(`- \`${m.file}:${m.line}\` score ${m.score} ${m.excerpt ?? ''}`);
893
+ console.log(`- \`${m.file}:${m.line}\` - score ${m.score} - ${m.excerpt ?? ''}`);
894
894
  else
895
895
  console.log(`- \`${m.file}\``);
896
896
  }
@@ -898,7 +898,7 @@ program
898
898
  }
899
899
  // Console
900
900
  const r = results;
901
- console.log(`\n ${chalk.bold(`Search "${query}"`)} ${chalk.dim(`[${r.scope}]`)}`);
901
+ console.log(`\n ${chalk.bold(`Search - "${query}"`)} ${chalk.dim(`[${r.scope}]`)}`);
902
902
  if (r.queryTokens)
903
903
  console.log(chalk.dim(` tokens: ${r.queryTokens.join(', ')}`));
904
904
  console.log(chalk.dim(' ─'.repeat(20)));
@@ -932,7 +932,7 @@ program
932
932
  // ── Command: coverage ─────────────────────────────────────
933
933
  program
934
934
  .command('coverage')
935
- .description('Join test coverage with hotspots surface the scariest untested files')
935
+ .description('Join test coverage with hotspots - surface the scariest untested files')
936
936
  .option('--limit <n>', 'limit number of entries shown', '30')
937
937
  .action(async (cmdOpts) => {
938
938
  setupLogLevel();
@@ -28,7 +28,7 @@ export declare function isParseable(filePath: string): boolean;
28
28
  * Uses @babel/parser with generous options so we accept real-world code:
29
29
  * TypeScript, JSX, decorators, top-level await, class properties, etc.
30
30
  *
31
- * Failures return ok:false with a reason callers decide whether to fall
31
+ * Failures return ok:false with a reason - callers decide whether to fall
32
32
  * back to regex or skip the file. Never throws.
33
33
  */
34
34
  export declare function parseSource(filePath: string, content: string): AstResult;
package/dist/core/ast.js CHANGED
@@ -28,7 +28,7 @@ export function isParseable(filePath) {
28
28
  * Uses @babel/parser with generous options so we accept real-world code:
29
29
  * TypeScript, JSX, decorators, top-level await, class properties, etc.
30
30
  *
31
- * Failures return ok:false with a reason callers decide whether to fall
31
+ * Failures return ok:false with a reason - callers decide whether to fall
32
32
  * back to regex or skip the file. Never throws.
33
33
  */
34
34
  export function parseSource(filePath, content) {
@@ -67,7 +67,7 @@ export function parseSource(filePath, content) {
67
67
  visitTopLevel(node, imports, exports);
68
68
  }
69
69
  // Second pass: extract dynamic imports + call sites. Walk the whole tree
70
- // (cheap we already have the AST in memory).
70
+ // (cheap - we already have the AST in memory).
71
71
  walk(ast.program, (n) => {
72
72
  if (n.type === 'CallExpression') {
73
73
  const callee = n.callee;
@@ -6,7 +6,7 @@ export interface AuditOptions {
6
6
  /**
7
7
  * Run `npm audit --json` and normalize the output.
8
8
  *
9
- * npm's audit JSON format has changed between npm 6/7/8/9+ we handle the
9
+ * npm's audit JSON format has changed between npm 6/7/8/9+ - we handle the
10
10
  * modern format (npm 7+) first and fall back to a friendly error otherwise.
11
11
  * Yarn/pnpm projects: we don't try to translate; we report "not available"
12
12
  * with a hint.
@@ -13,7 +13,7 @@ const EMPTY_SUMMARY = {
13
13
  /**
14
14
  * Run `npm audit --json` and normalize the output.
15
15
  *
16
- * npm's audit JSON format has changed between npm 6/7/8/9+ we handle the
16
+ * npm's audit JSON format has changed between npm 6/7/8/9+ - we handle the
17
17
  * modern format (npm 7+) first and fall back to a friendly error otherwise.
18
18
  * Yarn/pnpm projects: we don't try to translate; we report "not available"
19
19
  * with a hint.
@@ -28,12 +28,12 @@ export async function runAudit(rootPath, options = {}) {
28
28
  const hasPnpmLock = await fileExists(path.join(rootPath, 'pnpm-lock.yaml'));
29
29
  if (!hasNpmLock) {
30
30
  if (hasYarnLock) {
31
- return unavailable('yarn.lock detected run `yarn npm audit` instead');
31
+ return unavailable('yarn.lock detected - run `yarn npm audit` instead');
32
32
  }
33
33
  if (hasPnpmLock) {
34
- return unavailable('pnpm-lock.yaml detected run `pnpm audit` instead');
34
+ return unavailable('pnpm-lock.yaml detected - run `pnpm audit` instead');
35
35
  }
36
- return unavailable('No package-lock.json run `npm install` first, then retry');
36
+ return unavailable('No package-lock.json - run `npm install` first, then retry');
37
37
  }
38
38
  const timeoutMs = options.timeoutMs ?? 60_000;
39
39
  let stdout;
@@ -46,7 +46,7 @@ export async function runAudit(rootPath, options = {}) {
46
46
  stdout = result.stdout;
47
47
  }
48
48
  catch (err) {
49
- // `npm audit` exits non-zero when vulnerabilities exist this is normal.
49
+ // `npm audit` exits non-zero when vulnerabilities exist - this is normal.
50
50
  // The stdout still contains the JSON payload.
51
51
  const e = err;
52
52
  if (typeof e.stdout === 'string' && e.stdout) {
@@ -211,7 +211,7 @@ export function auditFindingsToIssues(report) {
211
211
  id: `audit-${f.name}`,
212
212
  title: f.title,
213
213
  description: f.url !== undefined
214
- ? `${f.title} ${f.url}${f.range ? ` (range: ${f.range})` : ''}`
214
+ ? `${f.title} - ${f.url}${f.range ? ` (range: ${f.range})` : ''}`
215
215
  : `${f.title}${f.range ? ` (range: ${f.range})` : ''}`,
216
216
  severity: severityMap[f.severity],
217
217
  category: 'security',
@@ -1,6 +1,6 @@
1
1
  import type { CoverageJoinedReport, CoverageReport, HotspotReport } from '../types.js';
2
2
  /**
3
3
  * Join a hotspot report with a coverage report and rank entries by
4
- * "risk × uncovered fraction" the files that most deserve tests.
4
+ * "risk × uncovered fraction" - the files that most deserve tests.
5
5
  */
6
6
  export declare function joinCoverageWithHotspots(hotspots: HotspotReport, coverage: CoverageReport): CoverageJoinedReport;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Join a hotspot report with a coverage report and rank entries by
3
- * "risk × uncovered fraction" the files that most deserve tests.
3
+ * "risk × uncovered fraction" - the files that most deserve tests.
4
4
  */
5
5
  export function joinCoverageWithHotspots(hotspots, coverage) {
6
6
  if (!hotspots.available) {
@@ -64,7 +64,7 @@ function parseByFormat(raw, source, rootPath) {
64
64
  return parseCoverageSummary(raw, rootPath);
65
65
  }
66
66
  /**
67
- * LCOV record-oriented plain text:
67
+ * LCOV - record-oriented plain text:
68
68
  * SF:/abs/path/to/file.ts
69
69
  * LF:100 (lines found)
70
70
  * LH:85 (lines hit)
@@ -104,7 +104,7 @@ function parseLcov(raw, rootPath) {
104
104
  return files;
105
105
  }
106
106
  /**
107
- * coverage-final.json Istanbul per-file detail:
107
+ * coverage-final.json - Istanbul per-file detail:
108
108
  * { "/abs/path/file.ts": { "path": "...", "statementMap": {...}, "s": { "0": 1, "1": 0 }, ... } }
109
109
  * We approximate line coverage from statement counts (statements is the closest
110
110
  * thing to "line" when line-level data isn't separately broken out).
@@ -132,7 +132,7 @@ function parseCoverageFinal(raw, rootPath) {
132
132
  return files;
133
133
  }
134
134
  /**
135
- * coverage-summary.json Istanbul per-file summary:
135
+ * coverage-summary.json - Istanbul per-file summary:
136
136
  * { "total": {...}, "/abs/path/file.ts": { "lines": { "total": 100, "covered": 85, "pct": 85.0 }, ... } }
137
137
  */
138
138
  function parseCoverageSummary(raw, rootPath) {