projscan 1.4.0 → 1.6.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 (112) hide show
  1. package/README.md +24 -7
  2. package/dist/cli/commands/applyFix.d.ts +7 -0
  3. package/dist/cli/commands/applyFix.js +113 -0
  4. package/dist/cli/commands/applyFix.js.map +1 -0
  5. package/dist/cli/commands/coverage.js +13 -0
  6. package/dist/cli/commands/coverage.js.map +1 -1
  7. package/dist/cli/commands/doctor.js +18 -2
  8. package/dist/cli/commands/doctor.js.map +1 -1
  9. package/dist/cli/commands/explain.js +1 -0
  10. package/dist/cli/commands/explain.js.map +1 -1
  11. package/dist/cli/commands/impact.js +25 -1
  12. package/dist/cli/commands/impact.js.map +1 -1
  13. package/dist/cli/commands/init.d.ts +6 -0
  14. package/dist/cli/commands/init.js +70 -0
  15. package/dist/cli/commands/init.js.map +1 -0
  16. package/dist/cli/commands/installHook.d.ts +9 -0
  17. package/dist/cli/commands/installHook.js +90 -0
  18. package/dist/cli/commands/installHook.js.map +1 -0
  19. package/dist/cli/commands/memory.d.ts +11 -0
  20. package/dist/cli/commands/memory.js +175 -0
  21. package/dist/cli/commands/memory.js.map +1 -0
  22. package/dist/cli/commands/taint.d.ts +6 -0
  23. package/dist/cli/commands/taint.js +74 -0
  24. package/dist/cli/commands/taint.js.map +1 -0
  25. package/dist/cli/commands/workspace.d.ts +11 -0
  26. package/dist/cli/commands/workspace.js +115 -0
  27. package/dist/cli/commands/workspace.js.map +1 -0
  28. package/dist/cli/index.js +12 -0
  29. package/dist/cli/index.js.map +1 -1
  30. package/dist/core/applyFix.d.ts +52 -0
  31. package/dist/core/applyFix.js +220 -0
  32. package/dist/core/applyFix.js.map +1 -0
  33. package/dist/core/ast.d.ts +9 -0
  34. package/dist/core/ast.js +35 -4
  35. package/dist/core/ast.js.map +1 -1
  36. package/dist/core/codeGraph.js +190 -241
  37. package/dist/core/codeGraph.js.map +1 -1
  38. package/dist/core/fileInspector.js +40 -44
  39. package/dist/core/fileInspector.js.map +1 -1
  40. package/dist/core/fixSuggest.d.ts +6 -0
  41. package/dist/core/fixSuggest.js +195 -0
  42. package/dist/core/fixSuggest.js.map +1 -1
  43. package/dist/core/hotspotAnalyzer.js +65 -19
  44. package/dist/core/hotspotAnalyzer.js.map +1 -1
  45. package/dist/core/impact.d.ts +8 -0
  46. package/dist/core/impact.js +41 -3
  47. package/dist/core/impact.js.map +1 -1
  48. package/dist/core/indexCache.js +4 -1
  49. package/dist/core/indexCache.js.map +1 -1
  50. package/dist/core/issueEngine.js +24 -0
  51. package/dist/core/issueEngine.js.map +1 -1
  52. package/dist/core/languages/csharpImports.js +6 -4
  53. package/dist/core/languages/csharpImports.js.map +1 -1
  54. package/dist/core/memory.d.ts +154 -0
  55. package/dist/core/memory.js +277 -0
  56. package/dist/core/memory.js.map +1 -0
  57. package/dist/core/review.d.ts +25 -1
  58. package/dist/core/review.js +133 -2
  59. package/dist/core/review.js.map +1 -1
  60. package/dist/core/taint.d.ts +91 -0
  61. package/dist/core/taint.js +185 -0
  62. package/dist/core/taint.js.map +1 -0
  63. package/dist/core/workspace.d.ts +62 -0
  64. package/dist/core/workspace.js +127 -0
  65. package/dist/core/workspace.js.map +1 -0
  66. package/dist/mcp/prompts.js +274 -0
  67. package/dist/mcp/prompts.js.map +1 -1
  68. package/dist/mcp/server.js +162 -146
  69. package/dist/mcp/server.js.map +1 -1
  70. package/dist/mcp/tokenBudget.d.ts +22 -0
  71. package/dist/mcp/tokenBudget.js +26 -0
  72. package/dist/mcp/tokenBudget.js.map +1 -1
  73. package/dist/mcp/tools/applyFix.d.ts +16 -0
  74. package/dist/mcp/tools/applyFix.js +91 -0
  75. package/dist/mcp/tools/applyFix.js.map +1 -0
  76. package/dist/mcp/tools/doctor.js +65 -2
  77. package/dist/mcp/tools/doctor.js.map +1 -1
  78. package/dist/mcp/tools/explain.js +4 -3
  79. package/dist/mcp/tools/explain.js.map +1 -1
  80. package/dist/mcp/tools/explainIssue.js +3 -2
  81. package/dist/mcp/tools/explainIssue.js.map +1 -1
  82. package/dist/mcp/tools/file.js +3 -2
  83. package/dist/mcp/tools/file.js.map +1 -1
  84. package/dist/mcp/tools/graph.js +16 -11
  85. package/dist/mcp/tools/graph.js.map +1 -1
  86. package/dist/mcp/tools/impact.js +36 -3
  87. package/dist/mcp/tools/impact.js.map +1 -1
  88. package/dist/mcp/tools/memory.d.ts +19 -0
  89. package/dist/mcp/tools/memory.js +134 -0
  90. package/dist/mcp/tools/memory.js.map +1 -0
  91. package/dist/mcp/tools/review.js +25 -4
  92. package/dist/mcp/tools/review.js.map +1 -1
  93. package/dist/mcp/tools/taint.d.ts +15 -0
  94. package/dist/mcp/tools/taint.js +67 -0
  95. package/dist/mcp/tools/taint.js.map +1 -0
  96. package/dist/mcp/tools/upgrade.js +3 -2
  97. package/dist/mcp/tools/upgrade.js.map +1 -1
  98. package/dist/mcp/tools/workspaceGraph.d.ts +18 -0
  99. package/dist/mcp/tools/workspaceGraph.js +188 -0
  100. package/dist/mcp/tools/workspaceGraph.js.map +1 -0
  101. package/dist/mcp/tools.js +8 -0
  102. package/dist/mcp/tools.js.map +1 -1
  103. package/dist/reporters/consoleReporter.d.ts +12 -1
  104. package/dist/reporters/consoleReporter.js +289 -179
  105. package/dist/reporters/consoleReporter.js.map +1 -1
  106. package/dist/reporters/markdownReporter.js +185 -128
  107. package/dist/reporters/markdownReporter.js.map +1 -1
  108. package/dist/tool-manifest.json +129 -6
  109. package/dist/types.d.ts +67 -0
  110. package/dist/utils/config.js +93 -53
  111. package/dist/utils/config.js.map +1 -1
  112. package/package.json +9 -2
package/README.md CHANGED
@@ -20,7 +20,7 @@
20
20
 
21
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 21 structured MCP tools. It can query the import graph, find symbol definitions, preview upgrades, rank hotspots, diff structural changes between refs, surface coupling/cycle hotspots, get a one-call PR review, request structured fix-action prompts for any open issue, ask "what breaks if I change this?" via transitive blast-radius analysis, and share a durable session across multiple agent invocations - 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 25 structured MCP tools. It can query the import graph, find symbol definitions, preview upgrades, rank hotspots, diff structural changes between refs, surface coupling/cycle hotspots, get a one-call PR review (now with new-taint-flow detection that *blocks* unsafe merges), request structured fix-action prompts for any open issue and **mechanically apply** the safe ones with rollback, ask "what breaks if I change this?" via transitive blast-radius analysis (across registered sibling repos too), surface source-to-sink taint flows, share a durable session across multiple agent invocations, and learn from how you use it quieting accumulated noise on this specific repo over time without ever phoning home.
24
24
 
25
25
  Humans get the same thing through the CLI.
26
26
 
@@ -99,8 +99,13 @@ For a comprehensive walkthrough, see the **[Full Guide](docs/GUIDE.md)**.
99
99
  | `projscan outdated` | Declared-vs-installed drift check (offline) |
100
100
  | `projscan audit` | `npm audit`-powered vulnerability report - SARIF-ready for Code Scanning |
101
101
  | `projscan upgrade <pkg>` | Preview upgrade impact - local CHANGELOG + importer list, offline |
102
- | `projscan coverage` | **Coverage × hotspots - rank the scariest untested files** |
102
+ | `projscan coverage` | **Coverage × hotspots - rank the scariest untested files** (`--changed-only` for diff mode) |
103
103
  | `projscan badge` | Generate a health score badge for your README |
104
+ | `projscan init` | *(1.6)* Scaffold `.projscanrc.json` with sensible defaults |
105
+ | `projscan install-hook` | *(1.6)* Install a `pre-commit` hook running `projscan ci --changed-only` |
106
+ | `projscan workspace` | *(1.6)* Register sibling repos for cross-repo intelligence (`add` / `list` / `remove`) |
107
+ | `projscan apply-fix <id>` | *(1.6)* Mechanically execute the safe fix templates with rollback (default dry-run) |
108
+ | `projscan taint` | *(1.6)* Source-to-sink reachability over the call graph |
104
109
  | `projscan mcp` | Run as an MCP server for AI coding agents (Claude Code, Cursor, …) |
105
110
 
106
111
  To see all commands and options, run:
@@ -231,12 +236,12 @@ Cache version bumped 2 → 3 in 0.11 (CC stored per file). Existing v2 caches ar
231
236
 
232
237
  ## Performance
233
238
 
234
- Reference numbers from `npm run bench` on an Apple M3 Pro running Node 25 (cold / warm cache, milliseconds). These are the 1.0 reference numbers:
239
+ Reference numbers from `npm run bench` on an Apple M3 Pro running Node 25 (cold / warm cache, milliseconds), refreshed for 1.5.0:
235
240
 
236
241
  | Repo | Files | analyze | doctor | hotspots | coupling | search |
237
242
  |------|-------|---------|--------|----------|----------|--------|
238
- | projscan itself | ~135 | 612 / 463 | 557 / 509 | 673 / 507 | 364 / 173 | 439 / 258 |
239
- | Synthetic medium | 500 | 278 / 268 | 274 / 260 | 300 / 297 | 217 / 174 | 238 / 193 |
243
+ | projscan itself | ~120 | 650 / 576 | 659 / 574 | 794 / 622 | 405 / 186 | 485 / 277 |
244
+ | Synthetic medium | 500 | 284 / 257 | 277 / 255 | 300 / 278 | 224 / 177 | 239 / 196 |
240
245
 
241
246
  For real-world numbers against larger codebases, `npm run bench:references` shallow-clones TypeScript, Django, and kubernetes/client-go into `.bench-cache/` (gitignored) and runs the same suite. First run is network-bound; later runs reuse the cache. Restrict to one target with `-- --only ts|django|k8s-client-go`.
242
247
 
@@ -622,7 +627,7 @@ Capability is advertised under `experimental.fileChanged` on `initialize` so cli
622
627
  - *"What breaks if I bump chalk to 6?"* → `projscan_upgrade { package: "chalk" }`
623
628
  - *"Where should I refactor first?"* → `projscan_hotspots`
624
629
 
625
- ### The 21 MCP tools
630
+ ### The 25 MCP tools
626
631
 
627
632
  **Structural (0.6.0 / 0.11 / 0.13 / 0.14 / 0.15 - agent-native):**
628
633
  - **`projscan_graph`** - query the AST-based code graph. Directions: `imports`, `exports`, `importers`, `symbol_defs`, `package_importers`. Millisecond responses on a warm cache.
@@ -655,6 +660,14 @@ Capability is advertised under `experimental.fileChanged` on `initialize` so cli
655
660
  **Session (1.4):**
656
661
  - **`projscan_session`** *(1.4)* - durable cross-invocation session. Subactions: `current` (id + counts), `touched` (files touched this session, sorted by recency, filterable by source: `tool-result` / `fs-watch` / `explicit`), `events` (chronological log), `reset` (start a fresh session). Auto-populated from every tool result and from `notifications/file_changed` push events when `--watch` is on. Lets multiple agents in the same project see "what's been touched here" without re-running git.
657
662
 
663
+ **Memory (1.5):**
664
+ - **`projscan_memory`** *(1.5)* - durable, local-only feedback loop. Records, per analyzer rule id, how many runs surfaced it and how many fixed it. Subactions: `current` (aggregate counts), `stable` (rules surfaced across ≥ 3 runs over ≥ 7 days without ever being fixed — paired with a ready-to-paste `.projscanrc.json disableRules` snippet), `runs` (every tracked rule with full history), `forget` (drop a single rule). Stored at `.projscan-memory/memory.json`; never leaves the machine. Lets an agent ask "what is this project tolerating?" and propose quieting it.
665
+
666
+ **Operator (1.6):**
667
+ - **`projscan_workspace_graph`** *(1.6)* - cross-repo intelligence over sibling repos registered with `projscan workspace add`. Subactions: `list` (registered repos + parsed-file + export counts), `graph` (every symbol exported by ≥ 2 repos — the candidate refactor / API contract surface), `file_importers` (given a file in one repo, every other repo whose graph imports it). Read-only.
668
+ - **`projscan_apply_fix`** *(1.6)* - mechanically execute the safe fix templates. Default is dry-run; pass `confirm: true` to write. Atomic writes, per-apply rollback record at `.projscan-cache/rollbacks/<id>.json`. Reverse with `action: "rollback", rollback_id: ...`. Six templates supported at this release: `unused-dependency-*`, `missing-test-framework`, `missing-eslint`, `missing-prettier`, `missing-editorconfig`, `missing-readme`.
669
+ - **`projscan_taint`** *(1.6)* - source-to-sink reachability over the per-function call graph. Built-in defaults cover common JS / Python sources (`process.env`, `req.body`, etc.) and sinks (`exec`, `eval`, `db.query`, etc.). Project-specific names go in `.projscanrc.json` `taint`. `projscan_review` automatically diffs taint flows between base and head and **blocks any PR that introduces a new flow**.
670
+
658
671
  ### Context-window budgeting
659
672
 
660
673
  **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.
@@ -698,9 +711,13 @@ All opt-in - default behavior is unchanged.
698
711
 
699
712
  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.
700
713
 
701
- ### Prompts (2, parameterized with live project data)
714
+ ### Prompts (6, parameterized with live project data)
702
715
  - `prioritize_refactoring` - ranked plan grounded in current hotspots
703
716
  - `investigate_file` - senior-engineer brief for a specific file
717
+ - **`refactor_hotspot`** *(1.5)* - step-by-step refactor plan for one hotspot file
718
+ - **`triage_doctor_issues`** *(1.5)* - critical / important / backlog ordering of open issues
719
+ - **`review_this_pr`** *(1.5)* - PR-comment-ready review primed with the structural diff and verdict
720
+ - **`safely_rename_symbol`** *(1.5)* - ordered rename + verification checklist via `projscan_impact` blast radius
704
721
 
705
722
  ### Resources (3, readable on demand)
706
723
  - `projscan://health` · `projscan://hotspots` · `projscan://structure`
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `projscan apply-fix <issue_id>` (1.6+) — execute the mechanical fix
3
+ * for an issue. Default is dry-run; pass --confirm to write to disk.
4
+ *
5
+ * `projscan apply-fix --rollback <id>` — reverse a prior apply.
6
+ */
7
+ export declare function registerApplyFix(): void;
@@ -0,0 +1,113 @@
1
+ import chalk from 'chalk';
2
+ import { program, getRootPath, getFormat, setupLogLevel, maybeCompactBanner } from '../_shared.js';
3
+ import { scanRepository } from '../../core/repositoryScanner.js';
4
+ import { collectIssues } from '../../core/issueEngine.js';
5
+ import { findIssue, buildApplyPlanForIssue } from '../../core/fixSuggest.js';
6
+ import { executePlan, rollback } from '../../core/applyFix.js';
7
+ /**
8
+ * `projscan apply-fix <issue_id>` (1.6+) — execute the mechanical fix
9
+ * for an issue. Default is dry-run; pass --confirm to write to disk.
10
+ *
11
+ * `projscan apply-fix --rollback <id>` — reverse a prior apply.
12
+ */
13
+ export function registerApplyFix() {
14
+ program
15
+ .command('apply-fix [issueId]')
16
+ .description('Apply a mechanical fix for an open issue (dry-run by default; --confirm to write)')
17
+ .option('--confirm', 'actually write to disk (default is dry-run)')
18
+ .option('--rollback <id>', 'reverse a previous apply by rollback id')
19
+ .action(async (issueId, opts) => {
20
+ setupLogLevel();
21
+ maybeCompactBanner();
22
+ const rootPath = getRootPath();
23
+ const format = getFormat();
24
+ try {
25
+ if (opts.rollback) {
26
+ const result = await rollback(rootPath, opts.rollback);
27
+ renderResult(result, format, true);
28
+ if (!result.ok)
29
+ process.exit(1);
30
+ return;
31
+ }
32
+ if (!issueId) {
33
+ console.error(chalk.red('apply-fix needs an issue id (e.g. `projscan apply-fix unused-dependency-foo`).'));
34
+ console.error(chalk.dim(' Get one from `projscan doctor` or `projscan analyze`.'));
35
+ process.exit(1);
36
+ return;
37
+ }
38
+ const scan = await scanRepository(rootPath);
39
+ const issues = await collectIssues(rootPath, scan.files);
40
+ const issue = findIssue(issues, issueId);
41
+ if (!issue) {
42
+ console.error(chalk.red(`No open issue with id "${issueId}".`));
43
+ console.error(chalk.dim(' The issue may have been resolved or the id may be stale.'));
44
+ process.exit(1);
45
+ return;
46
+ }
47
+ const plan = await buildApplyPlanForIssue(issue, rootPath);
48
+ if (!plan) {
49
+ console.error(chalk.yellow(`Issue "${issueId}" does not have apply support yet — only mechanical templates can be auto-applied.`));
50
+ console.error(chalk.dim(` Try \`projscan fix-suggest ${issueId}\` for the structured guidance.`));
51
+ process.exit(1);
52
+ return;
53
+ }
54
+ const dryRun = opts.confirm !== true;
55
+ const result = await executePlan(rootPath, plan, { dryRun });
56
+ if (format === 'json') {
57
+ console.log(JSON.stringify({ ...result, summary: plan.summary }, null, 2));
58
+ }
59
+ else {
60
+ renderApplyResult(result, plan.summary, dryRun);
61
+ }
62
+ if (!result.ok)
63
+ process.exit(1);
64
+ }
65
+ catch (error) {
66
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
67
+ process.exit(1);
68
+ }
69
+ });
70
+ }
71
+ function renderApplyResult(result, summary, dryRun) {
72
+ console.log('');
73
+ console.log(chalk.bold(dryRun ? 'Apply (dry-run)' : 'Apply'));
74
+ console.log(chalk.dim('────────────────────────────────────────'));
75
+ console.log(` ${chalk.bold('Plan:')} ${summary}`);
76
+ console.log('');
77
+ if (!result.ok) {
78
+ console.log(` ${chalk.red('✗')} ${result.reason ?? 'apply failed'}`);
79
+ return;
80
+ }
81
+ for (const c of result.changes) {
82
+ const opTag = c.op === 'create' ? chalk.green('+') : c.op === 'delete' ? chalk.red('-') : chalk.yellow('~');
83
+ console.log(` ${opTag} ${c.op.padEnd(7)} ${c.path}`);
84
+ }
85
+ console.log('');
86
+ if (dryRun) {
87
+ console.log(chalk.dim(' Dry-run only. Re-run with --confirm to write.'));
88
+ }
89
+ else {
90
+ console.log(chalk.green(` ✓ Applied. Rollback id: ${result.rollbackId}`));
91
+ console.log(chalk.dim(' Reverse with `projscan apply-fix --rollback ' + result.rollbackId + '`.'));
92
+ }
93
+ }
94
+ function renderResult(result, format, isRollback) {
95
+ if (format === 'json') {
96
+ console.log(JSON.stringify(result, null, 2));
97
+ return;
98
+ }
99
+ console.log('');
100
+ console.log(chalk.bold(isRollback ? 'Rollback' : 'Apply'));
101
+ console.log(chalk.dim('────────────────────────────────────────'));
102
+ if (!result.ok) {
103
+ console.log(` ${chalk.red('✗')} ${result.reason ?? 'failed'}`);
104
+ return;
105
+ }
106
+ for (const c of result.changes) {
107
+ const opTag = c.op === 'create' ? chalk.green('+') : c.op === 'delete' ? chalk.red('-') : chalk.yellow('~');
108
+ console.log(` ${opTag} ${c.op.padEnd(7)} ${c.path}`);
109
+ }
110
+ console.log('');
111
+ console.log(chalk.green(` ✓ ${isRollback ? 'Rolled back.' : 'Applied.'}`));
112
+ }
113
+ //# sourceMappingURL=applyFix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"applyFix.js","sourceRoot":"","sources":["../../../src/cli/commands/applyFix.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ import { analyzeHotspots } from '../../core/hotspotAnalyzer.js';
7
7
  import { parseCoverage, coverageMap } from '../../core/coverageParser.js';
8
8
  import { joinCoverageWithHotspots } from '../../core/coverageJoin.js';
9
9
  import { detectWorkspaces, filterFilesByPackage } from '../../core/monorepo.js';
10
+ import { getChangedFiles } from '../../utils/changedFiles.js';
10
11
  import { reportCoverage } from '../../reporters/consoleReporter.js';
11
12
  import { reportCoverageJson } from '../../reporters/jsonReporter.js';
12
13
  import { reportCoverageMarkdown } from '../../reporters/markdownReporter.js';
@@ -17,6 +18,8 @@ export function registerCoverage() {
17
18
  .description('Join test coverage with hotspots - surface the scariest untested files')
18
19
  .option('--limit <n>', 'limit number of entries shown', '30')
19
20
  .option('--package <name>', 'monorepo: scope to a single workspace package')
21
+ .option('--changed-only', '1.6+: scope to files changed vs base ref (auto-detected; override with --base-ref)')
22
+ .option('--base-ref <ref>', 'git base ref for --changed-only')
20
23
  .action(async (cmdOpts) => {
21
24
  setupLogLevel();
22
25
  maybeCompactBanner();
@@ -40,6 +43,16 @@ export function registerCoverage() {
40
43
  const allowed = new Set(filterFilesByPackage(ws, cmdOpts.package, joined.entries.map((e) => e.relativePath)));
41
44
  joined.entries = joined.entries.filter((e) => allowed.has(e.relativePath));
42
45
  }
46
+ if (cmdOpts.changedOnly && joined.available) {
47
+ const changed = await getChangedFiles(rootPath, cmdOpts.baseRef);
48
+ if (changed.available) {
49
+ const allowed = new Set(changed.files);
50
+ joined.entries = joined.entries.filter((e) => allowed.has(e.relativePath));
51
+ }
52
+ else if (spinner) {
53
+ spinner.warn(`--changed-only ignored: ${changed.reason}`);
54
+ }
55
+ }
43
56
  if (spinner)
44
57
  spinner.stop();
45
58
  switch (format) {
@@ -1 +1 @@
1
- {"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../../src/cli/commands/coverage.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EACL,OAAO,EACP,SAAS,EACT,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAErE,MAAM,UAAU,gBAAgB;IAC9B,OAAO;SACJ,OAAO,CAAC,UAAU,CAAC;SACnB,WAAW,CAAC,wEAAwE,CAAC;SACrF,MAAM,CAAC,aAAa,EAAE,+BAA+B,EAAE,IAAI,CAAC;SAC5D,MAAM,CAAC,kBAAkB,EAAE,+CAA+C,CAAC;SAC3E,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QACxB,aAAa,EAAE,CAAC;QAChB,kBAAkB,EAAE,CAAC;QACrB,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,MAAM,iBAAiB,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAEjF,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC/C,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;YACvE,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;YACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;YACjH,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE;gBACnE,KAAK;gBACL,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;aACjE,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,wBAAwB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC5D,IAAI,OAAO,CAAC,OAAO,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;gBACxC,MAAM,EAAE,GAAG,MAAM,gBAAgB,CAAC,QAAQ,CAAC,CAAC;gBAC5C,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,oBAAoB,CAAC,EAAE,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;gBAC9G,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;YAC7E,CAAC;YAED,IAAI,OAAO;gBAAE,OAAO,CAAC,IAAI,EAAE,CAAC;YAE5B,QAAQ,MAAM,EAAE,CAAC;gBACf,KAAK,MAAM;oBACT,kBAAkB,CAAC,MAAM,CAAC,CAAC;oBAC3B,MAAM;gBACR,KAAK,UAAU;oBACb,sBAAsB,CAAC,MAAM,CAAC,CAAC;oBAC/B,MAAM;gBACR,KAAK,MAAM;oBACT,kBAAkB,CAAC,MAAM,CAAC,CAAC;oBAC3B,MAAM;gBACR;oBACE,cAAc,CAAC,MAAM,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO;gBAAE,OAAO,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;YACtD,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACjF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../../src/cli/commands/coverage.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,7 @@ import { reportHealthJson } from '../../reporters/jsonReporter.js';
10
10
  import { reportHealthMarkdown } from '../../reporters/markdownReporter.js';
11
11
  import { reportHealthSarif } from '../../reporters/sarifReporter.js';
12
12
  import { reportHealthHtml } from '../../reporters/htmlReporter.js';
13
+ import { findStableRules, loadMemory } from '../../core/memory.js';
13
14
  export function registerDoctor() {
14
15
  program
15
16
  .command('doctor')
@@ -51,8 +52,23 @@ export function registerDoctor() {
51
52
  case 'html':
52
53
  reportHealthHtml(issues);
53
54
  break;
54
- default:
55
- reportHealth(issues, scan.scanDurationMs);
55
+ default: {
56
+ // 1.5+ — surface a Project Memory tip when stable rules
57
+ // have accumulated. Best-effort: a memory load failure
58
+ // never blocks the doctor output.
59
+ let stableRuleCount = 0;
60
+ try {
61
+ const memory = await loadMemory(rootPath);
62
+ stableRuleCount = findStableRules(memory).length;
63
+ }
64
+ catch {
65
+ // best-effort
66
+ }
67
+ reportHealth(issues, {
68
+ scanTimeMs: scan.scanDurationMs,
69
+ stableRuleCount,
70
+ });
71
+ }
56
72
  }
57
73
  }
58
74
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -31,6 +31,7 @@ export function registerExplain() {
31
31
  catch (error) {
32
32
  if (error.code === 'ENOENT') {
33
33
  console.error(chalk.red(`File not found: ${filePath}`));
34
+ console.error(chalk.dim(` Tip: paths are repo-relative. Run \`projscan structure\` to see the file tree.`));
34
35
  }
35
36
  else {
36
37
  console.error(chalk.red(error instanceof Error ? error.message : String(error)));
@@ -1 +1 @@
1
- {"version":3,"file":"explain.js","sourceRoot":"","sources":["../../../src/cli/commands/explain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAElC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AAEhF,MAAM,UAAU,eAAe;IAC7B,OAAO;SACJ,OAAO,CAAC,gBAAgB,CAAC;SACzB,WAAW,CAAC,yDAAyD,CAAC;SACtE,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,EAAE;QACjC,aAAa,EAAE,CAAC;QAChB,kBAAkB,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAE5C,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YACzD,MAAM,WAAW,GAAG,WAAW,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YAEvD,QAAQ,MAAM,EAAE,CAAC;gBACf,KAAK,MAAM;oBACT,qBAAqB,CAAC,WAAW,CAAC,CAAC;oBACnC,MAAM;gBACR,KAAK,UAAU;oBACb,yBAAyB,CAAC,WAAW,CAAC,CAAC;oBACvC,MAAM;gBACR;oBACE,iBAAiB,CAAC,WAAW,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvD,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC,CAAC;YAC1D,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACnF,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"explain.js","sourceRoot":"","sources":["../../../src/cli/commands/explain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAElC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AAEhF,MAAM,UAAU,eAAe;IAC7B,OAAO;SACJ,OAAO,CAAC,gBAAgB,CAAC;SACzB,WAAW,CAAC,yDAAyD,CAAC;SACtE,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,EAAE;QACjC,aAAa,EAAE,CAAC;QAChB,kBAAkB,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAE5C,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YACzD,MAAM,WAAW,GAAG,WAAW,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YAEvD,QAAQ,MAAM,EAAE,CAAC;gBACf,KAAK,MAAM;oBACT,qBAAqB,CAAC,WAAW,CAAC,CAAC;oBACnC,MAAM;gBACR,KAAK,UAAU;oBACb,yBAAyB,CAAC,WAAW,CAAC,CAAC;oBACvC,MAAM;gBACR;oBACE,iBAAiB,CAAC,WAAW,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvD,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC,CAAC;gBACxD,OAAO,CAAC,KAAK,CACX,KAAK,CAAC,GAAG,CAAC,kFAAkF,CAAC,CAC9F,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACnF,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -5,6 +5,7 @@ import { scanRepository } from '../../core/repositoryScanner.js';
5
5
  import { buildCodeGraph } from '../../core/codeGraph.js';
6
6
  import { loadCachedGraph, saveCachedGraph } from '../../core/indexCache.js';
7
7
  import { computeImpact } from '../../core/impact.js';
8
+ import { loadWorkspace } from '../../core/workspace.js';
8
9
  import { reportImpact } from '../../reporters/consoleReporter.js';
9
10
  import { reportImpactJson } from '../../reporters/jsonReporter.js';
10
11
  import { reportImpactMarkdown } from '../../reporters/markdownReporter.js';
@@ -15,6 +16,7 @@ export function registerImpact() {
15
16
  .description('Transitive blast radius for a file (repo path) or symbol (--symbol). Cycle-safe; depth-bounded.')
16
17
  .option('--symbol', 'treat <target> as a symbol (export) name instead of a file path')
17
18
  .option('--max-distance <n>', 'BFS depth limit (default 10)', (v) => parseInt(v, 10))
19
+ .option('--cross-repo', '1.6+: also include callers in sibling repos registered via `projscan workspace add` (symbol mode only)')
18
20
  .action(async (target, cmdOpts) => {
19
21
  setupLogLevel();
20
22
  maybeCompactBanner();
@@ -29,7 +31,13 @@ export function registerImpact() {
29
31
  const t = cmdOpts.symbol
30
32
  ? { kind: 'symbol', value: target }
31
33
  : { kind: 'file', value: target };
32
- const report = computeImpact(graph, t, cmdOpts.maxDistance ? { maxDistance: cmdOpts.maxDistance } : {});
34
+ const crossRepoGraphs = cmdOpts.crossRepo
35
+ ? await buildCrossRepoGraphs(rootPath)
36
+ : undefined;
37
+ const report = computeImpact(graph, t, {
38
+ ...(cmdOpts.maxDistance ? { maxDistance: cmdOpts.maxDistance } : {}),
39
+ ...(crossRepoGraphs ? { crossRepoGraphs } : {}),
40
+ });
33
41
  if (spinner)
34
42
  spinner.stop();
35
43
  switch (format) {
@@ -56,4 +64,20 @@ export function registerImpact() {
56
64
  }
57
65
  });
58
66
  }
67
+ async function buildCrossRepoGraphs(rootPath) {
68
+ const out = new Map();
69
+ const workspace = await loadWorkspace(rootPath);
70
+ if (!workspace || workspace.repos.length === 0)
71
+ return out;
72
+ for (const repo of workspace.repos) {
73
+ try {
74
+ const scan = await scanRepository(repo.path);
75
+ out.set(repo.name, await buildCodeGraph(repo.path, scan.files));
76
+ }
77
+ catch {
78
+ // skip
79
+ }
80
+ }
81
+ return out;
82
+ }
59
83
  //# sourceMappingURL=impact.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"impact.js","sourceRoot":"","sources":["../../../src/cli/commands/impact.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"impact.js","sourceRoot":"","sources":["../../../src/cli/commands/impact.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * `projscan init` (1.6+) — scaffold `.projscanrc.json` for new
3
+ * adopters. Idempotent: if the config already exists, prints a diff
4
+ * against the suggested defaults instead of overwriting.
5
+ */
6
+ export declare function registerInit(): void;
@@ -0,0 +1,70 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import chalk from 'chalk';
4
+ import { program, getRootPath, setupLogLevel, maybeCompactBanner } from '../_shared.js';
5
+ /**
6
+ * `projscan init` (1.6+) — scaffold `.projscanrc.json` for new
7
+ * adopters. Idempotent: if the config already exists, prints a diff
8
+ * against the suggested defaults instead of overwriting.
9
+ */
10
+ export function registerInit() {
11
+ program
12
+ .command('init')
13
+ .description('Scaffold .projscanrc.json with sensible defaults (1.6+)')
14
+ .option('--force', 'overwrite an existing .projscanrc.json (default: refuse)')
15
+ .action(async (opts) => {
16
+ setupLogLevel();
17
+ maybeCompactBanner();
18
+ const rootPath = getRootPath();
19
+ try {
20
+ await runInit(rootPath, opts.force === true);
21
+ }
22
+ catch (error) {
23
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
24
+ process.exit(1);
25
+ }
26
+ });
27
+ }
28
+ const DEFAULT_CONFIG = {
29
+ minScore: 70,
30
+ hotspots: { limit: 10 },
31
+ ignore: [],
32
+ disableRules: [],
33
+ };
34
+ async function runInit(rootPath, force) {
35
+ const target = path.join(rootPath, '.projscanrc.json');
36
+ let exists = false;
37
+ try {
38
+ await fs.access(target);
39
+ exists = true;
40
+ }
41
+ catch {
42
+ // not present
43
+ }
44
+ if (exists && !force) {
45
+ console.log('');
46
+ console.log(chalk.yellow('⚠ .projscanrc.json already exists. Refusing to overwrite.'));
47
+ console.log(chalk.dim(' Pass --force to overwrite, or merge manually with the defaults below:'));
48
+ console.log('');
49
+ console.log(prefixIndent(JSON.stringify(DEFAULT_CONFIG, null, 2), ' '));
50
+ console.log('');
51
+ console.log(chalk.dim(' See https://github.com/abhiyoheswaran1/projscan#projscanrc for the field reference.'));
52
+ return;
53
+ }
54
+ const content = JSON.stringify(DEFAULT_CONFIG, null, 2) + '\n';
55
+ await fs.writeFile(target, content, 'utf-8');
56
+ console.log('');
57
+ console.log(chalk.green('✓ Created .projscanrc.json'));
58
+ console.log('');
59
+ console.log(prefixIndent(content.trimEnd(), ' '));
60
+ console.log('');
61
+ console.log(chalk.dim(' Tune the score threshold, ignore globs, or disabled rules as needed.'));
62
+ console.log(chalk.dim(' Then run `projscan ci --min-score 70` (or whatever you set).'));
63
+ }
64
+ function prefixIndent(text, indent) {
65
+ return text
66
+ .split('\n')
67
+ .map((l) => indent + l)
68
+ .join('\n');
69
+ }
70
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExF;;;;GAIG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,yDAAyD,CAAC;SACtE,MAAM,CAAC,SAAS,EAAE,0DAA0D,CAAC;SAC7E,MAAM,CAAC,KAAK,EAAE,IAAyB,EAAE,EAAE;QAC1C,aAAa,EAAE,CAAC;QAChB,kBAAkB,EAAE,CAAC;QACrB,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACjF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC;AAED,MAAM,cAAc,GAAG;IACrB,QAAQ,EAAE,EAAE;IACZ,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;IACvB,MAAM,EAAE,EAAE;IACV,YAAY,EAAE,EAAE;CACjB,CAAC;AAEF,KAAK,UAAU,OAAO,CAAC,QAAgB,EAAE,KAAc;IACrD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;IACvD,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxB,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,cAAc;IAChB,CAAC;IAED,IAAI,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,2DAA2D,CAAC,CAAC,CAAC;QACvF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,yEAAyE,CAAC,CAAC,CAAC;QAClG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QACzE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,GAAG,CAAC,uFAAuF,CAAC,CACnG,CAAC;QACF,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;IAC/D,MAAM,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,wEAAwE,CAAC,CAAC,CAAC;IACjG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,gEAAgE,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED,SAAS,YAAY,CAAC,IAAY,EAAE,MAAc;IAChD,OAAO,IAAI;SACR,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;SACtB,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `projscan install-hook` (1.6+) — write `.git/hooks/pre-commit`
3
+ * that runs `projscan ci --changed-only --min-score <threshold>`.
4
+ *
5
+ * Skipped if the cwd is not a git repository. Refuses to overwrite
6
+ * an existing hook unless --force is passed; prints the new content
7
+ * for manual merging if there's a conflict.
8
+ */
9
+ export declare function registerInstallHook(): void;
@@ -0,0 +1,90 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import chalk from 'chalk';
4
+ import { program, getRootPath, setupLogLevel, maybeCompactBanner } from '../_shared.js';
5
+ /**
6
+ * `projscan install-hook` (1.6+) — write `.git/hooks/pre-commit`
7
+ * that runs `projscan ci --changed-only --min-score <threshold>`.
8
+ *
9
+ * Skipped if the cwd is not a git repository. Refuses to overwrite
10
+ * an existing hook unless --force is passed; prints the new content
11
+ * for manual merging if there's a conflict.
12
+ */
13
+ export function registerInstallHook() {
14
+ program
15
+ .command('install-hook')
16
+ .description('Install a pre-commit git hook running projscan ci --changed-only (1.6+)')
17
+ .option('--threshold <n>', 'min-score threshold for the hook (default 70)', (v) => parseInt(v, 10))
18
+ .option('--force', "overwrite an existing pre-commit hook (default: refuse)")
19
+ .action(async (opts) => {
20
+ setupLogLevel();
21
+ maybeCompactBanner();
22
+ const rootPath = getRootPath();
23
+ try {
24
+ await runInstallHook(rootPath, opts.threshold ?? 70, opts.force === true);
25
+ }
26
+ catch (error) {
27
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
28
+ process.exit(1);
29
+ }
30
+ });
31
+ }
32
+ async function runInstallHook(rootPath, threshold, force) {
33
+ const gitDir = path.join(rootPath, '.git');
34
+ try {
35
+ const stat = await fs.stat(gitDir);
36
+ if (!stat.isDirectory())
37
+ throw new Error('not a git repo');
38
+ }
39
+ catch {
40
+ console.error(chalk.red('✗ Not a git repository (no .git/ directory at the project root).'));
41
+ process.exit(1);
42
+ return;
43
+ }
44
+ const hooksDir = path.join(gitDir, 'hooks');
45
+ await fs.mkdir(hooksDir, { recursive: true });
46
+ const hookPath = path.join(hooksDir, 'pre-commit');
47
+ const content = buildHookScript(threshold);
48
+ let exists = false;
49
+ try {
50
+ await fs.access(hookPath);
51
+ exists = true;
52
+ }
53
+ catch {
54
+ // not present
55
+ }
56
+ if (exists && !force) {
57
+ console.log('');
58
+ console.log(chalk.yellow('⚠ .git/hooks/pre-commit already exists. Refusing to overwrite.'));
59
+ console.log(chalk.dim(' Pass --force to overwrite, or merge manually with the script below:'));
60
+ console.log('');
61
+ console.log(prefixIndent(content, ' '));
62
+ return;
63
+ }
64
+ await fs.writeFile(hookPath, content, 'utf-8');
65
+ await fs.chmod(hookPath, 0o755);
66
+ console.log('');
67
+ console.log(chalk.green('✓ Installed .git/hooks/pre-commit'));
68
+ console.log(chalk.dim(` Threshold: ${threshold}/100. Runs \`projscan ci --changed-only\` on every commit.`));
69
+ console.log(chalk.dim(' Skip the hook for one commit with `git commit --no-verify` (use sparingly).'));
70
+ }
71
+ function buildHookScript(threshold) {
72
+ return [
73
+ '#!/bin/sh',
74
+ '# Installed by `projscan install-hook` (1.6+).',
75
+ '# Runs projscan ci --changed-only on staged files. Fails the commit',
76
+ '# when the project health score drops below the threshold.',
77
+ `# Threshold: ${threshold}/100.`,
78
+ '',
79
+ 'set -e',
80
+ `npx projscan ci --changed-only --min-score ${threshold}`,
81
+ '',
82
+ ].join('\n');
83
+ }
84
+ function prefixIndent(text, indent) {
85
+ return text
86
+ .split('\n')
87
+ .map((l) => indent + l)
88
+ .join('\n');
89
+ }
90
+ //# sourceMappingURL=installHook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installHook.js","sourceRoot":"","sources":["../../../src/cli/commands/installHook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `projscan memory` — inspect or prune the local Project Memory store
3
+ * that learns which analyzer rules this repo has been carrying across
4
+ * many runs.
5
+ *
6
+ * projscan memory — aggregate summary (default)
7
+ * projscan memory stable — long-running rules + .projscanrc snippet
8
+ * projscan memory runs — every tracked rule with full history
9
+ * projscan memory forget <rule> — drop a single rule's history
10
+ */
11
+ export declare function registerMemory(): void;