dep-brain 1.0.2 → 1.1.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 CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 1.1.0
6
+
7
+ - Added `--focus` modes for targeted duplicate, unused, outdated, risk, and health analysis.
8
+ - Added `--ci` for low-noise CI defaults focused on duplicate and runtime risk enforcement.
9
+ - Added `dep-brain init` to generate a starter `depbrain.config.json`.
10
+ - Introduced capped health score deductions so large outdated/risk counts do not automatically collapse project health to `0/100`.
11
+ - Added GitHub Action inputs for `focus` and `ci`.
12
+ - Added regression coverage for focused analysis and capped scoring.
13
+
5
14
  ## 1.0.2
6
15
 
7
16
  - Treated npm `overrides` entries as intentional version pins so direct override packages are not flagged as unused.
package/README.md CHANGED
@@ -50,6 +50,9 @@ The long-term goal is not just to list problems, but to answer:
50
50
  - SARIF output via `--sarif`
51
51
  - Ranked top issues via `--top`
52
52
  - Baseline mode via `--baseline`
53
+ - Focused analysis via `--focus`
54
+ - Low-noise CI defaults via `--ci`
55
+ - Starter config generation via `dep-brain init`
53
56
  - Reusable GitHub Action via `action.yml`
54
57
  - Library entrypoint for programmatic use
55
58
 
@@ -70,10 +73,13 @@ npx dep-brain analyze ./path-to-project --fail-on-unused --json
70
73
  npx dep-brain analyze --md > depbrain.md
71
74
  npx dep-brain analyze --json --out depbrain.json
72
75
  npx dep-brain analyze --sarif --out depbrain.sarif
76
+ npx dep-brain analyze --focus duplicates
77
+ npx dep-brain analyze --ci
73
78
  npx dep-brain analyze --baseline depbrain-baseline.json
74
79
  npx dep-brain analyze --baseline depbrain-baseline.json --min-score 90 --fail-on-risks
75
80
  npx dep-brain report --from depbrain.json --md --out depbrain.md
76
81
 
82
+ dep-brain init
77
83
  dep-brain config
78
84
  dep-brain config --config depbrain.config.json
79
85
 
@@ -82,6 +88,34 @@ dep-brain analyze --help
82
88
  dep-brain --version
83
89
  ```
84
90
 
91
+ ## Focus Modes
92
+
93
+ Use `--focus` when you want a targeted signal instead of a full report:
94
+
95
+ ```bash
96
+ dep-brain analyze --focus duplicates
97
+ dep-brain analyze --focus health
98
+ dep-brain analyze --focus risks
99
+ ```
100
+
101
+ Supported values are `all`, `health`, `duplicates`, `unused`, `outdated`, and `risks`.
102
+
103
+ ## CI Preset
104
+
105
+ ```bash
106
+ dep-brain analyze --ci
107
+ ```
108
+
109
+ The CI preset applies low-noise defaults: a minimum score of `70`, failure on duplicates, and failure on risky dependencies.
110
+
111
+ ## Config Init
112
+
113
+ ```bash
114
+ dep-brain init
115
+ ```
116
+
117
+ Creates a starter `depbrain.config.json` with practical defaults for CI and common TypeScript/NestJS tooling.
118
+
85
119
  ## Workspaces
86
120
 
87
121
  If the root `package.json` defines `workspaces`, `dep-brain` analyzes each workspace package and reports per-package results. Aggregated counts are still shown at the top-level summary.
package/action.yml CHANGED
@@ -23,6 +23,14 @@ inputs:
23
23
  description: Optional baseline JSON report used to ignore existing findings.
24
24
  required: false
25
25
  default: ""
26
+ focus:
27
+ description: Analysis focus. Use all, health, duplicates, unused, outdated, or risks.
28
+ required: false
29
+ default: "all"
30
+ ci:
31
+ description: Apply low-noise CI defaults.
32
+ required: false
33
+ default: "false"
26
34
  min-score:
27
35
  description: Minimum project health score required to pass.
28
36
  required: false
@@ -55,6 +63,8 @@ runs:
55
63
  INPUT_OUT: ${{ inputs.out }}
56
64
  INPUT_CONFIG: ${{ inputs.config }}
57
65
  INPUT_BASELINE: ${{ inputs.baseline }}
66
+ INPUT_FOCUS: ${{ inputs.focus }}
67
+ INPUT_CI: ${{ inputs.ci }}
58
68
  INPUT_MIN_SCORE: ${{ inputs.min-score }}
59
69
  INPUT_FAIL_ON_UNUSED: ${{ inputs.fail-on-unused }}
60
70
  INPUT_FAIL_ON_OUTDATED: ${{ inputs.fail-on-outdated }}
@@ -89,6 +99,14 @@ runs:
89
99
  args+=("--baseline" "$INPUT_BASELINE")
90
100
  fi
91
101
 
102
+ if [ "$INPUT_FOCUS" != "all" ]; then
103
+ args+=("--focus" "$INPUT_FOCUS")
104
+ fi
105
+
106
+ if [ "$INPUT_CI" = "true" ]; then
107
+ args+=("--ci")
108
+ fi
109
+
92
110
  if [ -n "$INPUT_MIN_SCORE" ]; then
93
111
  args+=("--min-score" "$INPUT_MIN_SCORE")
94
112
  fi
package/dist/cli.js CHANGED
@@ -4,6 +4,7 @@ import { renderConsoleReport } from "./reporters/console.js";
4
4
  import { renderJsonReport } from "./reporters/json.js";
5
5
  import { renderMarkdownReport } from "./reporters/markdown.js";
6
6
  import { renderSarifReport } from "./reporters/sarif.js";
7
+ import { defaultConfig } from "./utils/config.js";
7
8
  import { promises as fs } from "node:fs";
8
9
  import path from "node:path";
9
10
  async function main() {
@@ -91,6 +92,22 @@ async function main() {
91
92
  return;
92
93
  }
93
94
  }
95
+ if (command === "init") {
96
+ try {
97
+ const outputPath = optionValues.get("--out") ?? "depbrain.config.json";
98
+ const resolvedOut = resolveUserPath(outputPath);
99
+ const config = buildStarterConfig();
100
+ await fs.writeFile(resolvedOut, `${JSON.stringify(config, null, 2)}\n`, "utf8");
101
+ console.log(`Created ${path.relative(process.cwd(), resolvedOut) || outputPath}`);
102
+ return;
103
+ }
104
+ catch (error) {
105
+ console.error("Failed to create config.");
106
+ console.error(error);
107
+ process.exitCode = 1;
108
+ return;
109
+ }
110
+ }
94
111
  console.error(`Unknown command: ${sanitizeForLog(command)}`);
95
112
  printHelp();
96
113
  process.exitCode = 1;
@@ -108,7 +125,8 @@ async function main() {
108
125
  rootDir: targetPath,
109
126
  configPath: optionValues.get("--config"),
110
127
  config: cliConfig,
111
- baseline
128
+ baseline,
129
+ focus: parseFocus(optionValues.get("--focus"))
112
130
  });
113
131
  let output;
114
132
  if (flags.has("--json")) {
@@ -169,6 +187,11 @@ function buildCliConfig(flags, optionValues) {
169
187
  if (flags.has("--fail-on-unused")) {
170
188
  policy.failOnUnused = true;
171
189
  }
190
+ if (flags.has("--ci")) {
191
+ policy.minScore = policy.minScore ?? 70;
192
+ policy.failOnDuplicates = true;
193
+ policy.failOnRisks = true;
194
+ }
172
195
  return {
173
196
  policy
174
197
  };
@@ -186,9 +209,10 @@ function printHelp() {
186
209
  console.log("Dependency Brain");
187
210
  console.log("");
188
211
  console.log("Usage:");
189
- console.log(" dep-brain analyze [path] [--json] [--md] [--sarif] [--top] [--out path] [--config path] [--baseline path] [--min-score n] [--fail-on-risks]");
212
+ console.log(" dep-brain analyze [path] [--json] [--md] [--sarif] [--top] [--focus kind] [--ci] [--out path] [--config path] [--baseline path] [--min-score n] [--fail-on-risks]");
190
213
  console.log(" dep-brain report --from <file> [--md] [--json] [--sarif] [--top] [--out path]");
191
214
  console.log(" dep-brain config [path] [--config path]");
215
+ console.log(" dep-brain init [--out depbrain.config.json]");
192
216
  console.log(" dep-brain help");
193
217
  console.log(" dep-brain --version");
194
218
  console.log("");
@@ -197,6 +221,8 @@ function printHelp() {
197
221
  console.log(" --md Output Markdown report");
198
222
  console.log(" --sarif Output SARIF format for Code Scanning");
199
223
  console.log(" --top Output the ranked top issues only");
224
+ console.log(" --focus <kind> Run all, health, duplicates, unused, outdated, or risks");
225
+ console.log(" --ci Apply low-noise CI defaults");
200
226
  console.log(" --config <path> Path to depbrain.config.json");
201
227
  console.log(" --baseline <path> Ignore findings already present in a baseline JSON report");
202
228
  console.log(" --from <file> Read analysis JSON from file");
@@ -209,6 +235,45 @@ function printHelp() {
209
235
  console.log(" --help Show this help output");
210
236
  console.log(" --version Show CLI version");
211
237
  }
238
+ function parseFocus(value) {
239
+ if (value === "duplicates" ||
240
+ value === "unused" ||
241
+ value === "outdated" ||
242
+ value === "risks" ||
243
+ value === "health" ||
244
+ value === "all") {
245
+ return value;
246
+ }
247
+ return "all";
248
+ }
249
+ function buildStarterConfig() {
250
+ return {
251
+ ...defaultConfig,
252
+ ignore: {
253
+ ...defaultConfig.ignore,
254
+ unused: [
255
+ "@nestjs/platform-express",
256
+ "reflect-metadata",
257
+ "source-map-support",
258
+ "ts-loader",
259
+ "ts-node",
260
+ "tsconfig-paths"
261
+ ]
262
+ },
263
+ policy: {
264
+ ...defaultConfig.policy,
265
+ minScore: 70,
266
+ failOnDuplicates: true,
267
+ failOnRisks: true
268
+ },
269
+ scoring: {
270
+ duplicateWeight: 8,
271
+ outdatedWeight: 1,
272
+ unusedWeight: 2,
273
+ riskWeight: 4
274
+ }
275
+ };
276
+ }
212
277
  async function loadPackageVersion() {
213
278
  try {
214
279
  const pkgPath = new URL("../package.json", import.meta.url);
@@ -4,7 +4,9 @@ export interface AnalysisOptions {
4
4
  configPath?: string;
5
5
  config?: DepBrainConfigOverrides;
6
6
  baseline?: DepBrainBaseline;
7
+ focus?: AnalysisFocus;
7
8
  }
9
+ export type AnalysisFocus = "all" | "duplicates" | "unused" | "outdated" | "risks" | "health";
8
10
  export interface DepBrainBaseline {
9
11
  duplicates?: Array<Partial<Pick<DuplicateDependency, "name">>>;
10
12
  unused?: Array<Partial<Pick<UnusedDependency, "name" | "section" | "package">>>;
@@ -6,27 +6,30 @@ import { runUnusedCheck } from "../checks/unused.js";
6
6
  import { loadDepBrainConfig } from "../utils/config.js";
7
7
  import { findWorkspacePackages } from "../utils/workspaces.js";
8
8
  import { buildDependencyGraph } from "./graph-builder.js";
9
- import { calculateHealthScore } from "./scorer.js";
9
+ import { calculateHealthScore, calculateScoreDeductions } from "./scorer.js";
10
10
  import { buildAnalysisContext } from "./context.js";
11
11
  export const OUTPUT_VERSION = "1.4";
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);
15
15
  const config = mergeConfig(loadedConfig, options.config);
16
+ const focus = options.focus ?? "all";
16
17
  const workspaces = await findWorkspacePackages(rootDir);
17
18
  if (workspaces.length === 0) {
18
19
  return analyzeSingleProject(rootDir, config, {
19
- baseline: options.baseline
20
+ baseline: options.baseline,
21
+ focus
20
22
  });
21
23
  }
22
24
  const rootGraph = await buildDependencyGraph(rootDir);
23
- const duplicateCheck = await runDuplicateCheck(rootGraph);
24
- const filteredDuplicateIssues = filterIssues(duplicateCheck.issues, "duplicates", config);
25
- const duplicates = mapDuplicateIssues(filteredDuplicateIssues);
25
+ const duplicates = shouldRunCheck("duplicate", focus)
26
+ ? mapDuplicateIssues(filterIssues((await runDuplicateCheck(rootGraph)).issues, "duplicates", config))
27
+ : [];
26
28
  const packages = [];
27
29
  for (const workspace of workspaces) {
28
30
  const result = await analyzeSingleProject(workspace.rootDir, config, {
29
- packageName: workspace.name
31
+ packageName: workspace.name,
32
+ focus
30
33
  });
31
34
  packages.push({ ...result, name: workspace.name });
32
35
  }
@@ -163,7 +166,7 @@ function evaluatePolicy(summary, config) {
163
166
  }
164
167
  async function analyzeSingleProject(rootDir, config, options = {}) {
165
168
  const context = await buildAnalysisContext(rootDir, config);
166
- const results = await runChecks(context);
169
+ const results = await runChecks(context, options.focus ?? "all");
167
170
  const issueGroups = normalizeIssues(results, config);
168
171
  const duplicates = mapDuplicateIssues(issueGroups.duplicates);
169
172
  const unused = mapUnusedIssues(issueGroups.unused);
@@ -255,7 +258,7 @@ function shouldIgnorePackage(name, bucket, config) {
255
258
  }
256
259
  });
257
260
  }
258
- async function runChecks(context) {
261
+ async function runChecks(context, focus) {
259
262
  const checks = [
260
263
  {
261
264
  name: "duplicate",
@@ -276,10 +279,21 @@ async function runChecks(context) {
276
279
  ];
277
280
  const results = [];
278
281
  for (const check of checks) {
279
- results.push(await check.run());
282
+ if (shouldRunCheck(check.name, focus)) {
283
+ results.push(await check.run());
284
+ }
280
285
  }
281
286
  return results;
282
287
  }
288
+ function shouldRunCheck(checkName, focus) {
289
+ if (focus === "all" || focus === "health") {
290
+ return true;
291
+ }
292
+ return ((focus === "duplicates" && checkName === "duplicate") ||
293
+ (focus === "unused" && checkName === "unused") ||
294
+ (focus === "outdated" && checkName === "outdated") ||
295
+ (focus === "risks" && checkName === "risk"));
296
+ }
283
297
  function normalizeIssues(results, config) {
284
298
  const map = new Map();
285
299
  for (const result of results) {
@@ -603,12 +617,22 @@ function normalizeWorkspaceUsage(value) {
603
617
  .filter((entry) => entry !== null);
604
618
  }
605
619
  function buildScoreBreakdown(counts, config) {
620
+ const deductions = calculateScoreDeductions({
621
+ duplicates: counts.duplicates,
622
+ outdated: counts.outdated,
623
+ unused: counts.unused,
624
+ risks: counts.risks,
625
+ duplicateWeight: config.scoring.duplicateWeight,
626
+ outdatedWeight: config.scoring.outdatedWeight,
627
+ unusedWeight: config.scoring.unusedWeight,
628
+ riskWeight: config.scoring.riskWeight
629
+ });
606
630
  return {
607
- baseScore: 100,
608
- duplicates: counts.duplicates * config.scoring.duplicateWeight,
609
- outdated: counts.outdated * config.scoring.outdatedWeight,
610
- unused: counts.unused * config.scoring.unusedWeight,
611
- risks: counts.risks * config.scoring.riskWeight,
631
+ baseScore: deductions.baseScore,
632
+ duplicates: deductions.duplicates,
633
+ outdated: deductions.outdated,
634
+ unused: deductions.unused,
635
+ risks: deductions.risks,
612
636
  weights: {
613
637
  duplicateWeight: config.scoring.duplicateWeight,
614
638
  outdatedWeight: config.scoring.outdatedWeight,
@@ -9,3 +9,10 @@ export interface ScoreInputs {
9
9
  riskWeight?: number;
10
10
  }
11
11
  export declare function calculateHealthScore(inputs: ScoreInputs): number;
12
+ export declare function calculateScoreDeductions(inputs: ScoreInputs): {
13
+ baseScore: number;
14
+ duplicates: number;
15
+ outdated: number;
16
+ unused: number;
17
+ risks: number;
18
+ };
@@ -1,12 +1,21 @@
1
1
  export function calculateHealthScore(inputs) {
2
+ const breakdown = calculateScoreDeductions(inputs);
3
+ return Math.max(0, breakdown.baseScore -
4
+ breakdown.duplicates -
5
+ breakdown.outdated -
6
+ breakdown.unused -
7
+ breakdown.risks);
8
+ }
9
+ export function calculateScoreDeductions(inputs) {
2
10
  const duplicateWeight = inputs.duplicateWeight ?? 5;
3
11
  const outdatedWeight = inputs.outdatedWeight ?? 3;
4
12
  const unusedWeight = inputs.unusedWeight ?? 4;
5
13
  const riskWeight = inputs.riskWeight ?? 10;
6
- const rawScore = 100 -
7
- inputs.duplicates * duplicateWeight -
8
- inputs.outdated * outdatedWeight -
9
- inputs.unused * unusedWeight -
10
- inputs.risks * riskWeight;
11
- return Math.max(0, rawScore);
14
+ return {
15
+ baseScore: 100,
16
+ duplicates: Math.min(35, inputs.duplicates * duplicateWeight),
17
+ outdated: Math.min(25, inputs.outdated * outdatedWeight),
18
+ unused: Math.min(20, inputs.unused * unusedWeight),
19
+ risks: Math.min(30, inputs.risks * riskWeight)
20
+ };
12
21
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { analyzeProject } from "./core/analyzer.js";
2
- export type { AnalysisOptions, AnalysisResult, DepBrainBaseline, DuplicateDependency, OutdatedDependency, PolicyResult, PackageAnalysisResult, Recommendation, RiskFactors, ScoreBreakdown, RiskDependency, TopIssue, TrustScore, UnusedDependency, WorkspaceDependencyUsage, WorkspaceOwnershipSummary } from "./core/analyzer.js";
2
+ export type { AnalysisOptions, AnalysisFocus, AnalysisResult, DepBrainBaseline, DuplicateDependency, OutdatedDependency, PolicyResult, PackageAnalysisResult, Recommendation, RiskFactors, ScoreBreakdown, RiskDependency, TopIssue, TrustScore, UnusedDependency, WorkspaceDependencyUsage, WorkspaceOwnershipSummary } 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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dep-brain",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "CLI and library for explainable dependency intelligence",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",