dep-brain 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 1.6.0
6
+
7
+ - Added Slack and Discord webhook notification summaries.
8
+ - Added `--notify` and `--notify-on` CLI controls.
9
+ - Added notification config with `enabled`, `on`, `slackWebhookEnv`, and `discordWebhookEnv`.
10
+ - Added GitHub Action inputs for notification runs.
11
+ - Kept analysis output contract at `1.6` because no JSON result fields changed.
12
+
5
13
  ## 1.5.0
6
14
 
7
15
  - Added structured upgrade advisor data under `outdated[].advice`.
package/README.md CHANGED
@@ -6,6 +6,8 @@
6
6
 
7
7
  `dep-brain` is a CLI and library for explainable dependency intelligence in JavaScript and TypeScript projects.
8
8
 
9
+ Current release `1.6.0` adds Slack and Discord notification summaries while keeping analysis output contract `1.6`.
10
+
9
11
  ## Vision
10
12
 
11
13
  `dep-brain` aims to become a dependency decision engine:
@@ -26,6 +28,7 @@
26
28
  - Generate a simple project health score
27
29
  - Output reports in console, JSON, Markdown, SARIF, dashboard, and top-issues formats
28
30
  - Output upgrade-advice reports via `--advise`
31
+ - Send Slack and Discord webhook summaries for CI runs
29
32
  - Gate CI with score and finding policies
30
33
  - Compare new findings against a baseline report
31
34
 
@@ -35,7 +38,7 @@ The long-term goal is not just to list problems, but to answer:
35
38
  - Can I remove it safely?
36
39
  - What should I fix first?
37
40
 
38
- ## v1 Features
41
+ ## 1.6 Highlights
39
42
 
40
43
  - Duplicate dependency detection with lockfile instance tracking
41
44
  - Unused dependency detection with runtime vs dev-tool heuristics
@@ -53,6 +56,7 @@ The long-term goal is not just to list problems, but to answer:
53
56
  - SARIF output via `--sarif`
54
57
  - Static HTML dashboard via `--dashboard`
55
58
  - Upgrade advisor output via `--advise`
59
+ - Slack and Discord notification summaries via `--notify`
56
60
  - Ranked top issues via `--top`
57
61
  - Baseline mode via `--baseline`
58
62
  - Focused analysis via `--focus`
@@ -82,6 +86,8 @@ npx dep-brain analyze --json --out depbrain.json
82
86
  npx dep-brain analyze --sarif --out depbrain.sarif
83
87
  npx dep-brain analyze --dashboard
84
88
  npx dep-brain analyze --dashboard --dashboard-out reports/depbrain.html
89
+ npx dep-brain analyze --notify
90
+ npx dep-brain analyze --notify --notify-on always
85
91
  npx dep-brain analyze --focus duplicates
86
92
  npx dep-brain analyze --ci
87
93
  npx dep-brain analyze --baseline depbrain-baseline.json
@@ -167,7 +173,9 @@ Suggestions:
167
173
  dep-brain analyze --json
168
174
  ```
169
175
 
170
- Output includes `outputVersion` for schema stability and can be validated with:
176
+ Output includes `outputVersion` for schema stability. `dep-brain@1.6.0` writes contract version `1.6`.
177
+
178
+ Validate against:
171
179
 
172
180
  - `depbrain.output.schema.json`
173
181
 
@@ -202,6 +210,15 @@ dep-brain analyze --dashboard --dashboard-out reports/depbrain.html
202
210
 
203
211
  Writes a static HTML dashboard. Default path comes from `dashboard.outputPath`.
204
212
 
213
+ ## Notifications
214
+
215
+ ```bash
216
+ DEPBRAIN_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/... dep-brain analyze --notify
217
+ DEPBRAIN_DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... dep-brain analyze --notify --notify-on always
218
+ ```
219
+
220
+ `--notify` sends compact summaries to configured Slack and Discord webhook URLs. Default trigger is `failure`, so passing runs stay quiet unless `--notify-on always` is set.
221
+
205
222
  ## Plugins
206
223
 
207
224
  ```json
@@ -272,6 +289,8 @@ Create a `depbrain.config.json` file in the project root:
272
289
  "outputPath": "depbrain-dashboard.html"
273
290
  },
274
291
  "notifications": {
292
+ "enabled": false,
293
+ "on": "failure",
275
294
  "slackWebhookEnv": "DEPBRAIN_SLACK_WEBHOOK_URL",
276
295
  "discordWebhookEnv": "DEPBRAIN_DISCORD_WEBHOOK_URL"
277
296
  },
@@ -311,6 +330,8 @@ Supported sections:
311
330
  - `risk.lowTrustWeightThreshold`
312
331
  - `risk.mediumTrustWeightThreshold`
313
332
  - `dashboard.outputPath`
333
+ - `notifications.enabled`
334
+ - `notifications.on`
314
335
  - `notifications.slackWebhookEnv`
315
336
  - `notifications.discordWebhookEnv`
316
337
  - `scoring.duplicateWeight`
@@ -338,6 +359,7 @@ dep-brain analyze --fail-on-unused
338
359
  dep-brain analyze --min-score 85 --fail-on-risks
339
360
  dep-brain analyze --config depbrain.config.json
340
361
  dep-brain analyze --baseline depbrain-baseline.json --fail-on-unused
362
+ dep-brain analyze --ci --notify
341
363
  ```
342
364
 
343
365
  ## Config Debugging
@@ -384,17 +406,19 @@ src/
384
406
 
385
407
  ## Product Direction
386
408
 
387
- `dep-brain` is in its `v1.0.0` production-ready CLI stage. The roadmap delivered through v1:
409
+ `dep-brain` is in `v1.6.0` production CLI stage, with current focus on actionable dependency decisions and CI workflow integration.
410
+
411
+ Recent releases added:
388
412
 
389
- - `v0.6`: explainability and confidence scoring
390
- - `v0.7`: safe removal guidance and actionable recommendations
391
- - `v0.8`: supply-chain trust and risk intelligence
392
- - `v0.9`: deeper monorepo and ownership intelligence
393
- - `v1.0`: stable CI, ecosystem exports, and production readiness
413
+ - transitive risk ownership and path tracing
414
+ - dashboard and plugin support
415
+ - baseline, focus, and CI workflows
416
+ - structured upgrade advice with release-note links
417
+ - Slack and Discord notification summaries
394
418
 
395
- The project should optimize for trust, clarity, and actionability over flashy UI, generic graphs, or simply adding more checks.
419
+ Project should optimize for trust, clarity, and actionability over flashy UI, generic graphs, or simply adding more checks.
396
420
 
397
- Risk findings now include a `trustScore`, structured `riskFactors`, `transitiveRiskScore`, and `riskyTransitiveDeps` path traces so teams can see which direct package introduces supply-chain risk.
421
+ Risk findings include `trustScore`, structured `riskFactors`, `transitiveRiskScore`, and `riskyTransitiveDeps` path traces so teams can see which direct package introduces supply-chain risk.
398
422
 
399
423
  ## Repository Notes
400
424
 
package/action.yml CHANGED
@@ -51,6 +51,14 @@ inputs:
51
51
  description: Fail when risky dependencies are found.
52
52
  required: false
53
53
  default: "false"
54
+ notify:
55
+ description: Send Slack or Discord webhook summaries.
56
+ required: false
57
+ default: "false"
58
+ notify-on:
59
+ description: Notification trigger. Use always, failure, or never.
60
+ required: false
61
+ default: "failure"
54
62
 
55
63
  runs:
56
64
  using: composite
@@ -70,6 +78,8 @@ runs:
70
78
  INPUT_FAIL_ON_OUTDATED: ${{ inputs.fail-on-outdated }}
71
79
  INPUT_FAIL_ON_DUPLICATES: ${{ inputs.fail-on-duplicates }}
72
80
  INPUT_FAIL_ON_RISKS: ${{ inputs.fail-on-risks }}
81
+ INPUT_NOTIFY: ${{ inputs.notify }}
82
+ INPUT_NOTIFY_ON: ${{ inputs.notify-on }}
73
83
  run: |
74
84
  set -euo pipefail
75
85
 
@@ -127,6 +137,10 @@ runs:
127
137
  args+=("--fail-on-risks")
128
138
  fi
129
139
 
140
+ if [ "$INPUT_NOTIFY" = "true" ]; then
141
+ args+=("--notify" "--notify-on" "$INPUT_NOTIFY_ON")
142
+ fi
143
+
130
144
  node "$GITHUB_ACTION_PATH/dist/cli.js" "${args[@]}"
131
145
 
132
146
  branding:
@@ -36,6 +36,8 @@
36
36
  "outputPath": "depbrain-dashboard.html"
37
37
  },
38
38
  "notifications": {
39
+ "enabled": false,
40
+ "on": "failure",
39
41
  "slackWebhookEnv": "DEPBRAIN_SLACK_WEBHOOK_URL",
40
42
  "discordWebhookEnv": "DEPBRAIN_DISCORD_WEBHOOK_URL"
41
43
  },
@@ -68,6 +68,8 @@
68
68
  "type": "object",
69
69
  "additionalProperties": false,
70
70
  "properties": {
71
+ "enabled": { "type": "boolean" },
72
+ "on": { "type": "string", "enum": ["always", "failure", "never"] },
71
73
  "slackWebhookEnv": { "type": "string" },
72
74
  "discordWebhookEnv": { "type": "string" }
73
75
  }
package/dist/cli.js CHANGED
@@ -5,6 +5,7 @@ import { renderJsonReport } from "./reporters/json.js";
5
5
  import { renderMarkdownReport } from "./reporters/markdown.js";
6
6
  import { renderSarifReport } from "./reporters/sarif.js";
7
7
  import { renderDashboardReport } from "./reporters/dashboard.js";
8
+ import { sendConfiguredNotifications } from "./utils/notifications.js";
8
9
  import { defaultConfig } from "./utils/config.js";
9
10
  import { promises as fs } from "node:fs";
10
11
  import path from "node:path";
@@ -160,6 +161,20 @@ async function main() {
160
161
  if (flags.has("--dashboard")) {
161
162
  await writeOutput(renderDashboardReport(result), optionValues.get("--dashboard-out") ?? result.config.dashboard.outputPath);
162
163
  }
164
+ if (result.config.notifications.enabled) {
165
+ const notificationResults = await sendConfiguredNotifications(result);
166
+ for (const item of notificationResults) {
167
+ if (item.status === "sent") {
168
+ console.error(`Notification sent to ${item.channel}.`);
169
+ }
170
+ else if (item.status === "failed") {
171
+ console.error(`Notification failed for ${item.channel}: ${item.reason ?? "unknown error"}`);
172
+ }
173
+ else if (item.reason?.includes("webhook env")) {
174
+ console.error(`Notification skipped for ${item.channel}: ${item.reason}`);
175
+ }
176
+ }
177
+ }
163
178
  if (!result.policy.passed) {
164
179
  process.exitCode = 1;
165
180
  }
@@ -183,6 +198,7 @@ void main();
183
198
  function buildCliConfig(flags, optionValues) {
184
199
  const minScore = optionValues.get("--min-score");
185
200
  const policy = {};
201
+ const notifications = {};
186
202
  if (minScore) {
187
203
  policy.minScore = Number(minScore);
188
204
  }
@@ -203,8 +219,18 @@ function buildCliConfig(flags, optionValues) {
203
219
  policy.failOnDuplicates = true;
204
220
  policy.failOnRisks = true;
205
221
  }
222
+ if (flags.has("--notify")) {
223
+ notifications.enabled = true;
224
+ }
225
+ const notifyOn = optionValues.get("--notify-on");
226
+ if (notifyOn === "always" ||
227
+ notifyOn === "failure" ||
228
+ notifyOn === "never") {
229
+ notifications.on = notifyOn;
230
+ }
206
231
  return {
207
- policy
232
+ policy,
233
+ notifications
208
234
  };
209
235
  }
210
236
  async function hasPackageJson(targetPath) {
@@ -220,7 +246,7 @@ function printHelp() {
220
246
  console.log("Dependency Brain");
221
247
  console.log("");
222
248
  console.log("Usage:");
223
- console.log(" dep-brain analyze [path] [--json] [--md] [--sarif] [--top] [--dashboard] [--focus kind] [--ci] [--out path] [--config path] [--baseline path] [--min-score n] [--fail-on-risks]");
249
+ console.log(" dep-brain analyze [path] [--json] [--md] [--sarif] [--top] [--dashboard] [--notify] [--notify-on kind] [--focus kind] [--ci] [--out path] [--config path] [--baseline path] [--min-score n] [--fail-on-risks]");
224
250
  console.log(" dep-brain report --from <file> [--md] [--json] [--sarif] [--top] [--advise] [--dashboard] [--out path]");
225
251
  console.log(" dep-brain config [path] [--config path]");
226
252
  console.log(" dep-brain init [--out depbrain.config.json]");
@@ -235,6 +261,8 @@ function printHelp() {
235
261
  console.log(" --advise Output upgrade advice for outdated dependencies");
236
262
  console.log(" --dashboard Write an HTML dashboard");
237
263
  console.log(" --dashboard-out <path> Write dashboard HTML to a custom path");
264
+ console.log(" --notify Send Slack or Discord webhook summaries");
265
+ console.log(" --notify-on <kind> Send notifications on always, failure, or never");
238
266
  console.log(" --focus <kind> Run all, health, duplicates, unused, outdated, or risks");
239
267
  console.log(" --ci Apply low-noise CI defaults");
240
268
  console.log(" --config <path> Path to depbrain.config.json");
@@ -154,6 +154,8 @@ function mergeConfig(base, overrides) {
154
154
  outputPath: overrides.dashboard?.outputPath ?? base.dashboard.outputPath
155
155
  },
156
156
  notifications: {
157
+ enabled: overrides.notifications?.enabled ?? base.notifications.enabled,
158
+ on: overrides.notifications?.on ?? base.notifications.on,
157
159
  slackWebhookEnv: overrides.notifications?.slackWebhookEnv ?? base.notifications.slackWebhookEnv,
158
160
  discordWebhookEnv: overrides.notifications?.discordWebhookEnv ?? base.notifications.discordWebhookEnv
159
161
  },
package/dist/index.d.ts CHANGED
@@ -5,4 +5,6 @@ export { PluginManager } from "./core/plugin-manager.js";
5
5
  export type { DepBrainPlugin, PluginDiagnostic, ProjectContext } from "./core/plugin-manager.js";
6
6
  export type { AnalysisContext, CheckResult, Issue } from "./core/types.js";
7
7
  export type { DepBrainConfig, DepBrainConfigOverrides } from "./utils/config.js";
8
+ export { renderNotificationMessage, sendConfiguredNotifications, shouldSendNotification } from "./utils/notifications.js";
9
+ export type { NotificationChannel, NotificationResult, NotificationSendInput, NotificationSender } from "./utils/notifications.js";
8
10
  export type { WorkspacePackage } from "./utils/workspaces.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { analyzeProject } from "./core/analyzer.js";
2
2
  export { OUTPUT_VERSION } from "./core/analyzer.js";
3
3
  export { PluginManager } from "./core/plugin-manager.js";
4
+ export { renderNotificationMessage, sendConfiguredNotifications, shouldSendNotification } from "./utils/notifications.js";
@@ -36,6 +36,8 @@ export interface DepBrainConfig {
36
36
  outputPath: string;
37
37
  };
38
38
  notifications: {
39
+ enabled: boolean;
40
+ on: "always" | "failure" | "never";
39
41
  slackWebhookEnv: string;
40
42
  discordWebhookEnv: string;
41
43
  };
@@ -38,6 +38,8 @@ export const defaultConfig = {
38
38
  outputPath: "depbrain-dashboard.html"
39
39
  },
40
40
  notifications: {
41
+ enabled: false,
42
+ on: "failure",
41
43
  slackWebhookEnv: "DEPBRAIN_SLACK_WEBHOOK_URL",
42
44
  discordWebhookEnv: "DEPBRAIN_DISCORD_WEBHOOK_URL"
43
45
  },
@@ -100,6 +102,8 @@ function normalizeConfig(loaded) {
100
102
  outputPath: normalizeString(loaded.dashboard?.outputPath, defaultConfig.dashboard.outputPath)
101
103
  },
102
104
  notifications: {
105
+ enabled: normalizeBoolean(loaded.notifications?.enabled, defaultConfig.notifications.enabled),
106
+ on: normalizeNotificationTrigger(loaded.notifications?.on, defaultConfig.notifications.on),
103
107
  slackWebhookEnv: normalizeString(loaded.notifications?.slackWebhookEnv, defaultConfig.notifications.slackWebhookEnv),
104
108
  discordWebhookEnv: normalizeString(loaded.notifications?.discordWebhookEnv, defaultConfig.notifications.discordWebhookEnv)
105
109
  },
@@ -129,3 +133,8 @@ function normalizeNumber(value, fallback) {
129
133
  function normalizeString(value, fallback) {
130
134
  return typeof value === "string" && value.trim().length > 0 ? value : fallback;
131
135
  }
136
+ function normalizeNotificationTrigger(value, fallback) {
137
+ return value === "always" || value === "failure" || value === "never"
138
+ ? value
139
+ : fallback;
140
+ }
@@ -0,0 +1,20 @@
1
+ import type { AnalysisResult } from "../core/analyzer.js";
2
+ import type { DepBrainConfig } from "./config.js";
3
+ export type NotificationChannel = "slack" | "discord";
4
+ export interface NotificationSendInput {
5
+ channel: NotificationChannel;
6
+ webhookUrl: string;
7
+ payload: unknown;
8
+ }
9
+ export interface NotificationResult {
10
+ channel: NotificationChannel;
11
+ status: "sent" | "skipped" | "failed";
12
+ reason?: string;
13
+ }
14
+ export type NotificationSender = (input: NotificationSendInput) => Promise<void>;
15
+ export declare function sendConfiguredNotifications(result: AnalysisResult, options?: {
16
+ env?: Record<string, string | undefined>;
17
+ sender?: NotificationSender;
18
+ }): Promise<NotificationResult[]>;
19
+ export declare function shouldSendNotification(result: AnalysisResult, config: DepBrainConfig["notifications"]): boolean;
20
+ export declare function renderNotificationMessage(result: AnalysisResult): string;
@@ -0,0 +1,116 @@
1
+ export async function sendConfiguredNotifications(result, options = {}) {
2
+ const config = result.config.notifications;
3
+ if (!shouldSendNotification(result, config)) {
4
+ return [
5
+ {
6
+ channel: "slack",
7
+ status: "skipped",
8
+ reason: "notification trigger did not match analysis result"
9
+ },
10
+ {
11
+ channel: "discord",
12
+ status: "skipped",
13
+ reason: "notification trigger did not match analysis result"
14
+ }
15
+ ];
16
+ }
17
+ const env = options.env ?? process.env;
18
+ const sender = options.sender ?? postWebhook;
19
+ const message = renderNotificationMessage(result);
20
+ const targets = [
21
+ {
22
+ channel: "slack",
23
+ webhookUrl: env[config.slackWebhookEnv],
24
+ payload: { text: message }
25
+ },
26
+ {
27
+ channel: "discord",
28
+ webhookUrl: env[config.discordWebhookEnv],
29
+ payload: { content: message }
30
+ }
31
+ ];
32
+ const results = [];
33
+ for (const target of targets) {
34
+ if (!target.webhookUrl) {
35
+ results.push({
36
+ channel: target.channel,
37
+ status: "skipped",
38
+ reason: `${target.channel} webhook env is not set`
39
+ });
40
+ continue;
41
+ }
42
+ try {
43
+ await sender({
44
+ channel: target.channel,
45
+ webhookUrl: target.webhookUrl,
46
+ payload: target.payload
47
+ });
48
+ results.push({ channel: target.channel, status: "sent" });
49
+ }
50
+ catch (error) {
51
+ results.push({
52
+ channel: target.channel,
53
+ status: "failed",
54
+ reason: error instanceof Error ? error.message : String(error)
55
+ });
56
+ }
57
+ }
58
+ return results;
59
+ }
60
+ export function shouldSendNotification(result, config) {
61
+ if (!config.enabled || config.on === "never") {
62
+ return false;
63
+ }
64
+ return config.on === "always" || !result.policy.passed;
65
+ }
66
+ export function renderNotificationMessage(result) {
67
+ const status = result.policy.passed ? "PASS" : "FAIL";
68
+ const counts = [
69
+ `duplicates ${result.duplicates.length}`,
70
+ `unused ${result.unused.length}`,
71
+ `outdated ${result.outdated.length}`,
72
+ `risks ${result.risks.length}`
73
+ ].join(", ");
74
+ const lines = [
75
+ `dep-brain ${status}: score ${result.score}/100`,
76
+ `path: ${result.rootDir}`,
77
+ `findings: ${counts}`
78
+ ];
79
+ const upgradePriorities = buildUpgradePriorityText(result);
80
+ if (result.policy.reasons.length > 0) {
81
+ lines.push(`policy: ${result.policy.reasons.join("; ")}`);
82
+ }
83
+ if (result.topIssues.length > 0) {
84
+ lines.push("top issues:");
85
+ for (const issue of result.topIssues.slice(0, 3)) {
86
+ lines.push(`- ${formatTopIssue(issue)}`);
87
+ }
88
+ }
89
+ if (upgradePriorities) {
90
+ lines.push(`upgrades: ${upgradePriorities}`);
91
+ }
92
+ return lines.join("\n");
93
+ }
94
+ async function postWebhook(input) {
95
+ const response = await fetch(input.webhookUrl, {
96
+ method: "POST",
97
+ headers: { "content-type": "application/json" },
98
+ body: JSON.stringify(input.payload)
99
+ });
100
+ if (!response.ok) {
101
+ throw new Error(`${input.channel} webhook failed with HTTP ${response.status}`);
102
+ }
103
+ }
104
+ function formatTopIssue(issue) {
105
+ const owner = issue.package ? ` [${issue.package}]` : "";
106
+ return `[${issue.priority}] ${issue.kind} ${issue.name}${owner}: ${issue.summary}`;
107
+ }
108
+ function buildUpgradePriorityText(result) {
109
+ const counts = result.outdated.reduce((acc, item) => {
110
+ acc[item.advice.risk] += 1;
111
+ return acc;
112
+ }, { high: 0, medium: 0, low: 0 });
113
+ return [`high ${counts.high}`, `medium ${counts.medium}`, `low ${counts.low}`]
114
+ .filter((entry) => !entry.endsWith(" 0"))
115
+ .join(", ");
116
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dep-brain",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "CLI and library for explainable dependency intelligence",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",