dep-brain 0.5.2 → 0.7.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/CHANGELOG.md +2 -0
- package/README.md +33 -7
- package/depbrain.output.schema.json +53 -8
- package/dist/checks/duplicate.js +29 -1
- package/dist/checks/outdated.js +32 -1
- package/dist/checks/risk.js +30 -1
- package/dist/checks/unused.js +38 -2
- package/dist/cli.js +26 -5
- package/dist/core/analyzer.d.ts +35 -1
- package/dist/core/analyzer.js +153 -5
- package/dist/core/types.d.ts +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/reporters/console.js +21 -7
- package/dist/reporters/markdown.js +21 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,3 +7,5 @@ All notable changes to this project will be documented in this file.
|
|
|
7
7
|
- Workspace-aware analysis for npm workspaces.
|
|
8
8
|
- Config loading and CI policy controls.
|
|
9
9
|
- Improved duplicate detection and unused dependency heuristics.
|
|
10
|
+
- Actionable recommendations for unused, duplicate, outdated, and risk findings.
|
|
11
|
+
- Ranked top-issues summary output with `--top`.
|
package/README.md
CHANGED
|
@@ -4,11 +4,16 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/dep-brain)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
`dep-brain` is a CLI and library for
|
|
7
|
+
`dep-brain` is a CLI and library for explainable dependency intelligence in JavaScript and TypeScript projects.
|
|
8
8
|
|
|
9
9
|
## Vision
|
|
10
10
|
|
|
11
|
-
`
|
|
11
|
+
`dep-brain` aims to become a dependency decision engine:
|
|
12
|
+
|
|
13
|
+
- Explain why a dependency matters
|
|
14
|
+
- Evaluate how safe, risky, or necessary it is
|
|
15
|
+
- Recommend what to do next
|
|
16
|
+
- Enforce decisions in CI workflows
|
|
12
17
|
|
|
13
18
|
## What It Does
|
|
14
19
|
|
|
@@ -19,6 +24,12 @@
|
|
|
19
24
|
- Generate a simple project health score
|
|
20
25
|
- Output reports in human-readable or JSON format
|
|
21
26
|
|
|
27
|
+
The long-term goal is not just to list problems, but to answer:
|
|
28
|
+
|
|
29
|
+
- Why is this dependency here?
|
|
30
|
+
- Can I remove it safely?
|
|
31
|
+
- What should I fix first?
|
|
32
|
+
|
|
22
33
|
## Current MVP Features
|
|
23
34
|
|
|
24
35
|
- Duplicate dependency detection with lockfile instance tracking
|
|
@@ -42,6 +53,7 @@ dep-brain analyze
|
|
|
42
53
|
npx dep-brain analyze
|
|
43
54
|
npx dep-brain analyze --json
|
|
44
55
|
npx dep-brain analyze --md
|
|
56
|
+
npx dep-brain analyze --top
|
|
45
57
|
npx dep-brain analyze ./path-to-project
|
|
46
58
|
npx dep-brain analyze --config depbrain.config.json
|
|
47
59
|
npx dep-brain analyze --min-score 90 --fail-on-risks
|
|
@@ -104,6 +116,14 @@ Output includes `outputVersion` for schema stability and can be validated with:
|
|
|
104
116
|
dep-brain analyze --md
|
|
105
117
|
```
|
|
106
118
|
|
|
119
|
+
## Top Issues Output
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
dep-brain analyze --top
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Shows the highest-priority actionable findings first, including confidence and next-step guidance.
|
|
126
|
+
|
|
107
127
|
## Report From JSON
|
|
108
128
|
|
|
109
129
|
```bash
|
|
@@ -225,14 +245,20 @@ src/
|
|
|
225
245
|
`-- config.ts
|
|
226
246
|
```
|
|
227
247
|
|
|
228
|
-
##
|
|
248
|
+
## Product Direction
|
|
249
|
+
|
|
250
|
+
`dep-brain` is currently in the `v0.5.x` foundation stage. The next roadmap is:
|
|
251
|
+
|
|
252
|
+
- `v0.6`: explainability and confidence scoring
|
|
253
|
+
- `v0.7`: safe removal guidance and actionable recommendations
|
|
254
|
+
- `v0.8`: supply-chain trust and risk intelligence
|
|
255
|
+
- `v0.9`: deeper monorepo and ownership intelligence
|
|
256
|
+
- `v1.0`: stable CI, ecosystem exports, and production readiness
|
|
229
257
|
|
|
230
|
-
|
|
231
|
-
- Improve monorepo and workspace support
|
|
232
|
-
- Strengthen risk scoring and suggestions
|
|
233
|
-
- Add CI and GitHub Action support in later releases
|
|
258
|
+
The project should optimize for trust, clarity, and actionability over flashy UI, generic graphs, or simply adding more checks.
|
|
234
259
|
|
|
235
260
|
## Repository Notes
|
|
236
261
|
|
|
237
262
|
- Project brief: [docs/project-brief.md](./docs/project-brief.md)
|
|
263
|
+
- Product roadmap: [docs/product-roadmap.md](./docs/product-roadmap.md)
|
|
238
264
|
- Implementation history: [docs/implementation-log.md](./docs/implementation-log.md)
|
|
@@ -2,9 +2,21 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"title": "Dependency Brain Analysis Output",
|
|
4
4
|
"type": "object",
|
|
5
|
-
"required": ["outputVersion", "rootDir", "score", "scoreBreakdown", "policy", "duplicates", "unused", "outdated", "risks", "suggestions", "config"],
|
|
5
|
+
"required": ["outputVersion", "rootDir", "score", "scoreBreakdown", "policy", "duplicates", "unused", "outdated", "risks", "suggestions", "topIssues", "config"],
|
|
6
6
|
"additionalProperties": false,
|
|
7
7
|
"properties": {
|
|
8
|
+
"recommendation": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"required": ["action", "priority", "safety", "summary", "reasons"],
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"action": { "type": "string", "enum": ["remove", "consolidate", "upgrade", "review"] },
|
|
14
|
+
"priority": { "type": "string", "enum": ["high", "medium", "low"] },
|
|
15
|
+
"safety": { "type": "string", "enum": ["safe", "caution", "unknown"] },
|
|
16
|
+
"summary": { "type": "string" },
|
|
17
|
+
"reasons": { "type": "array", "items": { "type": "string" } }
|
|
18
|
+
}
|
|
19
|
+
},
|
|
8
20
|
"outputVersion": { "type": "string" },
|
|
9
21
|
"rootDir": { "type": "string" },
|
|
10
22
|
"score": { "type": "number" },
|
|
@@ -44,11 +56,15 @@
|
|
|
44
56
|
"type": "array",
|
|
45
57
|
"items": {
|
|
46
58
|
"type": "object",
|
|
47
|
-
"required": ["name", "versions", "instances"],
|
|
59
|
+
"required": ["name", "versions", "instances", "confidence", "reasonCodes", "explanation", "recommendation"],
|
|
48
60
|
"additionalProperties": false,
|
|
49
61
|
"properties": {
|
|
50
62
|
"name": { "type": "string" },
|
|
51
63
|
"versions": { "type": "array", "items": { "type": "string" } },
|
|
64
|
+
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
65
|
+
"reasonCodes": { "type": "array", "items": { "type": "string" } },
|
|
66
|
+
"explanation": { "type": "array", "items": { "type": "string" } },
|
|
67
|
+
"recommendation": { "$ref": "#/properties/recommendation" },
|
|
52
68
|
"instances": {
|
|
53
69
|
"type": "array",
|
|
54
70
|
"items": {
|
|
@@ -68,12 +84,16 @@
|
|
|
68
84
|
"type": "array",
|
|
69
85
|
"items": {
|
|
70
86
|
"type": "object",
|
|
71
|
-
"required": ["name", "section"],
|
|
87
|
+
"required": ["name", "section", "confidence", "reasonCodes", "explanation", "recommendation"],
|
|
72
88
|
"additionalProperties": false,
|
|
73
89
|
"properties": {
|
|
74
90
|
"name": { "type": "string" },
|
|
75
91
|
"section": { "type": "string", "enum": ["dependencies", "devDependencies"] },
|
|
76
|
-
"package": { "type": "string" }
|
|
92
|
+
"package": { "type": "string" },
|
|
93
|
+
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
94
|
+
"reasonCodes": { "type": "array", "items": { "type": "string" } },
|
|
95
|
+
"explanation": { "type": "array", "items": { "type": "string" } },
|
|
96
|
+
"recommendation": { "$ref": "#/properties/recommendation" }
|
|
77
97
|
}
|
|
78
98
|
}
|
|
79
99
|
},
|
|
@@ -81,14 +101,18 @@
|
|
|
81
101
|
"type": "array",
|
|
82
102
|
"items": {
|
|
83
103
|
"type": "object",
|
|
84
|
-
"required": ["name", "current", "latest", "updateType"],
|
|
104
|
+
"required": ["name", "current", "latest", "updateType", "confidence", "reasonCodes", "explanation", "recommendation"],
|
|
85
105
|
"additionalProperties": false,
|
|
86
106
|
"properties": {
|
|
87
107
|
"name": { "type": "string" },
|
|
88
108
|
"current": { "type": "string" },
|
|
89
109
|
"latest": { "type": "string" },
|
|
90
110
|
"updateType": { "type": "string" },
|
|
91
|
-
"package": { "type": "string" }
|
|
111
|
+
"package": { "type": "string" },
|
|
112
|
+
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
113
|
+
"reasonCodes": { "type": "array", "items": { "type": "string" } },
|
|
114
|
+
"explanation": { "type": "array", "items": { "type": "string" } },
|
|
115
|
+
"recommendation": { "$ref": "#/properties/recommendation" }
|
|
92
116
|
}
|
|
93
117
|
}
|
|
94
118
|
},
|
|
@@ -96,16 +120,37 @@
|
|
|
96
120
|
"type": "array",
|
|
97
121
|
"items": {
|
|
98
122
|
"type": "object",
|
|
99
|
-
"required": ["name", "reasons"],
|
|
123
|
+
"required": ["name", "reasons", "confidence", "reasonCodes", "explanation", "recommendation"],
|
|
100
124
|
"additionalProperties": false,
|
|
101
125
|
"properties": {
|
|
102
126
|
"name": { "type": "string" },
|
|
103
127
|
"reasons": { "type": "array", "items": { "type": "string" } },
|
|
104
|
-
"package": { "type": "string" }
|
|
128
|
+
"package": { "type": "string" },
|
|
129
|
+
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
130
|
+
"reasonCodes": { "type": "array", "items": { "type": "string" } },
|
|
131
|
+
"explanation": { "type": "array", "items": { "type": "string" } },
|
|
132
|
+
"recommendation": { "$ref": "#/properties/recommendation" }
|
|
105
133
|
}
|
|
106
134
|
}
|
|
107
135
|
},
|
|
108
136
|
"suggestions": { "type": "array", "items": { "type": "string" } },
|
|
137
|
+
"topIssues": {
|
|
138
|
+
"type": "array",
|
|
139
|
+
"items": {
|
|
140
|
+
"type": "object",
|
|
141
|
+
"required": ["kind", "name", "priority", "confidence", "summary", "recommendation"],
|
|
142
|
+
"additionalProperties": false,
|
|
143
|
+
"properties": {
|
|
144
|
+
"kind": { "type": "string", "enum": ["unused", "duplicate", "outdated", "risk"] },
|
|
145
|
+
"name": { "type": "string" },
|
|
146
|
+
"package": { "type": "string" },
|
|
147
|
+
"priority": { "type": "string", "enum": ["high", "medium", "low"] },
|
|
148
|
+
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
149
|
+
"summary": { "type": "string" },
|
|
150
|
+
"recommendation": { "$ref": "#/properties/recommendation" }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
109
154
|
"config": { "type": "object" },
|
|
110
155
|
"packages": { "type": "array" }
|
|
111
156
|
}
|
package/dist/checks/duplicate.js
CHANGED
|
@@ -3,11 +3,36 @@ export async function findDuplicateDependencies(graph) {
|
|
|
3
3
|
.map(([name, instances]) => ({
|
|
4
4
|
name,
|
|
5
5
|
versions: Array.from(new Set(instances.map((instance) => instance.version))).sort(),
|
|
6
|
-
instances
|
|
6
|
+
instances,
|
|
7
|
+
confidence: 0.98,
|
|
8
|
+
reasonCodes: [
|
|
9
|
+
"multiple_lockfile_versions",
|
|
10
|
+
"multiple_installation_paths"
|
|
11
|
+
],
|
|
12
|
+
explanation: [
|
|
13
|
+
`Multiple versions of ${name} were found in the lockfile.`,
|
|
14
|
+
"The package is installed from more than one dependency path."
|
|
15
|
+
],
|
|
16
|
+
recommendation: buildDuplicateRecommendation(Array.from(new Set(instances.map((instance) => instance.version))).sort(), instances.length)
|
|
7
17
|
}))
|
|
8
18
|
.filter((dependency) => dependency.versions.length > 1)
|
|
9
19
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
10
20
|
}
|
|
21
|
+
function buildDuplicateRecommendation(versions, instanceCount) {
|
|
22
|
+
const targetVersion = versions[versions.length - 1];
|
|
23
|
+
return {
|
|
24
|
+
action: "consolidate",
|
|
25
|
+
priority: versions.length >= 3 ? "high" : "medium",
|
|
26
|
+
safety: "caution",
|
|
27
|
+
summary: targetVersion
|
|
28
|
+
? `Consolidate toward ${targetVersion}; ${instanceCount} installation paths are affected.`
|
|
29
|
+
: "Consolidate duplicate versions to a single target version.",
|
|
30
|
+
reasons: [
|
|
31
|
+
"Multiple versions of the same package were detected in the lockfile.",
|
|
32
|
+
"Consolidating versions can reduce drift and simplify upgrades."
|
|
33
|
+
]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
11
36
|
export async function runDuplicateCheck(graph) {
|
|
12
37
|
const duplicates = await findDuplicateDependencies(graph);
|
|
13
38
|
return {
|
|
@@ -17,6 +42,9 @@ export async function runDuplicateCheck(graph) {
|
|
|
17
42
|
id: `duplicate:${item.name}`,
|
|
18
43
|
message: `${item.name} has ${item.versions.length} versions`,
|
|
19
44
|
severity: "warning",
|
|
45
|
+
confidence: item.confidence,
|
|
46
|
+
reasonCodes: item.reasonCodes,
|
|
47
|
+
explanation: item.explanation,
|
|
20
48
|
meta: {
|
|
21
49
|
name: item.name,
|
|
22
50
|
versions: item.versions,
|
package/dist/checks/outdated.js
CHANGED
|
@@ -11,17 +11,45 @@ export async function findOutdatedDependencies(graph, options = {}) {
|
|
|
11
11
|
if (!latest || latest === normalized) {
|
|
12
12
|
return null;
|
|
13
13
|
}
|
|
14
|
+
const updateType = classifyUpdateType(normalized, latest);
|
|
14
15
|
return {
|
|
15
16
|
name,
|
|
16
17
|
current,
|
|
17
18
|
latest,
|
|
18
|
-
updateType
|
|
19
|
+
updateType,
|
|
20
|
+
confidence: 0.97,
|
|
21
|
+
reasonCodes: [
|
|
22
|
+
"latest_registry_version_newer",
|
|
23
|
+
`update_type_${updateType}`
|
|
24
|
+
],
|
|
25
|
+
explanation: [
|
|
26
|
+
"The npm registry reports a newer published version than the one declared in this project.",
|
|
27
|
+
`The change is classified as a ${updateType} update.`
|
|
28
|
+
],
|
|
29
|
+
recommendation: buildOutdatedRecommendation(updateType)
|
|
19
30
|
};
|
|
20
31
|
}));
|
|
21
32
|
return results
|
|
22
33
|
.filter((item) => item !== null)
|
|
23
34
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
24
35
|
}
|
|
36
|
+
function buildOutdatedRecommendation(updateType) {
|
|
37
|
+
return {
|
|
38
|
+
action: "upgrade",
|
|
39
|
+
priority: updateType === "major" ? "high" : updateType === "minor" ? "medium" : "low",
|
|
40
|
+
safety: updateType === "patch" ? "safe" : updateType === "minor" ? "caution" : "unknown",
|
|
41
|
+
summary: updateType === "major"
|
|
42
|
+
? "New major version available; review breaking changes before upgrading."
|
|
43
|
+
: updateType === "minor"
|
|
44
|
+
? "New minor version available; review release notes before upgrading."
|
|
45
|
+
: updateType === "patch"
|
|
46
|
+
? "Routine patch update available."
|
|
47
|
+
: "Newer version available; review upgrade impact.",
|
|
48
|
+
reasons: [
|
|
49
|
+
"A newer published version is available in the registry."
|
|
50
|
+
]
|
|
51
|
+
};
|
|
52
|
+
}
|
|
25
53
|
export async function runOutdatedCheck(graph) {
|
|
26
54
|
const outdated = await findOutdatedDependencies(graph);
|
|
27
55
|
return {
|
|
@@ -31,6 +59,9 @@ export async function runOutdatedCheck(graph) {
|
|
|
31
59
|
id: `outdated:${item.name}`,
|
|
32
60
|
message: `${item.name} ${item.current} -> ${item.latest}`,
|
|
33
61
|
severity: item.updateType === "major" ? "critical" : "warning",
|
|
62
|
+
confidence: item.confidence,
|
|
63
|
+
reasonCodes: item.reasonCodes,
|
|
64
|
+
explanation: item.explanation,
|
|
34
65
|
meta: {
|
|
35
66
|
name: item.name,
|
|
36
67
|
current: item.current,
|
package/dist/checks/risk.js
CHANGED
|
@@ -23,12 +23,28 @@ export async function findRiskDependencies(graph) {
|
|
|
23
23
|
if (reasons.length === 0) {
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
|
-
return {
|
|
26
|
+
return {
|
|
27
|
+
name,
|
|
28
|
+
reasons,
|
|
29
|
+
confidence: calculateRiskConfidence(reasons),
|
|
30
|
+
reasonCodes: reasons.map(toRiskReasonCode),
|
|
31
|
+
explanation: reasons,
|
|
32
|
+
recommendation: buildRiskRecommendation(reasons, calculateRiskConfidence(reasons))
|
|
33
|
+
};
|
|
27
34
|
}));
|
|
28
35
|
return results
|
|
29
36
|
.filter((item) => item !== null)
|
|
30
37
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
31
38
|
}
|
|
39
|
+
function buildRiskRecommendation(reasons, confidence) {
|
|
40
|
+
return {
|
|
41
|
+
action: "review",
|
|
42
|
+
priority: confidence >= 0.79 ? "high" : "medium",
|
|
43
|
+
safety: "caution",
|
|
44
|
+
summary: "Review package trust signals and decide whether to keep, replace, or monitor it.",
|
|
45
|
+
reasons
|
|
46
|
+
};
|
|
47
|
+
}
|
|
32
48
|
export async function runRiskCheck(graph) {
|
|
33
49
|
const risks = await findRiskDependencies(graph);
|
|
34
50
|
return {
|
|
@@ -38,6 +54,9 @@ export async function runRiskCheck(graph) {
|
|
|
38
54
|
id: `risk:${item.name}`,
|
|
39
55
|
message: `${item.name}: ${item.reasons.join("; ")}`,
|
|
40
56
|
severity: "warning",
|
|
57
|
+
confidence: item.confidence,
|
|
58
|
+
reasonCodes: item.reasonCodes,
|
|
59
|
+
explanation: item.explanation,
|
|
41
60
|
meta: {
|
|
42
61
|
name: item.name,
|
|
43
62
|
reasons: item.reasons
|
|
@@ -45,3 +64,13 @@ export async function runRiskCheck(graph) {
|
|
|
45
64
|
}))
|
|
46
65
|
};
|
|
47
66
|
}
|
|
67
|
+
function calculateRiskConfidence(reasons) {
|
|
68
|
+
return Math.min(0.99, 0.55 + reasons.length * 0.12);
|
|
69
|
+
}
|
|
70
|
+
function toRiskReasonCode(reason) {
|
|
71
|
+
const normalized = reason
|
|
72
|
+
.toLowerCase()
|
|
73
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
74
|
+
.replace(/^_+|_+$/g, "");
|
|
75
|
+
return normalized || "risk_signal_detected";
|
|
76
|
+
}
|
package/dist/checks/unused.js
CHANGED
|
@@ -33,11 +33,11 @@ export async function findUnusedDependencies(rootDir, graph, fileEntries, option
|
|
|
33
33
|
}
|
|
34
34
|
const unusedDependencies = Object.keys(graph.dependencies)
|
|
35
35
|
.filter((name) => !runtimeUsed.has(name))
|
|
36
|
-
.map((name) => (
|
|
36
|
+
.map((name) => buildUnusedDependency(name, "dependencies"));
|
|
37
37
|
const unusedDevDependencies = Object.keys(graph.devDependencies)
|
|
38
38
|
.filter((name) => !devUsed.has(name) && !runtimeUsed.has(name))
|
|
39
39
|
.filter((name) => !isImplicitlyUsedDevDependency(name, hasTypeScriptSources, options.hasTypeScriptConfig))
|
|
40
|
-
.map((name) => (
|
|
40
|
+
.map((name) => buildUnusedDependency(name, "devDependencies"));
|
|
41
41
|
return [...unusedDependencies, ...unusedDevDependencies].sort((left, right) => left.name.localeCompare(right.name));
|
|
42
42
|
}
|
|
43
43
|
export async function runUnusedCheck(context) {
|
|
@@ -49,6 +49,9 @@ export async function runUnusedCheck(context) {
|
|
|
49
49
|
id: `unused:${item.section}:${item.name}`,
|
|
50
50
|
message: `${item.name} appears unused`,
|
|
51
51
|
severity: "warning",
|
|
52
|
+
confidence: item.confidence,
|
|
53
|
+
reasonCodes: item.reasonCodes,
|
|
54
|
+
explanation: item.explanation,
|
|
52
55
|
meta: {
|
|
53
56
|
name: item.name,
|
|
54
57
|
section: item.section
|
|
@@ -127,3 +130,36 @@ function isImplicitlyUsedDevDependency(name, hasTypeScriptSources, hasTypeScript
|
|
|
127
130
|
}
|
|
128
131
|
return false;
|
|
129
132
|
}
|
|
133
|
+
function buildUnusedDependency(name, section) {
|
|
134
|
+
return {
|
|
135
|
+
name,
|
|
136
|
+
section,
|
|
137
|
+
confidence: section === "dependencies" ? 0.9 : 0.82,
|
|
138
|
+
reasonCodes: [
|
|
139
|
+
"no_source_import_found",
|
|
140
|
+
"no_config_reference_found",
|
|
141
|
+
"no_script_reference_found"
|
|
142
|
+
],
|
|
143
|
+
explanation: [
|
|
144
|
+
"No import or require usage was found in scanned source files.",
|
|
145
|
+
"No matching reference was found in recognized config files.",
|
|
146
|
+
"No matching binary or package reference was found in package scripts."
|
|
147
|
+
],
|
|
148
|
+
recommendation: buildUnusedRecommendation(section)
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function buildUnusedRecommendation(section) {
|
|
152
|
+
const safety = section === "devDependencies" ? "safe" : "caution";
|
|
153
|
+
return {
|
|
154
|
+
action: "remove",
|
|
155
|
+
priority: section === "dependencies" ? "high" : "medium",
|
|
156
|
+
safety,
|
|
157
|
+
summary: safety === "safe"
|
|
158
|
+
? `Safe to remove from ${section}.`
|
|
159
|
+
: `Likely removable from ${section}, but review before deleting.`,
|
|
160
|
+
reasons: [
|
|
161
|
+
"No code usage was found in scanned files.",
|
|
162
|
+
"No config or script reference was detected."
|
|
163
|
+
]
|
|
164
|
+
};
|
|
165
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -51,9 +51,11 @@ async function main() {
|
|
|
51
51
|
const resolvedFrom = resolveUserPath(fromPath);
|
|
52
52
|
const raw = await fs.readFile(resolvedFrom, "utf8");
|
|
53
53
|
const reportData = JSON.parse(raw);
|
|
54
|
-
const output = flags.has("--
|
|
55
|
-
?
|
|
56
|
-
:
|
|
54
|
+
const output = flags.has("--top")
|
|
55
|
+
? renderTopIssuesReport(reportData)
|
|
56
|
+
: flags.has("--json")
|
|
57
|
+
? JSON.stringify(reportData, null, 2)
|
|
58
|
+
: renderMarkdownReport(reportData);
|
|
57
59
|
await writeOutput(output, optionValues.get("--out"));
|
|
58
60
|
return;
|
|
59
61
|
}
|
|
@@ -107,6 +109,9 @@ async function main() {
|
|
|
107
109
|
if (flags.has("--json")) {
|
|
108
110
|
output = renderJsonReport(result);
|
|
109
111
|
}
|
|
112
|
+
else if (flags.has("--top")) {
|
|
113
|
+
output = renderTopIssuesReport(result);
|
|
114
|
+
}
|
|
110
115
|
else if (flags.has("--md")) {
|
|
111
116
|
output = renderMarkdownReport(result);
|
|
112
117
|
}
|
|
@@ -173,8 +178,8 @@ function printHelp() {
|
|
|
173
178
|
console.log("Dependency Brain");
|
|
174
179
|
console.log("");
|
|
175
180
|
console.log("Usage:");
|
|
176
|
-
console.log(" dep-brain analyze [path] [--json] [--md] [--out path] [--config path] [--min-score n] [--fail-on-risks] [--fail-on-outdated] [--fail-on-unused] [--fail-on-duplicates]");
|
|
177
|
-
console.log(" dep-brain report --from <file> [--md] [--json] [--out path]");
|
|
181
|
+
console.log(" dep-brain analyze [path] [--json] [--md] [--top] [--out path] [--config path] [--min-score n] [--fail-on-risks] [--fail-on-outdated] [--fail-on-unused] [--fail-on-duplicates]");
|
|
182
|
+
console.log(" dep-brain report --from <file> [--md] [--json] [--top] [--out path]");
|
|
178
183
|
console.log(" dep-brain config [path] [--config path]");
|
|
179
184
|
console.log(" dep-brain help");
|
|
180
185
|
console.log(" dep-brain --version");
|
|
@@ -182,6 +187,7 @@ function printHelp() {
|
|
|
182
187
|
console.log("Options:");
|
|
183
188
|
console.log(" --json Output JSON for analysis");
|
|
184
189
|
console.log(" --md Output Markdown report");
|
|
190
|
+
console.log(" --top Output the ranked top issues only");
|
|
185
191
|
console.log(" --config <path> Path to depbrain.config.json");
|
|
186
192
|
console.log(" --from <file> Read analysis JSON from file");
|
|
187
193
|
console.log(" --out <path> Write output to a file");
|
|
@@ -218,3 +224,18 @@ function sanitizeForLog(value) {
|
|
|
218
224
|
function resolveUserPath(value) {
|
|
219
225
|
return path.resolve(process.cwd(), value);
|
|
220
226
|
}
|
|
227
|
+
function renderTopIssuesReport(result) {
|
|
228
|
+
const lines = [];
|
|
229
|
+
lines.push("Top Issues");
|
|
230
|
+
lines.push("");
|
|
231
|
+
if (!Array.isArray(result.topIssues) || result.topIssues.length === 0) {
|
|
232
|
+
lines.push("No actionable issues found.");
|
|
233
|
+
return lines.join("\n");
|
|
234
|
+
}
|
|
235
|
+
for (const [index, item] of result.topIssues.entries()) {
|
|
236
|
+
lines.push(`${index + 1}. [${item.priority.toUpperCase()}] ${item.kind} ${item.name}${item.package ? ` [${item.package}]` : ""}`);
|
|
237
|
+
lines.push(` Confidence: ${Math.round(item.confidence * 100)}%`);
|
|
238
|
+
lines.push(` Next: ${item.recommendation.summary}`);
|
|
239
|
+
}
|
|
240
|
+
return lines.join("\n");
|
|
241
|
+
}
|
package/dist/core/analyzer.d.ts
CHANGED
|
@@ -8,15 +8,30 @@ export interface DuplicateInstance {
|
|
|
8
8
|
path: string;
|
|
9
9
|
version: string;
|
|
10
10
|
}
|
|
11
|
+
export interface Recommendation {
|
|
12
|
+
action: "remove" | "consolidate" | "upgrade" | "review";
|
|
13
|
+
priority: "high" | "medium" | "low";
|
|
14
|
+
safety: "safe" | "caution" | "unknown";
|
|
15
|
+
summary: string;
|
|
16
|
+
reasons: string[];
|
|
17
|
+
}
|
|
11
18
|
export interface DuplicateDependency {
|
|
12
19
|
name: string;
|
|
13
20
|
versions: string[];
|
|
14
21
|
instances: DuplicateInstance[];
|
|
22
|
+
confidence: number;
|
|
23
|
+
reasonCodes: string[];
|
|
24
|
+
explanation: string[];
|
|
25
|
+
recommendation: Recommendation;
|
|
15
26
|
}
|
|
16
27
|
export interface UnusedDependency {
|
|
17
28
|
name: string;
|
|
18
29
|
section: "dependencies" | "devDependencies";
|
|
19
30
|
package?: string;
|
|
31
|
+
confidence: number;
|
|
32
|
+
reasonCodes: string[];
|
|
33
|
+
explanation: string[];
|
|
34
|
+
recommendation: Recommendation;
|
|
20
35
|
}
|
|
21
36
|
export interface OutdatedDependency {
|
|
22
37
|
name: string;
|
|
@@ -24,11 +39,28 @@ export interface OutdatedDependency {
|
|
|
24
39
|
latest: string;
|
|
25
40
|
updateType: "major" | "minor" | "patch" | "unknown";
|
|
26
41
|
package?: string;
|
|
42
|
+
confidence: number;
|
|
43
|
+
reasonCodes: string[];
|
|
44
|
+
explanation: string[];
|
|
45
|
+
recommendation: Recommendation;
|
|
27
46
|
}
|
|
28
47
|
export interface RiskDependency {
|
|
29
48
|
name: string;
|
|
30
49
|
reasons: string[];
|
|
31
50
|
package?: string;
|
|
51
|
+
confidence: number;
|
|
52
|
+
reasonCodes: string[];
|
|
53
|
+
explanation: string[];
|
|
54
|
+
recommendation: Recommendation;
|
|
55
|
+
}
|
|
56
|
+
export interface TopIssue {
|
|
57
|
+
kind: "unused" | "duplicate" | "outdated" | "risk";
|
|
58
|
+
name: string;
|
|
59
|
+
package?: string;
|
|
60
|
+
priority: "high" | "medium" | "low";
|
|
61
|
+
confidence: number;
|
|
62
|
+
summary: string;
|
|
63
|
+
recommendation: Recommendation;
|
|
32
64
|
}
|
|
33
65
|
export interface AnalysisResult {
|
|
34
66
|
outputVersion: string;
|
|
@@ -41,6 +73,7 @@ export interface AnalysisResult {
|
|
|
41
73
|
outdated: OutdatedDependency[];
|
|
42
74
|
risks: RiskDependency[];
|
|
43
75
|
suggestions: string[];
|
|
76
|
+
topIssues: TopIssue[];
|
|
44
77
|
config: DepBrainConfig;
|
|
45
78
|
packages?: PackageAnalysisResult[];
|
|
46
79
|
}
|
|
@@ -59,8 +92,9 @@ export interface PackageAnalysisResult {
|
|
|
59
92
|
outdated: OutdatedDependency[];
|
|
60
93
|
risks: RiskDependency[];
|
|
61
94
|
suggestions: string[];
|
|
95
|
+
topIssues: TopIssue[];
|
|
62
96
|
}
|
|
63
|
-
export declare const OUTPUT_VERSION = "1.
|
|
97
|
+
export declare const OUTPUT_VERSION = "1.2";
|
|
64
98
|
export interface ScoreBreakdown {
|
|
65
99
|
baseScore: number;
|
|
66
100
|
duplicates: number;
|
package/dist/core/analyzer.js
CHANGED
|
@@ -8,7 +8,7 @@ import { findWorkspacePackages } from "../utils/workspaces.js";
|
|
|
8
8
|
import { buildDependencyGraph } from "./graph-builder.js";
|
|
9
9
|
import { calculateHealthScore } from "./scorer.js";
|
|
10
10
|
import { buildAnalysisContext } from "./context.js";
|
|
11
|
-
export const OUTPUT_VERSION = "1.
|
|
11
|
+
export const OUTPUT_VERSION = "1.2";
|
|
12
12
|
export async function analyzeProject(options = {}) {
|
|
13
13
|
const rootDir = path.resolve(options.rootDir ?? process.cwd());
|
|
14
14
|
const loadedConfig = await loadDepBrainConfig(rootDir, options.configPath);
|
|
@@ -68,6 +68,7 @@ export async function analyzeProject(options = {}) {
|
|
|
68
68
|
outdated,
|
|
69
69
|
risks,
|
|
70
70
|
suggestions,
|
|
71
|
+
topIssues: buildTopIssues({ duplicates, unused, outdated, risks }),
|
|
71
72
|
config,
|
|
72
73
|
packages
|
|
73
74
|
};
|
|
@@ -186,6 +187,12 @@ async function analyzeSingleProject(rootDir, config, options = {}) {
|
|
|
186
187
|
outdated: scopedOutdated,
|
|
187
188
|
risks: scopedRisks,
|
|
188
189
|
suggestions,
|
|
190
|
+
topIssues: buildTopIssues({
|
|
191
|
+
duplicates,
|
|
192
|
+
unused: scopedUnused,
|
|
193
|
+
outdated: scopedOutdated,
|
|
194
|
+
risks: scopedRisks
|
|
195
|
+
}),
|
|
189
196
|
config
|
|
190
197
|
};
|
|
191
198
|
}
|
|
@@ -264,13 +271,21 @@ function mapDuplicateIssues(issues) {
|
|
|
264
271
|
versions: Array.isArray(issue.meta?.versions) ? issue.meta?.versions : [],
|
|
265
272
|
instances: Array.isArray(issue.meta?.instances)
|
|
266
273
|
? issue.meta?.instances
|
|
267
|
-
: []
|
|
274
|
+
: [],
|
|
275
|
+
confidence: normalizeConfidence(issue.confidence),
|
|
276
|
+
reasonCodes: normalizeStringArray(issue.reasonCodes),
|
|
277
|
+
explanation: normalizeStringArray(issue.explanation),
|
|
278
|
+
recommendation: buildDuplicateRecommendation(issue)
|
|
268
279
|
}));
|
|
269
280
|
}
|
|
270
281
|
function mapUnusedIssues(issues) {
|
|
271
282
|
return issues.map((issue) => ({
|
|
272
283
|
name: String(issue.meta?.name ?? issue.package ?? "unknown"),
|
|
273
|
-
section: issue.meta?.section === "devDependencies" ? "devDependencies" : "dependencies"
|
|
284
|
+
section: issue.meta?.section === "devDependencies" ? "devDependencies" : "dependencies",
|
|
285
|
+
confidence: normalizeConfidence(issue.confidence),
|
|
286
|
+
reasonCodes: normalizeStringArray(issue.reasonCodes),
|
|
287
|
+
explanation: normalizeStringArray(issue.explanation),
|
|
288
|
+
recommendation: buildUnusedRecommendation(issue)
|
|
274
289
|
}));
|
|
275
290
|
}
|
|
276
291
|
function mapOutdatedIssues(issues) {
|
|
@@ -280,15 +295,148 @@ function mapOutdatedIssues(issues) {
|
|
|
280
295
|
latest: String(issue.meta?.latest ?? ""),
|
|
281
296
|
updateType: issue.meta?.updateType === "major" || issue.meta?.updateType === "minor" || issue.meta?.updateType === "patch"
|
|
282
297
|
? issue.meta.updateType
|
|
283
|
-
: "unknown"
|
|
298
|
+
: "unknown",
|
|
299
|
+
confidence: normalizeConfidence(issue.confidence),
|
|
300
|
+
reasonCodes: normalizeStringArray(issue.reasonCodes),
|
|
301
|
+
explanation: normalizeStringArray(issue.explanation),
|
|
302
|
+
recommendation: buildOutdatedRecommendation(issue)
|
|
284
303
|
}));
|
|
285
304
|
}
|
|
286
305
|
function mapRiskIssues(issues) {
|
|
287
306
|
return issues.map((issue) => ({
|
|
288
307
|
name: String(issue.meta?.name ?? issue.package ?? "unknown"),
|
|
289
|
-
reasons: Array.isArray(issue.meta?.reasons) ? issue.meta?.reasons : []
|
|
308
|
+
reasons: Array.isArray(issue.meta?.reasons) ? issue.meta?.reasons : [],
|
|
309
|
+
confidence: normalizeConfidence(issue.confidence),
|
|
310
|
+
reasonCodes: normalizeStringArray(issue.reasonCodes),
|
|
311
|
+
explanation: normalizeStringArray(issue.explanation),
|
|
312
|
+
recommendation: buildRiskRecommendation(issue)
|
|
290
313
|
}));
|
|
291
314
|
}
|
|
315
|
+
function buildUnusedRecommendation(issue) {
|
|
316
|
+
const confidence = normalizeConfidence(issue.confidence);
|
|
317
|
+
const section = issue.meta?.section === "devDependencies" ? "devDependencies" : "dependencies";
|
|
318
|
+
const safety = section === "devDependencies" || confidence >= 0.88
|
|
319
|
+
? "safe"
|
|
320
|
+
: confidence >= 0.7
|
|
321
|
+
? "caution"
|
|
322
|
+
: "unknown";
|
|
323
|
+
return {
|
|
324
|
+
action: "remove",
|
|
325
|
+
priority: confidence >= 0.88 ? "high" : "medium",
|
|
326
|
+
safety,
|
|
327
|
+
summary: safety === "safe"
|
|
328
|
+
? `Safe to remove from ${section}.`
|
|
329
|
+
: safety === "caution"
|
|
330
|
+
? `Likely removable from ${section}, but review before deleting.`
|
|
331
|
+
: `Potentially removable from ${section}, but evidence is limited.`,
|
|
332
|
+
reasons: normalizeStringArray(issue.explanation)
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function buildDuplicateRecommendation(issue) {
|
|
336
|
+
const versions = Array.isArray(issue.meta?.versions) ? issue.meta.versions : [];
|
|
337
|
+
const targetVersion = versions[versions.length - 1];
|
|
338
|
+
const instances = Array.isArray(issue.meta?.instances) ? issue.meta.instances.length : 0;
|
|
339
|
+
return {
|
|
340
|
+
action: "consolidate",
|
|
341
|
+
priority: versions.length >= 3 ? "high" : "medium",
|
|
342
|
+
safety: "caution",
|
|
343
|
+
summary: targetVersion
|
|
344
|
+
? `Consolidate toward ${targetVersion}; ${instances} installation paths are affected.`
|
|
345
|
+
: "Consolidate duplicate versions to a single target version.",
|
|
346
|
+
reasons: normalizeStringArray(issue.explanation)
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function buildOutdatedRecommendation(issue) {
|
|
350
|
+
const updateType = issue.meta?.updateType === "major" ||
|
|
351
|
+
issue.meta?.updateType === "minor" ||
|
|
352
|
+
issue.meta?.updateType === "patch"
|
|
353
|
+
? issue.meta.updateType
|
|
354
|
+
: "unknown";
|
|
355
|
+
const priority = updateType === "major" ? "high" : updateType === "minor" ? "medium" : "low";
|
|
356
|
+
const safety = updateType === "patch" ? "safe" : updateType === "minor" ? "caution" : "unknown";
|
|
357
|
+
return {
|
|
358
|
+
action: "upgrade",
|
|
359
|
+
priority,
|
|
360
|
+
safety,
|
|
361
|
+
summary: updateType === "major"
|
|
362
|
+
? "New major version available; review breaking changes before upgrading."
|
|
363
|
+
: updateType === "minor"
|
|
364
|
+
? "New minor version available; review release notes before upgrading."
|
|
365
|
+
: updateType === "patch"
|
|
366
|
+
? "Routine patch update available."
|
|
367
|
+
: "Newer version available; review upgrade impact.",
|
|
368
|
+
reasons: normalizeStringArray(issue.explanation)
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function buildRiskRecommendation(issue) {
|
|
372
|
+
const reasons = normalizeStringArray(issue.explanation);
|
|
373
|
+
const confidence = normalizeConfidence(issue.confidence);
|
|
374
|
+
return {
|
|
375
|
+
action: "review",
|
|
376
|
+
priority: confidence >= 0.79 ? "high" : "medium",
|
|
377
|
+
safety: "caution",
|
|
378
|
+
summary: "Review package trust signals and decide whether to keep, replace, or monitor it.",
|
|
379
|
+
reasons
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function buildTopIssues(input) {
|
|
383
|
+
const issues = [
|
|
384
|
+
...input.unused.map((item) => ({
|
|
385
|
+
kind: "unused",
|
|
386
|
+
name: item.name,
|
|
387
|
+
package: item.package,
|
|
388
|
+
priority: item.recommendation.priority,
|
|
389
|
+
confidence: item.confidence,
|
|
390
|
+
summary: item.recommendation.summary,
|
|
391
|
+
recommendation: item.recommendation
|
|
392
|
+
})),
|
|
393
|
+
...input.duplicates.map((item) => ({
|
|
394
|
+
kind: "duplicate",
|
|
395
|
+
name: item.name,
|
|
396
|
+
priority: item.recommendation.priority,
|
|
397
|
+
confidence: item.confidence,
|
|
398
|
+
summary: item.recommendation.summary,
|
|
399
|
+
recommendation: item.recommendation
|
|
400
|
+
})),
|
|
401
|
+
...input.outdated.map((item) => ({
|
|
402
|
+
kind: "outdated",
|
|
403
|
+
name: item.name,
|
|
404
|
+
package: item.package,
|
|
405
|
+
priority: item.recommendation.priority,
|
|
406
|
+
confidence: item.confidence,
|
|
407
|
+
summary: item.recommendation.summary,
|
|
408
|
+
recommendation: item.recommendation
|
|
409
|
+
})),
|
|
410
|
+
...input.risks.map((item) => ({
|
|
411
|
+
kind: "risk",
|
|
412
|
+
name: item.name,
|
|
413
|
+
package: item.package,
|
|
414
|
+
priority: item.recommendation.priority,
|
|
415
|
+
confidence: item.confidence,
|
|
416
|
+
summary: item.recommendation.summary,
|
|
417
|
+
recommendation: item.recommendation
|
|
418
|
+
}))
|
|
419
|
+
];
|
|
420
|
+
return issues
|
|
421
|
+
.sort((left, right) => comparePriority(right.priority, left.priority) || right.confidence - left.confidence)
|
|
422
|
+
.slice(0, 5);
|
|
423
|
+
}
|
|
424
|
+
function comparePriority(left, right) {
|
|
425
|
+
const rank = { high: 3, medium: 2, low: 1 };
|
|
426
|
+
return rank[left] - rank[right];
|
|
427
|
+
}
|
|
428
|
+
function normalizeConfidence(value) {
|
|
429
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
430
|
+
return 0.5;
|
|
431
|
+
}
|
|
432
|
+
return Math.min(0.99, Math.max(0, Number(value.toFixed(2))));
|
|
433
|
+
}
|
|
434
|
+
function normalizeStringArray(value) {
|
|
435
|
+
if (!Array.isArray(value)) {
|
|
436
|
+
return [];
|
|
437
|
+
}
|
|
438
|
+
return value.filter((entry) => typeof entry === "string");
|
|
439
|
+
}
|
|
292
440
|
function buildScoreBreakdown(counts, config) {
|
|
293
441
|
return {
|
|
294
442
|
baseScore: 100,
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
export type IssueSeverity = "info" | "warning" | "critical";
|
|
2
|
+
export type ReasonCode = string;
|
|
2
3
|
export type Issue = {
|
|
3
4
|
id: string;
|
|
4
5
|
message: string;
|
|
5
6
|
package?: string;
|
|
6
7
|
severity: IssueSeverity;
|
|
8
|
+
confidence?: number;
|
|
9
|
+
reasonCodes?: ReasonCode[];
|
|
10
|
+
explanation?: string[];
|
|
7
11
|
meta?: Record<string, unknown>;
|
|
8
12
|
};
|
|
9
13
|
export type CheckResult = {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { analyzeProject } from "./core/analyzer.js";
|
|
2
|
-
export type { AnalysisOptions, AnalysisResult, DuplicateDependency, OutdatedDependency, PolicyResult, PackageAnalysisResult, ScoreBreakdown, RiskDependency, UnusedDependency } from "./core/analyzer.js";
|
|
2
|
+
export type { AnalysisOptions, AnalysisResult, DuplicateDependency, OutdatedDependency, PolicyResult, PackageAnalysisResult, Recommendation, ScoreBreakdown, RiskDependency, TopIssue, UnusedDependency } from "./core/analyzer.js";
|
|
3
3
|
export { OUTPUT_VERSION } from "./core/analyzer.js";
|
|
4
4
|
export type { AnalysisContext, CheckResult, Issue } from "./core/types.js";
|
|
5
5
|
export type { DepBrainConfig, DepBrainConfigOverrides } from "./utils/config.js";
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
export function renderConsoleReport(result) {
|
|
2
2
|
const lines = [];
|
|
3
|
+
if (result.topIssues.length > 0) {
|
|
4
|
+
lines.push("Top Issues:");
|
|
5
|
+
for (const item of result.topIssues) {
|
|
6
|
+
lines.push(`- [${item.priority.toUpperCase()}] ${item.kind} ${item.name}${item.package ? ` [${item.package}]` : ""} | confidence ${Math.round(item.confidence * 100)}% | ${item.summary}`);
|
|
7
|
+
}
|
|
8
|
+
lines.push("");
|
|
9
|
+
}
|
|
3
10
|
lines.push(`Project Health: ${result.score}/100`);
|
|
4
11
|
lines.push(`Path: ${result.rootDir}`);
|
|
5
12
|
lines.push(`Policy: ${result.policy.passed ? "PASS" : "FAIL"}`);
|
|
@@ -16,16 +23,16 @@ export function renderConsoleReport(result) {
|
|
|
16
23
|
lines.push(`- ${pkg.name}: ${pkg.score}/100, D:${pkg.duplicates.length} U:${pkg.unused.length} O:${pkg.outdated.length} R:${pkg.risks.length}`);
|
|
17
24
|
}
|
|
18
25
|
}
|
|
19
|
-
appendSection(lines, "Duplicate dependencies", result.duplicates.map((item) => `${item.name}: ${item.versions.join(", ")}
|
|
20
|
-
appendSection(lines, "Unused dependencies", result.unused.map((item) => item.package
|
|
26
|
+
appendSection(lines, "Duplicate dependencies", result.duplicates.map((item) => formatEntry(`${item.name}: ${item.versions.join(", ")}`, item.confidence, item.explanation, item.recommendation)));
|
|
27
|
+
appendSection(lines, "Unused dependencies", result.unused.map((item) => formatEntry(item.package
|
|
21
28
|
? `${item.name} (${item.section}) [${item.package}]`
|
|
22
|
-
: `${item.name} (${item.section})
|
|
23
|
-
appendSection(lines, "Outdated dependencies", result.outdated.map((item) => item.package
|
|
29
|
+
: `${item.name} (${item.section})`, item.confidence, item.explanation, item.recommendation)));
|
|
30
|
+
appendSection(lines, "Outdated dependencies", result.outdated.map((item) => formatEntry(item.package
|
|
24
31
|
? `${item.name}: ${item.current} -> ${item.latest} [${item.updateType}] [${item.package}]`
|
|
25
|
-
: `${item.name}: ${item.current} -> ${item.latest} [${item.updateType}]
|
|
26
|
-
appendSection(lines, "Risky dependencies", result.risks.map((item) => item.package
|
|
32
|
+
: `${item.name}: ${item.current} -> ${item.latest} [${item.updateType}]`, item.confidence, item.explanation, item.recommendation)));
|
|
33
|
+
appendSection(lines, "Risky dependencies", result.risks.map((item) => formatEntry(item.package
|
|
27
34
|
? `${item.name}: ${item.reasons.join("; ")} [${item.package}]`
|
|
28
|
-
: `${item.name}: ${item.reasons.join("; ")}
|
|
35
|
+
: `${item.name}: ${item.reasons.join("; ")}`, item.confidence, item.explanation, item.recommendation)));
|
|
29
36
|
appendSection(lines, "Policy reasons", result.policy.reasons);
|
|
30
37
|
if (result.suggestions.length > 0) {
|
|
31
38
|
lines.push("");
|
|
@@ -50,3 +57,10 @@ function appendSection(lines, title, entries) {
|
|
|
50
57
|
lines.push(`- ${entry}`);
|
|
51
58
|
}
|
|
52
59
|
}
|
|
60
|
+
function formatEntry(label, confidence, explanation, recommendation) {
|
|
61
|
+
const reasonSummary = explanation.length > 0 ? ` | why: ${explanation.join("; ")}` : "";
|
|
62
|
+
const recommendationSummary = recommendation
|
|
63
|
+
? ` | next: ${recommendation.summary}`
|
|
64
|
+
: "";
|
|
65
|
+
return `${label} | confidence ${Math.round(confidence * 100)}%${recommendationSummary}${reasonSummary}`;
|
|
66
|
+
}
|
|
@@ -2,6 +2,13 @@ export function renderMarkdownReport(result) {
|
|
|
2
2
|
const lines = [];
|
|
3
3
|
lines.push(`# Dependency Brain Report`);
|
|
4
4
|
lines.push("");
|
|
5
|
+
if (result.topIssues.length > 0) {
|
|
6
|
+
lines.push("## Top Issues");
|
|
7
|
+
for (const item of result.topIssues) {
|
|
8
|
+
lines.push(`- **${item.priority.toUpperCase()}** ${item.kind} \`${item.name}\`${item.package ? ` [${item.package}]` : ""} | confidence ${Math.round(item.confidence * 100)}% | ${item.summary}`);
|
|
9
|
+
}
|
|
10
|
+
lines.push("");
|
|
11
|
+
}
|
|
5
12
|
lines.push(`- **Project Health:** ${result.score}/100`);
|
|
6
13
|
lines.push(`- **Path:** ${result.rootDir}`);
|
|
7
14
|
lines.push(`- **Policy:** ${result.policy.passed ? "PASS" : "FAIL"}`);
|
|
@@ -20,16 +27,16 @@ export function renderMarkdownReport(result) {
|
|
|
20
27
|
lines.push(`- Outdated: ${result.outdated.length}`);
|
|
21
28
|
lines.push(`- Risks: ${result.risks.length}`);
|
|
22
29
|
lines.push("");
|
|
23
|
-
appendSection(lines, "Duplicate dependencies", result.duplicates.map((item) => `${item.name}: ${item.versions.join(", ")}
|
|
24
|
-
appendSection(lines, "Unused dependencies", result.unused.map((item) => item.package
|
|
30
|
+
appendSection(lines, "Duplicate dependencies", result.duplicates.map((item) => formatEntry(`${item.name}: ${item.versions.join(", ")}`, item.confidence, item.explanation, item.recommendation)));
|
|
31
|
+
appendSection(lines, "Unused dependencies", result.unused.map((item) => formatEntry(item.package
|
|
25
32
|
? `${item.name} (${item.section}) [${item.package}]`
|
|
26
|
-
: `${item.name} (${item.section})
|
|
27
|
-
appendSection(lines, "Outdated dependencies", result.outdated.map((item) => item.package
|
|
33
|
+
: `${item.name} (${item.section})`, item.confidence, item.explanation, item.recommendation)));
|
|
34
|
+
appendSection(lines, "Outdated dependencies", result.outdated.map((item) => formatEntry(item.package
|
|
28
35
|
? `${item.name}: ${item.current} -> ${item.latest} [${item.updateType}] [${item.package}]`
|
|
29
|
-
: `${item.name}: ${item.current} -> ${item.latest} [${item.updateType}]
|
|
30
|
-
appendSection(lines, "Risky dependencies", result.risks.map((item) => item.package
|
|
36
|
+
: `${item.name}: ${item.current} -> ${item.latest} [${item.updateType}]`, item.confidence, item.explanation, item.recommendation)));
|
|
37
|
+
appendSection(lines, "Risky dependencies", result.risks.map((item) => formatEntry(item.package
|
|
31
38
|
? `${item.name}: ${item.reasons.join("; ")} [${item.package}]`
|
|
32
|
-
: `${item.name}: ${item.reasons.join("; ")}
|
|
39
|
+
: `${item.name}: ${item.reasons.join("; ")}`, item.confidence, item.explanation, item.recommendation)));
|
|
33
40
|
appendSection(lines, "Policy reasons", result.policy.reasons);
|
|
34
41
|
if (result.suggestions.length > 0) {
|
|
35
42
|
lines.push("## Suggestions");
|
|
@@ -50,3 +57,10 @@ function appendSection(lines, title, items) {
|
|
|
50
57
|
}
|
|
51
58
|
lines.push("");
|
|
52
59
|
}
|
|
60
|
+
function formatEntry(label, confidence, explanation, recommendation) {
|
|
61
|
+
const reasonSummary = explanation.length > 0 ? ` | why: ${explanation.join("; ")}` : "";
|
|
62
|
+
const recommendationSummary = recommendation
|
|
63
|
+
? ` | next: ${recommendation.summary}`
|
|
64
|
+
: "";
|
|
65
|
+
return `${label} | confidence ${Math.round(confidence * 100)}%${recommendationSummary}${reasonSummary}`;
|
|
66
|
+
}
|