projscan 0.12.0 → 0.14.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.
- package/README.md +18 -13
- package/dist/analyzers/crossPackageImportCheck.d.ts +13 -0
- package/dist/analyzers/crossPackageImportCheck.js +136 -0
- package/dist/analyzers/crossPackageImportCheck.js.map +1 -0
- package/dist/analyzers/cycleCheck.d.ts +12 -0
- package/dist/analyzers/cycleCheck.js +65 -0
- package/dist/analyzers/cycleCheck.js.map +1 -0
- package/dist/cli/commands/audit.js +8 -2
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/dependencies.js +4 -3
- package/dist/cli/commands/dependencies.js.map +1 -1
- package/dist/cli/commands/explainIssue.d.ts +1 -0
- package/dist/cli/commands/explainIssue.js +49 -0
- package/dist/cli/commands/explainIssue.js.map +1 -0
- package/dist/cli/commands/fixSuggest.d.ts +1 -0
- package/dist/cli/commands/fixSuggest.js +71 -0
- package/dist/cli/commands/fixSuggest.js.map +1 -0
- package/dist/cli/commands/review.d.ts +1 -0
- package/dist/cli/commands/review.js +66 -0
- package/dist/cli/commands/review.js.map +1 -0
- package/dist/cli/index.js +6 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/ast.d.ts +20 -0
- package/dist/core/ast.js +190 -0
- package/dist/core/ast.js.map +1 -1
- package/dist/core/auditRunner.d.ts +8 -0
- package/dist/core/auditRunner.js +50 -1
- package/dist/core/auditRunner.js.map +1 -1
- package/dist/core/codeGraph.d.ts +7 -1
- package/dist/core/codeGraph.js +2 -0
- package/dist/core/codeGraph.js.map +1 -1
- package/dist/core/dependencyAnalyzer.d.ts +15 -1
- package/dist/core/dependencyAnalyzer.js +115 -18
- package/dist/core/dependencyAnalyzer.js.map +1 -1
- package/dist/core/explainIssue.d.ts +9 -0
- package/dist/core/explainIssue.js +106 -0
- package/dist/core/explainIssue.js.map +1 -0
- package/dist/core/fileInspector.js +12 -0
- package/dist/core/fileInspector.js.map +1 -1
- package/dist/core/fixSuggest.d.ts +41 -0
- package/dist/core/fixSuggest.js +327 -0
- package/dist/core/fixSuggest.js.map +1 -0
- package/dist/core/indexCache.js +5 -1
- package/dist/core/indexCache.js.map +1 -1
- package/dist/core/issueEngine.js +18 -0
- package/dist/core/issueEngine.js.map +1 -1
- package/dist/core/languages/goAdapter.js +5 -0
- package/dist/core/languages/goAdapter.js.map +1 -1
- package/dist/core/languages/goFunctions.d.ts +24 -0
- package/dist/core/languages/goFunctions.js +99 -0
- package/dist/core/languages/goFunctions.js.map +1 -0
- package/dist/core/languages/javaAdapter.js +5 -0
- package/dist/core/languages/javaAdapter.js.map +1 -1
- package/dist/core/languages/javaFunctions.d.ts +22 -0
- package/dist/core/languages/javaFunctions.js +87 -0
- package/dist/core/languages/javaFunctions.js.map +1 -0
- package/dist/core/languages/pythonAdapter.js +5 -0
- package/dist/core/languages/pythonAdapter.js.map +1 -1
- package/dist/core/languages/pythonFunctions.d.ts +23 -0
- package/dist/core/languages/pythonFunctions.js +87 -0
- package/dist/core/languages/pythonFunctions.js.map +1 -0
- package/dist/core/languages/rubyAdapter.js +5 -0
- package/dist/core/languages/rubyAdapter.js.map +1 -1
- package/dist/core/languages/rubyFunctions.d.ts +22 -0
- package/dist/core/languages/rubyFunctions.js +91 -0
- package/dist/core/languages/rubyFunctions.js.map +1 -0
- package/dist/core/review.d.ts +21 -0
- package/dist/core/review.js +457 -0
- package/dist/core/review.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/tools/audit.js +7 -2
- package/dist/mcp/tools/audit.js.map +1 -1
- package/dist/mcp/tools/dependencies.js +11 -5
- package/dist/mcp/tools/dependencies.js.map +1 -1
- package/dist/mcp/tools/explainIssue.d.ts +2 -0
- package/dist/mcp/tools/explainIssue.js +30 -0
- package/dist/mcp/tools/explainIssue.js.map +1 -0
- package/dist/mcp/tools/file.js +1 -1
- package/dist/mcp/tools/file.js.map +1 -1
- package/dist/mcp/tools/fixSuggest.d.ts +2 -0
- package/dist/mcp/tools/fixSuggest.js +57 -0
- package/dist/mcp/tools/fixSuggest.js.map +1 -0
- package/dist/mcp/tools/hotspots.js +40 -1
- package/dist/mcp/tools/hotspots.js.map +1 -1
- package/dist/mcp/tools/review.d.ts +2 -0
- package/dist/mcp/tools/review.js +54 -0
- package/dist/mcp/tools/review.js.map +1 -0
- package/dist/mcp/tools.js +6 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/reporters/consoleReporter.d.ts +9 -1
- package/dist/reporters/consoleReporter.js +177 -0
- package/dist/reporters/consoleReporter.js.map +1 -1
- package/dist/reporters/jsonReporter.d.ts +9 -1
- package/dist/reporters/jsonReporter.js +9 -0
- package/dist/reporters/jsonReporter.js.map +1 -1
- package/dist/reporters/markdownReporter.d.ts +9 -1
- package/dist/reporters/markdownReporter.js +141 -0
- package/dist/reporters/markdownReporter.js.map +1 -1
- package/dist/tool-manifest.json +96 -8
- package/dist/types.d.ts +228 -0
- package/dist/utils/config.js +27 -0
- package/dist/utils/config.js.map +1 -1
- package/package.json +4 -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
|
|
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 19 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, and request structured fix-action prompts for any open issue - without loading the file tree into its context.
|
|
24
24
|
|
|
25
25
|
Humans get the same thing through the CLI.
|
|
26
26
|
|
|
@@ -231,14 +231,16 @@ Cache version bumped 2 → 3 in 0.11 (CC stored per file). Existing v2 caches ar
|
|
|
231
231
|
|
|
232
232
|
## Performance
|
|
233
233
|
|
|
234
|
-
Reference numbers from `npm run bench` on an Apple M3 Pro running Node 25:
|
|
234
|
+
Reference numbers from `npm run bench` (projscan repo + synthetic) on an Apple M3 Pro running Node 25:
|
|
235
235
|
|
|
236
236
|
| Repo | Files | analyze | doctor | hotspots | coupling | search |
|
|
237
237
|
|------|-------|---------|--------|----------|----------|--------|
|
|
238
|
-
| projscan itself | ~120 |
|
|
239
|
-
| Synthetic medium | 500 |
|
|
238
|
+
| projscan itself | ~120 | 415–578 ms | 404–498 ms | 462–637 ms | 164–326 ms | 234–393 ms |
|
|
239
|
+
| Synthetic medium | 500 | 244–273 ms | 248–262 ms | 264–293 ms | 165–208 ms | 182–225 ms |
|
|
240
240
|
|
|
241
|
-
|
|
241
|
+
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
|
+
|
|
243
|
+
Cold-cache and warm-cache times shown side by side. Run `npm run bench` against your own machine to recalibrate.
|
|
242
244
|
|
|
243
245
|
- **Zero network requests** - everything runs locally
|
|
244
246
|
- **11 runtime dependencies** - still minimal (the five tree-sitter grammars bring ~3.3 MB of vendored wasm: web-tree-sitter ~190 KB, tree-sitter-python ~450 KB, tree-sitter-go ~210 KB, tree-sitter-java ~405 KB, tree-sitter-ruby ~2.0 MB)
|
|
@@ -461,27 +463,30 @@ claude mcp add projscan -- npx projscan mcp
|
|
|
461
463
|
- *"What breaks if I bump chalk to 6?"* → `projscan_upgrade { package: "chalk" }`
|
|
462
464
|
- *"Where should I refactor first?"* → `projscan_hotspots`
|
|
463
465
|
|
|
464
|
-
### The
|
|
466
|
+
### The 19 MCP tools
|
|
465
467
|
|
|
466
|
-
**Structural (0.6.0 / 0.11 - agent-native):**
|
|
468
|
+
**Structural (0.6.0 / 0.11 / 0.13 / 0.14 - agent-native):**
|
|
467
469
|
- **`projscan_graph`** - query the AST-based code graph. Directions: `imports`, `exports`, `importers`, `symbol_defs`, `package_importers`. Millisecond responses on a warm cache.
|
|
468
470
|
- **`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.
|
|
469
471
|
- **`projscan_coupling`** *(0.11)* - per-file fan-in / fan-out / instability + circular-import cycles (Tarjan SCC). Filter by `direction: cycles_only | high_fan_in | high_fan_out`.
|
|
470
472
|
- **`projscan_pr_diff`** *(0.11)* - structural diff between two git refs. Returns added/removed/modified files with explicit lists of exports, imports, and call sites that changed, plus ΔCC and Δfan-in.
|
|
473
|
+
- **`projscan_review`** *(0.13)* - one-call PR review. Composes `pr_diff` + per-changed-file risk + new/expanded import cycles + risky function additions + dependency changes + a verdict (`ok` / `review` / `block`).
|
|
474
|
+
- **`projscan_fix_suggest`** *(0.14)* - structured action prompt for any open issue: headline, why it matters, where, one-paragraph instruction, optional suggested test. Closes the diagnose → fix loop.
|
|
475
|
+
- **`projscan_explain_issue`** *(0.14)* - deep dive on one issue: code excerpt, related issues in the same file, similar past commits via `git log --grep`, plus the structured FixSuggestion.
|
|
471
476
|
|
|
472
477
|
**Analysis:**
|
|
473
478
|
- `projscan_analyze` - full project report
|
|
474
|
-
- `projscan_doctor` - health score + issues
|
|
475
|
-
- `projscan_hotspots` - risk-ranked files (churn × **AST cyclomatic complexity** × issues × ownership × coverage; falls back to LOC for non-AST languages)
|
|
476
|
-
- `projscan_file` - per-file risk + ownership + related issues + CC + fan-in/fan-out
|
|
479
|
+
- `projscan_doctor` - health score + issues (now includes `cycle-detected-N` for circular imports as of 0.13)
|
|
480
|
+
- `projscan_hotspots` - risk-ranked files (churn × **AST cyclomatic complexity** × issues × ownership × coverage; falls back to LOC for non-AST languages). Pass `view: "functions"` *(0.13)* for top-N risky individual functions.
|
|
481
|
+
- `projscan_file` - per-file risk + ownership + related issues + CC + fan-in/fan-out + per-function CC table *(0.13)*
|
|
477
482
|
- `projscan_explain` - per-file purpose, imports, exports, smells
|
|
478
483
|
- `projscan_structure` - directory tree
|
|
479
484
|
- `projscan_coverage` - scariest untested files (coverage × hotspots)
|
|
480
485
|
|
|
481
486
|
**Dependencies:**
|
|
482
|
-
- `projscan_dependencies` - declared deps, risks
|
|
483
|
-
- `projscan_outdated` - declared-vs-installed drift (offline)
|
|
484
|
-
- `projscan_audit` - normalized `npm audit`
|
|
487
|
+
- `projscan_dependencies` - declared deps, risks. In a monorepo: aggregated totals + `byWorkspace` breakdown; `package` arg scopes to one *(0.13)*.
|
|
488
|
+
- `projscan_outdated` - declared-vs-installed drift (offline). Per-package `byWorkspace`; `package` arg.
|
|
489
|
+
- `projscan_audit` - normalized `npm audit`. `package` arg scopes findings to one workspace's direct deps *(0.13)*.
|
|
485
490
|
- `projscan_upgrade` - upgrade preview (CHANGELOG + importers, offline)
|
|
486
491
|
|
|
487
492
|
**Workspace (0.11):**
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FileEntry, Issue } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-package import policy (0.14.0). Walks the cross-package edges
|
|
4
|
+
* detected by `computeCoupling` and checks each against the user-configured
|
|
5
|
+
* `monorepo.importPolicy` block in `.projscanrc`. Emits one
|
|
6
|
+
* `cross-package-violation-N` issue per violating edge (capped at 50 to
|
|
7
|
+
* keep the doctor output bounded on large monorepos).
|
|
8
|
+
*
|
|
9
|
+
* Off by default: the analyzer is a no-op when no `monorepo.importPolicy`
|
|
10
|
+
* entries are configured or when the repo isn't a monorepo. Adding a
|
|
11
|
+
* single rule turns it on for the matching `from` package.
|
|
12
|
+
*/
|
|
13
|
+
export declare function check(rootPath: string, files: FileEntry[]): Promise<Issue[]>;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { buildCodeGraph } from '../core/codeGraph.js';
|
|
3
|
+
import { loadCachedGraph, saveCachedGraph } from '../core/indexCache.js';
|
|
4
|
+
import { computeCoupling } from '../core/couplingAnalyzer.js';
|
|
5
|
+
import { detectWorkspaces } from '../core/monorepo.js';
|
|
6
|
+
import { loadConfig } from '../utils/config.js';
|
|
7
|
+
const MAX_VIOLATIONS_REPORTED = 50;
|
|
8
|
+
/**
|
|
9
|
+
* Cross-package import policy (0.14.0). Walks the cross-package edges
|
|
10
|
+
* detected by `computeCoupling` and checks each against the user-configured
|
|
11
|
+
* `monorepo.importPolicy` block in `.projscanrc`. Emits one
|
|
12
|
+
* `cross-package-violation-N` issue per violating edge (capped at 50 to
|
|
13
|
+
* keep the doctor output bounded on large monorepos).
|
|
14
|
+
*
|
|
15
|
+
* Off by default: the analyzer is a no-op when no `monorepo.importPolicy`
|
|
16
|
+
* entries are configured or when the repo isn't a monorepo. Adding a
|
|
17
|
+
* single rule turns it on for the matching `from` package.
|
|
18
|
+
*/
|
|
19
|
+
export async function check(rootPath, files) {
|
|
20
|
+
const { config } = await loadConfig(rootPath);
|
|
21
|
+
const rules = config.monorepo?.importPolicy ?? [];
|
|
22
|
+
if (rules.length === 0)
|
|
23
|
+
return [];
|
|
24
|
+
const ws = await detectWorkspaces(rootPath);
|
|
25
|
+
const realWorkspaces = ws.packages.filter((p) => !p.isRoot);
|
|
26
|
+
if (ws.kind === 'none' || realWorkspaces.length < 2)
|
|
27
|
+
return [];
|
|
28
|
+
// Index rules by `from` for O(1) lookup.
|
|
29
|
+
const rulesByPackage = new Map();
|
|
30
|
+
for (const r of rules)
|
|
31
|
+
rulesByPackage.set(r.from, r);
|
|
32
|
+
const cached = await loadCachedGraph(rootPath);
|
|
33
|
+
let graph;
|
|
34
|
+
try {
|
|
35
|
+
graph = await buildCodeGraph(rootPath, files, cached);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
await saveCachedGraph(rootPath, graph).catch(() => undefined);
|
|
41
|
+
const coupling = computeCoupling(graph, ws);
|
|
42
|
+
if (coupling.crossPackageEdges.length === 0)
|
|
43
|
+
return [];
|
|
44
|
+
const issues = [];
|
|
45
|
+
let counter = 0;
|
|
46
|
+
for (const edge of coupling.crossPackageEdges) {
|
|
47
|
+
if (issues.length >= MAX_VIOLATIONS_REPORTED)
|
|
48
|
+
break;
|
|
49
|
+
const rule = rulesByPackage.get(edge.from.package);
|
|
50
|
+
if (!rule)
|
|
51
|
+
continue;
|
|
52
|
+
const verdict = evaluateEdge(rule, edge.to.package);
|
|
53
|
+
if (verdict !== 'deny')
|
|
54
|
+
continue;
|
|
55
|
+
counter++;
|
|
56
|
+
// Try to find the actual import line. The graph file's imports include
|
|
57
|
+
// resolved files; we can match by source string suffix to recover the line.
|
|
58
|
+
const importingFile = graph.files.get(edge.from.file);
|
|
59
|
+
const lineHint = importingFile?.imports.find((i) =>
|
|
60
|
+
// The resolved target's basename usually appears in the source spec;
|
|
61
|
+
// this is best-effort. If we can't pin a line, fall back to file only.
|
|
62
|
+
edge.to.file.includes(path.basename(i.source).replace(/\.[a-z]+$/, '')));
|
|
63
|
+
const location = lineHint?.line
|
|
64
|
+
? { file: edge.from.file, line: lineHint.line }
|
|
65
|
+
: { file: edge.from.file };
|
|
66
|
+
issues.push({
|
|
67
|
+
id: `cross-package-violation-${counter}`,
|
|
68
|
+
title: `Disallowed import from "${edge.from.package}" to "${edge.to.package}"`,
|
|
69
|
+
description: `${edge.from.file} imports from package "${edge.to.package}" but the .projscanrc importPolicy rule for "${edge.from.package}" forbids it. ` +
|
|
70
|
+
'Replace with the package\'s public entry or update the importPolicy.',
|
|
71
|
+
severity: 'warning',
|
|
72
|
+
category: 'architecture',
|
|
73
|
+
fixAvailable: false,
|
|
74
|
+
locations: [location],
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (coupling.crossPackageEdges.length > MAX_VIOLATIONS_REPORTED && issues.length === MAX_VIOLATIONS_REPORTED) {
|
|
78
|
+
issues.push({
|
|
79
|
+
id: 'cross-package-violation-overflow',
|
|
80
|
+
title: 'Additional cross-package policy violations not reported',
|
|
81
|
+
description: `Doctor caps violation reporting at ${MAX_VIOLATIONS_REPORTED}. Run \`projscan coupling\` for the full edge list.`,
|
|
82
|
+
severity: 'info',
|
|
83
|
+
category: 'architecture',
|
|
84
|
+
fixAvailable: false,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return issues;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Decide whether an edge from a rule's `from` package to `targetPackage`
|
|
91
|
+
* is allowed or denied. Logic:
|
|
92
|
+
* - If `allow` is set and matches → 'allow' (short-circuits).
|
|
93
|
+
* - If `deny` is set and matches → 'deny'.
|
|
94
|
+
* - If `allow` is set and doesn't match → 'deny' (allow-list semantics).
|
|
95
|
+
* - Otherwise → 'allow' (no policy hit).
|
|
96
|
+
*/
|
|
97
|
+
function evaluateEdge(rule, targetPackage) {
|
|
98
|
+
if (rule.allow && rule.allow.length > 0) {
|
|
99
|
+
if (matchesAny(targetPackage, rule.allow))
|
|
100
|
+
return 'allow';
|
|
101
|
+
// allow-list miss is a deny.
|
|
102
|
+
return 'deny';
|
|
103
|
+
}
|
|
104
|
+
if (rule.deny && rule.deny.length > 0) {
|
|
105
|
+
if (matchesAny(targetPackage, rule.deny))
|
|
106
|
+
return 'deny';
|
|
107
|
+
}
|
|
108
|
+
return 'allow';
|
|
109
|
+
}
|
|
110
|
+
function matchesAny(name, patterns) {
|
|
111
|
+
for (const p of patterns) {
|
|
112
|
+
if (matchesGlob(name, p))
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
function matchesGlob(name, pattern) {
|
|
118
|
+
if (pattern === '*')
|
|
119
|
+
return true;
|
|
120
|
+
if (pattern === name)
|
|
121
|
+
return true;
|
|
122
|
+
// Simple suffix glob: `pkg/*`
|
|
123
|
+
if (pattern.endsWith('/*')) {
|
|
124
|
+
const prefix = pattern.slice(0, -2);
|
|
125
|
+
if (name.startsWith(prefix + '/'))
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
// Simple prefix glob: `*/sub`
|
|
129
|
+
if (pattern.startsWith('*/')) {
|
|
130
|
+
const suffix = pattern.slice(2);
|
|
131
|
+
if (name.endsWith('/' + suffix))
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=crossPackageImportCheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crossPackageImportCheck.js","sourceRoot":"","sources":["../../src/analyzers/crossPackageImportCheck.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FileEntry, Issue } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Lift Tarjan-detected import cycles from the coupling analyzer into the
|
|
4
|
+
* doctor issue list. Each cycle yields ONE warning issue listing every file
|
|
5
|
+
* in the cycle as a location, so an agent calling `projscan_doctor` (which
|
|
6
|
+
* doesn't fetch coupling data on its own) still sees circular dependencies.
|
|
7
|
+
*
|
|
8
|
+
* Capped at MAX_CYCLES_REPORTED to keep the doctor output bounded on
|
|
9
|
+
* pathological codebases. Each issue's `locations` is capped at
|
|
10
|
+
* MAX_FILES_PER_CYCLE_ISSUE; the rest are listed in the description.
|
|
11
|
+
*/
|
|
12
|
+
export declare function check(rootPath: string, files: FileEntry[]): Promise<Issue[]>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { buildCodeGraph } from '../core/codeGraph.js';
|
|
2
|
+
import { loadCachedGraph, saveCachedGraph } from '../core/indexCache.js';
|
|
3
|
+
import { computeCoupling } from '../core/couplingAnalyzer.js';
|
|
4
|
+
const MAX_FILES_PER_CYCLE_ISSUE = 8;
|
|
5
|
+
const MAX_CYCLES_REPORTED = 20;
|
|
6
|
+
/**
|
|
7
|
+
* Lift Tarjan-detected import cycles from the coupling analyzer into the
|
|
8
|
+
* doctor issue list. Each cycle yields ONE warning issue listing every file
|
|
9
|
+
* in the cycle as a location, so an agent calling `projscan_doctor` (which
|
|
10
|
+
* doesn't fetch coupling data on its own) still sees circular dependencies.
|
|
11
|
+
*
|
|
12
|
+
* Capped at MAX_CYCLES_REPORTED to keep the doctor output bounded on
|
|
13
|
+
* pathological codebases. Each issue's `locations` is capped at
|
|
14
|
+
* MAX_FILES_PER_CYCLE_ISSUE; the rest are listed in the description.
|
|
15
|
+
*/
|
|
16
|
+
export async function check(rootPath, files) {
|
|
17
|
+
// Build the graph (cache-first). saveCachedGraph is best-effort and won't
|
|
18
|
+
// throw on permissions issues.
|
|
19
|
+
const cached = await loadCachedGraph(rootPath);
|
|
20
|
+
let graph;
|
|
21
|
+
try {
|
|
22
|
+
graph = await buildCodeGraph(rootPath, files, cached);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// If graph building fails entirely (e.g. all parsers throw), skip silently.
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
await saveCachedGraph(rootPath, graph).catch(() => undefined);
|
|
29
|
+
const coupling = computeCoupling(graph);
|
|
30
|
+
if (coupling.cycles.length === 0)
|
|
31
|
+
return [];
|
|
32
|
+
const issues = [];
|
|
33
|
+
const cyclesToReport = coupling.cycles.slice(0, MAX_CYCLES_REPORTED);
|
|
34
|
+
for (let i = 0; i < cyclesToReport.length; i++) {
|
|
35
|
+
const cycle = cyclesToReport[i];
|
|
36
|
+
const id = `cycle-detected-${i + 1}`;
|
|
37
|
+
const locFiles = cycle.files.slice(0, MAX_FILES_PER_CYCLE_ISSUE);
|
|
38
|
+
const overflowCount = cycle.files.length - locFiles.length;
|
|
39
|
+
const filesPretty = cycle.files.join(', ');
|
|
40
|
+
const description = overflowCount > 0
|
|
41
|
+
? `Circular import among ${cycle.size} files: ${filesPretty}. Resolve by introducing an interface boundary or moving shared types to a leaf module.`
|
|
42
|
+
: `Circular import among ${cycle.size} files: ${filesPretty}. Resolve by introducing an interface boundary or moving shared types to a leaf module.`;
|
|
43
|
+
issues.push({
|
|
44
|
+
id,
|
|
45
|
+
title: `Circular imports detected (${cycle.size} files)`,
|
|
46
|
+
description,
|
|
47
|
+
severity: 'warning',
|
|
48
|
+
category: 'architecture',
|
|
49
|
+
fixAvailable: false,
|
|
50
|
+
locations: locFiles.map((file) => ({ file })),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (coupling.cycles.length > MAX_CYCLES_REPORTED) {
|
|
54
|
+
issues.push({
|
|
55
|
+
id: 'cycle-detected-overflow',
|
|
56
|
+
title: `${coupling.cycles.length - MAX_CYCLES_REPORTED} additional import cycles not reported`,
|
|
57
|
+
description: `Doctor caps cycle reporting at ${MAX_CYCLES_REPORTED} cycles. Run \`projscan coupling --cycles-only\` for the full list.`,
|
|
58
|
+
severity: 'info',
|
|
59
|
+
category: 'architecture',
|
|
60
|
+
fixAvailable: false,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return issues;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=cycleCheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cycleCheck.js","sourceRoot":"","sources":["../../src/analyzers/cycleCheck.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,MAAM,yBAAyB,GAAG,CAAC,CAAC;AACpC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,QAAgB,EAAE,KAAkB;IAC9D,0EAA0E;IAC1E,+BAA+B;IAC/B,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC/C,IAAI,KAAK,CAAC;IACV,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,4EAA4E;QAC5E,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAE9D,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACxC,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAE5C,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC;IAErE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,yBAAyB,CAAC,CAAC;QACjE,MAAM,aAAa,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;QAE3D,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,WAAW,GACf,aAAa,GAAG,CAAC;YACf,CAAC,CAAC,yBAAyB,KAAK,CAAC,IAAI,WAAW,WAAW,yFAAyF;YACpJ,CAAC,CAAC,yBAAyB,KAAK,CAAC,IAAI,WAAW,WAAW,yFAAyF,CAAC;QAEzJ,MAAM,CAAC,IAAI,CAAC;YACV,EAAE;YACF,KAAK,EAAE,8BAA8B,KAAK,CAAC,IAAI,SAAS;YACxD,WAAW;YACX,QAAQ,EAAE,SAAS;YACnB,QAAQ,EAAE,cAAc;YACxB,YAAY,EAAE,KAAK;YACnB,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;SAC9C,CAAC,CAAC;IACL,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,mBAAmB,EAAE,CAAC;QACjD,MAAM,CAAC,IAAI,CAAC;YACV,EAAE,EAAE,yBAAyB;YAC7B,KAAK,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,mBAAmB,wCAAwC;YAC9F,WAAW,EAAE,kCAAkC,mBAAmB,qEAAqE;YACvI,QAAQ,EAAE,MAAM;YAChB,QAAQ,EAAE,cAAc;YACxB,YAAY,EAAE,KAAK;SACpB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -9,8 +9,9 @@ import { issuesToSarif } from '../../reporters/sarifReporter.js';
|
|
|
9
9
|
export function registerAudit() {
|
|
10
10
|
program
|
|
11
11
|
.command('audit')
|
|
12
|
-
.description('Run npm audit and surface vulnerabilities (SARIF supported)')
|
|
12
|
+
.description('Run npm audit and surface vulnerabilities (SARIF supported; --package scopes findings to a single workspace)')
|
|
13
13
|
.option('--timeout <ms>', 'override npm audit timeout (default 60000)')
|
|
14
|
+
.option('--package <name>', 'monorepo: scope findings to direct deps of one workspace package')
|
|
14
15
|
.action(async (cmdOpts) => {
|
|
15
16
|
setupLogLevel();
|
|
16
17
|
maybeCompactBanner();
|
|
@@ -19,7 +20,12 @@ export function registerAudit() {
|
|
|
19
20
|
const spinner = format === 'console' ? ora('Running npm audit...').start() : null;
|
|
20
21
|
try {
|
|
21
22
|
const timeoutMs = cmdOpts.timeout ? Math.max(5_000, parseInt(cmdOpts.timeout, 10)) : undefined;
|
|
22
|
-
const
|
|
23
|
+
const auditOpts = {};
|
|
24
|
+
if (timeoutMs !== undefined)
|
|
25
|
+
auditOpts.timeoutMs = timeoutMs;
|
|
26
|
+
if (cmdOpts.package)
|
|
27
|
+
auditOpts.packageFilter = cmdOpts.package;
|
|
28
|
+
const report = await runAudit(rootPath, auditOpts);
|
|
23
29
|
if (spinner)
|
|
24
30
|
spinner.stop();
|
|
25
31
|
switch (format) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/cli/commands/audit.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxG,OAAO,EAAE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEjE,MAAM,UAAU,aAAa;IAC3B,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/cli/commands/audit.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxG,OAAO,EAAE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEjE,MAAM,UAAU,aAAa;IAC3B,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,8GAA8G,CAAC;SAC3H,MAAM,CAAC,gBAAgB,EAAE,4CAA4C,CAAC;SACtE,MAAM,CAAC,kBAAkB,EAAE,kEAAkE,CAAC;SAC9F,MAAM,CAAC,KAAK,EAAE,OAA+C,EAAE,EAAE;QAChE,aAAa,EAAE,CAAC;QAChB,kBAAkB,EAAE,CAAC;QACrB,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAElF,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC/F,MAAM,SAAS,GAAqD,EAAE,CAAC;YACvE,IAAI,SAAS,KAAK,SAAS;gBAAE,SAAS,CAAC,SAAS,GAAG,SAAS,CAAC;YAC7D,IAAI,OAAO,CAAC,OAAO;gBAAE,SAAS,CAAC,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;YAC/D,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;YACnD,IAAI,OAAO;gBAAE,OAAO,CAAC,IAAI,EAAE,CAAC;YAE5B,QAAQ,MAAM,EAAE,CAAC;gBACf,KAAK,MAAM;oBACT,eAAe,CAAC,MAAM,CAAC,CAAC;oBACxB,MAAM;gBACR,KAAK,UAAU;oBACb,mBAAmB,CAAC,MAAM,CAAC,CAAC;oBAC5B,MAAM;gBACR,KAAK,OAAO;oBACV,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,qBAAqB,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;oBAChG,MAAM;gBACR;oBACE,WAAW,CAAC,MAAM,CAAC,CAAC;YACxB,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO;gBAAE,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAC1C,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"}
|
|
@@ -8,15 +8,16 @@ import { reportDependenciesMarkdown } from '../../reporters/markdownReporter.js'
|
|
|
8
8
|
export function registerDependencies() {
|
|
9
9
|
program
|
|
10
10
|
.command('dependencies')
|
|
11
|
-
.description('Analyze project dependencies')
|
|
12
|
-
.
|
|
11
|
+
.description('Analyze project dependencies (workspace-aware in monorepos)')
|
|
12
|
+
.option('--package <name>', 'monorepo: scope analysis to a single workspace package')
|
|
13
|
+
.action(async (cmdOpts) => {
|
|
13
14
|
setupLogLevel();
|
|
14
15
|
maybeCompactBanner();
|
|
15
16
|
const rootPath = getRootPath();
|
|
16
17
|
const format = getFormat();
|
|
17
18
|
const spinner = format === 'console' ? ora('Analyzing dependencies...').start() : null;
|
|
18
19
|
try {
|
|
19
|
-
const report = await analyzeDependencies(rootPath);
|
|
20
|
+
const report = await analyzeDependencies(rootPath, { packageFilter: cmdOpts.package });
|
|
20
21
|
if (spinner)
|
|
21
22
|
spinner.stop();
|
|
22
23
|
if (!report) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dependencies.js","sourceRoot":"","sources":["../../../src/cli/commands/dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AAEjF,MAAM,UAAU,oBAAoB;IAClC,OAAO;SACJ,OAAO,CAAC,cAAc,CAAC;SACvB,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"dependencies.js","sourceRoot":"","sources":["../../../src/cli/commands/dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AAEjF,MAAM,UAAU,oBAAoB;IAClC,OAAO;SACJ,OAAO,CAAC,cAAc,CAAC;SACvB,WAAW,CAAC,6DAA6D,CAAC;SAC1E,MAAM,CAAC,kBAAkB,EAAE,wDAAwD,CAAC;SACpF,MAAM,CAAC,KAAK,EAAE,OAA6B,EAAE,EAAE;QAC9C,aAAa,EAAE,CAAC;QAChB,kBAAkB,EAAE,CAAC;QACrB,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAEvF,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,EAAE,EAAE,aAAa,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;YAEvF,IAAI,OAAO;gBAAE,OAAO,CAAC,IAAI,EAAE,CAAC;YAE5B,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,gDAAgD,CAAC,CAAC,CAAC;gBAC5E,OAAO;YACT,CAAC;YAED,QAAQ,MAAM,EAAE,CAAC;gBACf,KAAK,MAAM;oBACT,sBAAsB,CAAC,MAAM,CAAC,CAAC;oBAC/B,MAAM;gBACR,KAAK,UAAU;oBACb,0BAA0B,CAAC,MAAM,CAAC,CAAC;oBACnC,MAAM;gBACR;oBACE,kBAAkB,CAAC,MAAM,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO;gBAAE,OAAO,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;YACxD,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerExplainIssue(): void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { program, getFormat, getRootPath, setupLogLevel, maybeCompactBanner } from '../_shared.js';
|
|
4
|
+
import { scanRepository } from '../../core/repositoryScanner.js';
|
|
5
|
+
import { collectIssues } from '../../core/issueEngine.js';
|
|
6
|
+
import { explainIssue } from '../../core/explainIssue.js';
|
|
7
|
+
import { reportExplainIssue } from '../../reporters/consoleReporter.js';
|
|
8
|
+
import { reportExplainIssueJson } from '../../reporters/jsonReporter.js';
|
|
9
|
+
import { reportExplainIssueMarkdown } from '../../reporters/markdownReporter.js';
|
|
10
|
+
export function registerExplainIssue() {
|
|
11
|
+
program
|
|
12
|
+
.command('explain-issue <issue_id>')
|
|
13
|
+
.description('Deep dive on one open issue: code excerpt, related issues, past fixes, suggested action')
|
|
14
|
+
.action(async (issueId) => {
|
|
15
|
+
setupLogLevel();
|
|
16
|
+
maybeCompactBanner();
|
|
17
|
+
const rootPath = getRootPath();
|
|
18
|
+
const format = getFormat();
|
|
19
|
+
const spinner = format === 'console' ? ora('Resolving issue context...').start() : null;
|
|
20
|
+
try {
|
|
21
|
+
const scan = await scanRepository(rootPath);
|
|
22
|
+
const issues = await collectIssues(rootPath, scan.files);
|
|
23
|
+
const explanation = await explainIssue(rootPath, issues, issueId);
|
|
24
|
+
if (spinner)
|
|
25
|
+
spinner.stop();
|
|
26
|
+
if (!explanation) {
|
|
27
|
+
console.error(chalk.red(`\n No open issue with id "${issueId}" in current doctor run.\n`));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
switch (format) {
|
|
31
|
+
case 'json':
|
|
32
|
+
reportExplainIssueJson(explanation);
|
|
33
|
+
break;
|
|
34
|
+
case 'markdown':
|
|
35
|
+
reportExplainIssueMarkdown(explanation);
|
|
36
|
+
break;
|
|
37
|
+
default:
|
|
38
|
+
reportExplainIssue(explanation);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (spinner)
|
|
43
|
+
spinner.fail('explain-issue failed');
|
|
44
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=explainIssue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explainIssue.js","sourceRoot":"","sources":["../../../src/cli/commands/explainIssue.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AAEjF,MAAM,UAAU,oBAAoB;IAClC,OAAO;SACJ,OAAO,CAAC,0BAA0B,CAAC;SACnC,WAAW,CAAC,yFAAyF,CAAC;SACtG,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,EAAE;QAChC,aAAa,EAAE,CAAC;QAChB,kBAAkB,EAAE,CAAC;QACrB,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAExF,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;YAC5C,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;YACzD,MAAM,WAAW,GAAG,MAAM,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;YAClE,IAAI,OAAO;gBAAE,OAAO,CAAC,IAAI,EAAE,CAAC;YAE5B,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,8BAA8B,OAAO,4BAA4B,CAAC,CAAC,CAAC;gBAC5F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YAED,QAAQ,MAAM,EAAE,CAAC;gBACf,KAAK,MAAM;oBACT,sBAAsB,CAAC,WAAW,CAAC,CAAC;oBACpC,MAAM;gBACR,KAAK,UAAU;oBACb,0BAA0B,CAAC,WAAW,CAAC,CAAC;oBACxC,MAAM;gBACR;oBACE,kBAAkB,CAAC,WAAW,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO;gBAAE,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;YAClD,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerFixSuggest(): void;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { program, getFormat, getRootPath, setupLogLevel, maybeCompactBanner } from '../_shared.js';
|
|
4
|
+
import { scanRepository } from '../../core/repositoryScanner.js';
|
|
5
|
+
import { collectIssues } from '../../core/issueEngine.js';
|
|
6
|
+
import { findIssue, suggestFixForIssue, syntheticIssue } from '../../core/fixSuggest.js';
|
|
7
|
+
import { reportFixSuggest } from '../../reporters/consoleReporter.js';
|
|
8
|
+
import { reportFixSuggestJson } from '../../reporters/jsonReporter.js';
|
|
9
|
+
import { reportFixSuggestMarkdown } from '../../reporters/markdownReporter.js';
|
|
10
|
+
export function registerFixSuggest() {
|
|
11
|
+
program
|
|
12
|
+
.command('fix-suggest [issue_id]')
|
|
13
|
+
.description('Get a structured fix-action prompt for an issue (rule-driven; no LLM inside projscan)')
|
|
14
|
+
.option('--file <path>', 'when no issue_id given: file the rule applies to')
|
|
15
|
+
.option('--rule <name>', 'when no issue_id given: rule / issue-id prefix')
|
|
16
|
+
.option('--severity <level>', 'severity for synthesized requests (info | warning | error)', 'warning')
|
|
17
|
+
.action(async (issueId, cmdOpts) => {
|
|
18
|
+
setupLogLevel();
|
|
19
|
+
maybeCompactBanner();
|
|
20
|
+
const rootPath = getRootPath();
|
|
21
|
+
const format = getFormat();
|
|
22
|
+
const spinner = format === 'console' ? ora('Resolving fix suggestion...').start() : null;
|
|
23
|
+
try {
|
|
24
|
+
let result;
|
|
25
|
+
if (issueId) {
|
|
26
|
+
const scan = await scanRepository(rootPath);
|
|
27
|
+
const issues = await collectIssues(rootPath, scan.files);
|
|
28
|
+
const found = findIssue(issues, issueId);
|
|
29
|
+
if (!found) {
|
|
30
|
+
result = { matched: false, reason: `No open issue with id "${issueId}" in current doctor run.` };
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const fix = await suggestFixForIssue(found, rootPath);
|
|
34
|
+
result = { matched: true, fix };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (cmdOpts.file && cmdOpts.rule) {
|
|
38
|
+
const sev = cmdOpts.severity === 'info' || cmdOpts.severity === 'warning' || cmdOpts.severity === 'error'
|
|
39
|
+
? cmdOpts.severity
|
|
40
|
+
: 'warning';
|
|
41
|
+
const synthetic = syntheticIssue(cmdOpts.rule, cmdOpts.file, sev);
|
|
42
|
+
const fix = await suggestFixForIssue(synthetic, rootPath);
|
|
43
|
+
result = { matched: true, fix, synthetic: true };
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
result = { matched: false, reason: 'Provide either an <issue_id> argument or both --file and --rule.' };
|
|
47
|
+
}
|
|
48
|
+
if (spinner)
|
|
49
|
+
spinner.stop();
|
|
50
|
+
switch (format) {
|
|
51
|
+
case 'json':
|
|
52
|
+
reportFixSuggestJson(result);
|
|
53
|
+
break;
|
|
54
|
+
case 'markdown':
|
|
55
|
+
reportFixSuggestMarkdown(result);
|
|
56
|
+
break;
|
|
57
|
+
default:
|
|
58
|
+
reportFixSuggest(result);
|
|
59
|
+
}
|
|
60
|
+
if (!result.matched)
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (spinner)
|
|
65
|
+
spinner.fail('fix-suggest failed');
|
|
66
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=fixSuggest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixSuggest.js","sourceRoot":"","sources":["../../../src/cli/commands/fixSuggest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerReview(): void;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { program, getFormat, getRootPath, setupLogLevel, maybeCompactBanner } from '../_shared.js';
|
|
4
|
+
import { computeReview } from '../../core/review.js';
|
|
5
|
+
import { detectWorkspaces, filterFilesByPackage } from '../../core/monorepo.js';
|
|
6
|
+
import { reportReview } from '../../reporters/consoleReporter.js';
|
|
7
|
+
import { reportReviewJson } from '../../reporters/jsonReporter.js';
|
|
8
|
+
import { reportReviewMarkdown } from '../../reporters/markdownReporter.js';
|
|
9
|
+
export function registerReview() {
|
|
10
|
+
program
|
|
11
|
+
.command('review')
|
|
12
|
+
.description('One-shot PR review: structural diff + risk + cycles + risky functions + dep changes + verdict')
|
|
13
|
+
.option('--base <ref>', 'base ref (default: origin/main, falling back to main/master/HEAD~1)')
|
|
14
|
+
.option('--head <ref>', 'head ref (default: HEAD)')
|
|
15
|
+
.option('--package <name>', 'monorepo: scope review to a single workspace package')
|
|
16
|
+
.action(async (cmdOpts) => {
|
|
17
|
+
setupLogLevel();
|
|
18
|
+
maybeCompactBanner();
|
|
19
|
+
const rootPath = getRootPath();
|
|
20
|
+
const format = getFormat();
|
|
21
|
+
const spinner = format === 'console' ? ora('Reviewing PR...').start() : null;
|
|
22
|
+
try {
|
|
23
|
+
const report = await computeReview(rootPath, { base: cmdOpts.base, head: cmdOpts.head });
|
|
24
|
+
if (cmdOpts.package && report.available) {
|
|
25
|
+
const ws = await detectWorkspaces(rootPath);
|
|
26
|
+
const target = cmdOpts.package;
|
|
27
|
+
const allChangedPaths = [
|
|
28
|
+
...report.prDiff.filesAdded,
|
|
29
|
+
...report.prDiff.filesRemoved,
|
|
30
|
+
...report.prDiff.filesModified.map((f) => f.relativePath),
|
|
31
|
+
];
|
|
32
|
+
const allowed = new Set(filterFilesByPackage(ws, target, allChangedPaths));
|
|
33
|
+
report.prDiff.filesAdded = report.prDiff.filesAdded.filter((f) => allowed.has(f));
|
|
34
|
+
report.prDiff.filesRemoved = report.prDiff.filesRemoved.filter((f) => allowed.has(f));
|
|
35
|
+
report.prDiff.filesModified = report.prDiff.filesModified.filter((f) => allowed.has(f.relativePath));
|
|
36
|
+
report.prDiff.totalFilesChanged =
|
|
37
|
+
report.prDiff.filesAdded.length +
|
|
38
|
+
report.prDiff.filesRemoved.length +
|
|
39
|
+
report.prDiff.filesModified.length;
|
|
40
|
+
report.changedFiles = report.changedFiles.filter((f) => allowed.has(f.relativePath));
|
|
41
|
+
report.newCycles = report.newCycles.filter((c) => c.files.some((f) => allowed.has(f)));
|
|
42
|
+
report.riskyFunctions = report.riskyFunctions.filter((f) => allowed.has(f.file));
|
|
43
|
+
report.dependencyChanges = report.dependencyChanges.filter((d) => d.workspace === target);
|
|
44
|
+
}
|
|
45
|
+
if (spinner)
|
|
46
|
+
spinner.stop();
|
|
47
|
+
switch (format) {
|
|
48
|
+
case 'json':
|
|
49
|
+
reportReviewJson(report);
|
|
50
|
+
break;
|
|
51
|
+
case 'markdown':
|
|
52
|
+
reportReviewMarkdown(report);
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
reportReview(report);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (spinner)
|
|
60
|
+
spinner.fail('PR review failed');
|
|
61
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.js","sourceRoot":"","sources":["../../../src/cli/commands/review.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/index.js
CHANGED
|
@@ -13,6 +13,9 @@ import { registerDependencies } from './commands/dependencies.js';
|
|
|
13
13
|
import { registerHotspots } from './commands/hotspots.js';
|
|
14
14
|
import { registerCoupling } from './commands/coupling.js';
|
|
15
15
|
import { registerPrDiff } from './commands/prDiff.js';
|
|
16
|
+
import { registerReview } from './commands/review.js';
|
|
17
|
+
import { registerFixSuggest } from './commands/fixSuggest.js';
|
|
18
|
+
import { registerExplainIssue } from './commands/explainIssue.js';
|
|
16
19
|
import { registerWorkspaces } from './commands/workspaces.js';
|
|
17
20
|
import { registerOutdated } from './commands/outdated.js';
|
|
18
21
|
import { registerAudit } from './commands/audit.js';
|
|
@@ -35,6 +38,9 @@ registerDependencies();
|
|
|
35
38
|
registerHotspots();
|
|
36
39
|
registerCoupling();
|
|
37
40
|
registerPrDiff();
|
|
41
|
+
registerReview();
|
|
42
|
+
registerFixSuggest();
|
|
43
|
+
registerExplainIssue();
|
|
38
44
|
registerWorkspaces();
|
|
39
45
|
registerOutdated();
|
|
40
46
|
registerAudit();
|