dep-diff-mcp
dep-diff-mcp
MCP-Server, der einen Lockfile-Diff in einen menschenlesbaren Upgrade-Plan übersetzt.
Richten Sie Ihren KI-Assistenten (Cursor, Claude Desktop, Claude Code) auf einen Dependabot-PR, eine npm outdated-Ausgabe oder ein beliebiges Versionspaar und Sie erhalten einen sortierten Upgrade-Plan: Semver-Klasse, Breaking Changes aus den GitHub-Release-Notizen, im Bereich behobene CVEs, Links zu Migrationsanleitungen und eine klare Empfehlung pro Paket.
Installation
Claude Code
Ein Befehl, Benutzerbereich (in jedem Projekt verfügbar):
claude mcp add -s user dep-diff -- npx -y @digicatalyst/dep-diff-mcpProjektbereich (schreibt .mcp.json in das Repository-Wurzelverzeichnis und wird mit dem Team geteilt):
claude mcp add -s project dep-diff -- npx -y @digicatalyst/dep-diff-mcpMit einem expliziten Token (überspringen Sie diesen Schritt, wenn Ihre gh-CLI authentifiziert ist — siehe GitHub-Token weiter unten):
claude mcp add -s user --env GITHUB_TOKEN=ghp_xxx dep-diff -- npx -y @digicatalyst/dep-diff-mcpÜberprüfen:
claude mcp listStarten Sie die Sitzung in Claude Code neu, damit der Server erkannt wird.
Cursor und Claude Desktop
Fügen Sie Folgendes zu Ihrer MCP-Client-Konfiguration hinzu:
Cursor:
~/.cursor/mcp.jsonClaude Desktop:
~/Library/Application Support/Claude/claude_desktop_config.json(macOS) oder%APPDATA%\Claude\claude_desktop_config.json(Windows)
{
"mcpServers": {
"dep-diff": {
"command": "npx",
"args": ["-y", "@digicatalyst/dep-diff-mcp"]
}
}
}Starten Sie Ihren MCP-Client neu. Fragen Sie etwas wie „Was ist bei diesem Dependabot-PR riskant?“ und die Werkzeuge werden automatisch aufgerufen.
Gehostete Remote-Instanz (ohne Installation)
Eine gehostete Instanz läuft unter https://dep-diff.digicatalyst.ca/mcp über Streamable HTTP, sodass Sie das npm-Paket vollständig überspringen können:
claude mcp add -s user -t http dep-diff https://dep-diff.digicatalyst.ca/mcpOder in einer Client-Konfiguration:
{
"mcpServers": {
"dep-diff": {
"type": "http",
"url": "https://dep-diff.digicatalyst.ca/mcp"
}
}
}Übergeben Sie ein GitHub-Token mit ?githubToken=ghp_xxx in der URL, wenn Sie höhere Rate-Limits möchten. Die gehostete Instanz ist zustandslos und speichert keine Protokolle Ihrer Abfragen — Siehe PRIVACY.md. Führen Sie das npm-Paket stattdessen lokal aus, wenn Sie es vorbeugen, dass Ihr Token Ihren Rechner nie verlässt.
Related MCP server: PatchEvergreen MCP Server
GitHub-Token (optional, aber empfohlen)
Der Server greift auf die GitHub-API zu, um Release-Notizen zu lesen. Ohne Token erhalten Sie 60 Anfragen pro Stunde (das anonyme Limit von GitHub) — ausreichend für gelegentliche Anfragen einzelner Pakete, aber nicht für die Analyse großer Lockfiles.
Der Server ermittelt ein Token in dieser Reihenfolge:
Umgebungsvariable
GITHUB_TOKEN, sofern gesetzt.gh auth token— wenn die GitHub-CLI installiert und authentifiziert ist, verwendet der Server dieses Token automatisch. Keine Konfigurationsänderung erforderlich.Anonym (60 Anfragen pro Stunde).
Empfohlen: die gh-CLI verwenden
Wenn Sie gh bereits installiert haben (brew install gh && gh auth login), können Sie hier aufhören — der Server übernimmt Ihre vorhandene Authentifizierung. Kein Klartext-Token liegt irgendwo vor.
Alternative: Umgebungsvariable
Erstellen Sie ein feingranulares Token unter https://github.com/settings/tokens:
Token-Name:
dep-diff-mcpGültigkeit: 90 Tage (regelmäßig rotieren)
Repository-Zugriff:
Public Repositories (read-only)— kein Zugriff auf private RepositoriesBerechtigungen: keine über das Standard-Lesen öffentlicher Repositories hinaus — erteilen Sie keine
repo-,workflow-,user- oder sonstigen Schreib-Scopes
Verweisen Sie in der MCP-Konfiguration darauf:
{
"mcpServers": {
"dep-diff": {
"command": "npx",
"args": ["-y", "@digicatalyst/dep-diff-mcp"],
"env": { "GITHUB_TOKEN": "github_pat_xxx" }
}
}
}Sicherheitshinweise
Diese Konfigurationsdatei liegt im Klartext auf Ihrer Festplatte. Schränken Sie die Zugriffsrechte strikt ein (
chmod 600) und fügen Sie das Token nicht in KI-Chats, Issues oder geteilte Bildschirmsitzungen ein — Transkripte werden häufig gespeichert.Das Token in dieser Konfiguration sollte minimal privilegiert sein (nur Lesezugriff auf öffentliche Repositories). Selbst wenn es offengelegt wird, kann es nur öffentliche Daten lesen, die Sie auch ohne Token lesen können.
Rotieren Sie Tokens regelmäßig. Widerrufen Sie jedes Token, das möglicherweise offengelegt wurde, unter https://github.com/settings/tokens.
Der Server schreibt das Token nie in stdout/stderr oder in die Antwort-Payload.
Tools
analyze_package_change
Analysiert ein einzelnes Paket-Upgrade. Eingaben: ecosystem (npm oder pypi), name, fromVersion, toVersion.
analyze_packages_bulk
Analysiert bis zu 50 Paket-Upgrades parallel. Gibt die Pakete nach Risiko sortiert zurück (security > caution > review > likly-safe > safe) plus zusammenfassende Zählwerte.
Was Sie zurückerhalten
Semver-Klassifikation — major / minor / patch / downgrade / unknown
Breaking Changes — extrahiert aus den Überschriften der GitHub-Release-Notizen
Sicherheitskorrekturen — CVEs, die in
fromVersionvorhanden, aber intoVersionbehoben sind (über OSV.dev)Migrationslinks — Upgrade-Guide-URLs aus den Release-Notizen
Empfehlung — einzeilige Bewertung + Stufe
Unterstützte Ökosysteme
npm
PyPI
Entwicklung
npm install
npm run build
GITHUB_TOKEN=ghp_xxx npm run inspect # MCP InspectorLizenz
MIT
Available Tools
2 toolsanalyze_package_changeAnalyze a single dependency version changeARead-onlyIdempotentInspect
Given one package and two versions (from -> to), returns a structured upgrade analysis: semver classification, GitHub release notes summary, detected breaking changes, security advisories fixed in the range, migration guide links, and a clear recommendation. Use when the user asks about a specific package upgrade ('what changed between react 18 and 19', 'is it safe to bump axios from 0.27 to 1.0', 'what does upgrading lodash 4.17.20 to 4.17.21 fix'). Supports npm, pypi, and github-actions (use the action reference as the name, e.g. actions/checkout). For analyzing many packages at once or a Dependabot batch, use analyze_packages_bulk instead.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Package name (e.g. 'react', 'requests') | |
| ecosystem | Yes | Package ecosystem | |
| toVersion | Yes | Target version (e.g. '19.0.0') | |
| fromVersion | Yes | Current version (e.g. '18.2.0') |
Output Schema
| Name | Required | Description |
|---|---|---|
| package | Yes | Package name that was analyzed |
| repoUrl | Yes | Source repository URL, or null when none could be resolved |
| ecosystem | Yes | Package ecosystem |
| toVersion | Yes | Version being upgraded to |
| fromVersion | Yes | Version being upgraded from |
| semverClass | Yes | Semver relationship between the two versions |
| releaseCount | Yes | Number of GitHub releases found strictly between the two versions |
| securityFixes | Yes | Advisories affecting fromVersion that are resolved at toVersion |
| migrationLinks | Yes | Migration or upgrade guide URLs found in release notes |
| recommendation | Yes | Single-line verdict explaining the recommendation level |
| breakingChanges | Yes | Breaking changes extracted from release notes; empty when none were found |
| releaseExcerpts | No | Raw release-note excerpts, present only as a fallback when a major/minor bump yielded no breaking changes |
| recommendationLevel | Yes | Risk classification, used to rank packages in bulk results |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover readOnly, idempotent, and non-destructive behavior, so the description does not need to restate those. It adds useful behavioral context by enumerating the analysis components returned (semver classification, release notes, breaking changes, advisories, migration links, recommendation) and by specifying supported ecosystems and the github-actions naming convention. No contradiction with annotations is present.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences carry the full message with no filler: the core action and output are front-loaded, examples make the intended queries concrete, and the sibling tool handoff is compressed into a single clause. Every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description provides all essential invocation context: required inputs, output content, supported ecosystems, and when to choose the sibling tool instead. Output schema and annotations already cover return structure and safety semantics, so nothing critical is missing for correct selection and invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and each parameter has a basic description, so the baseline is solid. The description adds meaningful semantic detail beyond the schema: concrete version examples and the rule that github-actions names should use the action reference like 'actions/checkout'. This extra guidance helps an agent supply correctly formatted parameters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('returns a structured upgrade analysis') and identifies the exact resource: one package plus a from/to version pair. It clearly distinguishes itself from the sibling analyze_packages_bulk by emphasizing the single-dependency scope. There is no ambiguity about what the tool does.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly gives the trigger condition: use when the user asks about a specific package upgrade, backed by concrete example queries. It also names the alternative analyze_packages_bulk for many packages or Dependabot batches, making the routing decision unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
analyze_packages_bulkAnalyze multiple dependency changes in parallelARead-onlyIdempotentInspect
Analyzes a list of package upgrades in parallel and returns a unified risk report with packages ranked by recommendation level (security > caution > review > likely-safe > safe). Use when the user provides many dependency changes from a Dependabot PR, npm outdated output, lockfile diff, or batch upgrade. Returns: total count, breakdown by semver class, total security fixes found, packages with breaking changes, and per-package details. Limit 50 packages per call (chunk larger lists).
| Name | Required | Description | Default |
|---|---|---|---|
| changes | Yes | List of package changes to analyze |
Output Schema
| Name | Required | Description |
|---|---|---|
| packages | Yes | Per-package results, ranked security > caution > review > likely-safe > safe |
| bySemverClass | Yes | Breakdown of the batch by semver class |
| totalPackages | Yes | Number of package changes submitted |
| securityFixesTotal | Yes | Total security advisories resolved across the whole batch |
| packagesWithBreakingChanges | Yes | How many packages had at least one breaking change |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds substantial behavioral detail beyond the annotations: it discloses parallel execution, the unified risk report structure, the explicit recommendation ordering (security > caution > review > likely-safe > safe), the returned aggregates, and the 50-package limit with a chunking instruction. The readOnlyHint, openWorldHint, and idempotentHint are already provided by annotations, and the description does not contradict them. This is a strong, transparent behavioral profile.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is tightly structured: purpose first, then use case, then return content, then a practical limit. It is four sentences with no filler, and every sentence adds information an agent needs to invoke correctly or set expectations. The limit and chunking guidance is efficiently placed at the end.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with one well-schematized parameter, a rich output schema, and annotations covering safety and idempotency, the description is complete. It covers what the tool does, common triggers, output content, and operational constraints (parallel, 50-item limit, chunking). Nothing an agent needs to decide between this and the singular sibling is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3; the schema already documents the 'changes' array and the ecosystem enum. The description adds contextual color by calling the items 'package upgrades' and referencing batch sources, but it does not add significant semantic detail about the fromVersion or toVersion fields, which remain self-explanatory. This meets the adequate threshold without exceeding the schema-driven baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Analyzes a list of package upgrades in parallel and returns a unified risk report with packages ranked by recommendation level.' It clearly distinguishes itself from the singular sibling tool by explicitly targeting 'many dependency changes' and listing concrete batch sources like Dependabot PRs, npm outdated output, lockfile diffs, and batch upgrades. The ranking order further clarifies the tool's specific output, leaving no ambiguity about what it does.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear when-to-use context: 'Use when the user provides many dependency changes from a Dependabot PR, npm outdated output, lockfile diff, or batch upgrade.' This strongly implies that single changes belong to the sibling analyze_package_change tool, but it does not explicitly name the alternative or state a when-not condition. That is a clear usage context without formal exclusions, so it misses the top score only slightly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
v0.3.2- Changed
analyze_package_change2 fields changed- changed
Input schema / properties / ecosystem / enumPrevious value: -[ - "npm", - "pypi" -]New value: +[ + "npm", + "pypi", + "github-actions" +] - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "breakingChanges": { + "description": "Breaking changes extracted from release notes; empty when none were found", + "items": { + "type": "string" + }, + "type": "array" + }, + "ecosystem": { + "description": "Package ecosystem", + "enum": [ + "npm", + "pypi", + "github-actions" + ], + "type": "string" + }, + "fromVersion": { + "description": "Version being upgraded from", + "type": "string" + }, + "migrationLinks": { + "description": "Migration or upgrade guide URLs found in release notes", + "items": { + "type": "string" + }, + "type": "array" + }, + "package": { + "description": "Package name that was analyzed", + "type": "string" + }, + "recommendation": { + "description": "Single-line verdict explaining the recommendation level", + "type": "string" + }, + "recommendationLevel": { + "description": "Risk classification, used to rank packages in bulk results", + "enum": [ + "safe", + "likely-safe", + "review", + "caution", + "security" + ], + "type": "string" + }, + "releaseCount": { + "description": "Number of GitHub releases found strictly between the two versions", + "type": "number" + }, + "releaseExcerpts": { + "description": "Raw release-note excerpts, present only as a fallback when a major/minor bump yielded no breaking changes", + "items": { + "additionalProperties": false, + "properties": { + "excerpt": { + "description": "Short excerpt of the release notes", + "type": "string" + }, + "tag": { + "description": "Release tag the excerpt came from", + "type": "string" + } + }, + "required": [ + "tag", + "excerpt" + ], + "type": "object" + }, + "type": "array" + }, + "repoUrl": { + "description": "Source repository URL, or null when none could be resolved", + "type": [ + "string", + "null" + ] + }, + "securityFixes": { + "description": "Advisories affecting fromVersion that are resolved at toVersion", + "items": { + "additionalProperties": false, + "properties": { + "id": { + "description": "Advisory identifier (e.g. 'GHSA-29mw-wpgm-hmr9' or a CVE)", + "type": "string" + }, + "severity": { + "description": "Severity as reported by OSV (e.g. 'LOW', 'MODERATE', 'HIGH', 'CRITICAL')", + "type": "string" + }, + "summary": { + "description": "One-line description of the advisory", + "type": "string" + } + }, + "required": [ + "id", + "summary", + "severity" + ], + "type": "object" + }, + "type": "array" + }, + "semverClass": { + "description": "Semver relationship between the two versions", + "enum": [ + "major", + "minor", + "patch", + "downgrade", + "unknown" + ], + "type": "string" + }, + "toVersion": { + "description": "Version being upgraded to", + "type": "string" + } + }, + "required": [ + "package", + "ecosystem", + "fromVersion", + "toVersion", + "semverClass", + "repoUrl", + "releaseCount", + "breakingChanges", + "securityFixes", + "migrationLinks", + "recommendation", + "recommendationLevel" + ], + "type": "object" +}
- Changed
analyze_packages_bulk2 fields changed- changed
Input schema / properties / changes / items / properties / ecosystem / enumPrevious value: -[ - "npm", - "pypi" -]New value: +[ + "npm", + "pypi", + "github-actions" +] - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "bySemverClass": { + "additionalProperties": false, + "description": "Breakdown of the batch by semver class", + "properties": { + "major": { + "description": "Count of major bumps", + "type": "number" + }, + "minor": { + "description": "Count of minor bumps", + "type": "number" + }, + "patch": { + "description": "Count of patch bumps", + "type": "number" + } + }, + "required": [ + "major", + "minor", + "patch" + ], + "type": "object" + }, + "packages": { + "description": "Per-package results, ranked security > caution > review > likely-safe > safe", + "items": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "breakingChanges": { + "description": "Breaking changes extracted from release notes; empty when none were found", + "items": { + "type": "string" + }, + "type": "array" + }, + "ecosystem": { + "description": "Package ecosystem", + "enum": [ + "npm", + "pypi", + "github-actions" + ], + "type": "string" + }, + "fromVersion": { + "description": "Version being upgraded from", + "type": "string" + }, + "migrationLinks": { + "description": "Migration or upgrade guide URLs found in release notes", + "items": { + "type": "string" + }, + "type": "array" + }, + "package": { + "description": "Package name that was analyzed", + "type": "string" + }, + "recommendation": { + "description": "Single-line verdict explaining the recommendation level", + "type": "string" + }, + "recommendationLevel": { + "description": "Risk classification, used to rank packages in bulk results", + "enum": [ + "safe", + "likely-safe", + "review", + "caution", + "security" + ], + "type": "string" + }, + "releaseCount": { + "description": "Number of GitHub releases found strictly between the two versions", + "type": "number" + }, + "releaseExcerpts": { + "description": "Raw release-note excerpts, present only as a fallback when a major/minor bump yielded no breaking changes", + "items": { + "additionalProperties": false, + "properties": { + "excerpt": { + "description": "Short excerpt of the release notes", + "type": "string" + }, + "tag": { + "description": "Release tag the excerpt came from", + "type": "string" + } + }, + "required": [ + "tag", + "excerpt" + ], + "type": "object" + }, + "type": "array" + }, + "repoUrl": { + "description": "Source repository URL, or null when none could be resolved", + "type": [ + "string", + "null" + ] + }, + "securityFixes": { + "description": "Advisories affecting fromVersion that are resolved at toVersion", + "items": { + "additionalProperties": false, + "properties": { + "id": { + "description": "Advisory identifier (e.g. 'GHSA-29mw-wpgm-hmr9' or a CVE)", + "type": "string" + }, + "severity": { + "description": "Severity as reported by OSV (e.g. 'LOW', 'MODERATE', 'HIGH', 'CRITICAL')", + "type": "string" + }, + "summary": { + "description": "One-line description of the advisory", + "type": "string" + } + }, + "required": [ + "id", + "summary", + "severity" + ], + "type": "object" + }, + "type": "array" + }, + "semverClass": { + "description": "Semver relationship between the two versions", + "enum": [ + "major", + "minor", + "patch", + "downgrade", + "unknown" + ], + "type": "string" + }, + "toVersion": { + "description": "Version being upgraded to", + "type": "string" + } + }, + "required": [ + "package", + "ecosystem", + "fromVersion", + "toVersion", + "semverClass", + "repoUrl", + "releaseCount", + "breakingChanges", + "securityFixes", + "migrationLinks", + "recommendation", + "recommendationLevel" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "error": { + "description": "Why the analysis could not be completed", + "type": "string" + }, + "package": { + "description": "Package name whose analysis failed", + "type": "string" + }, + "recommendationLevel": { + "const": "review", + "description": "Always 'review' — a package that could not be analyzed cannot be cleared automatically", + "type": "string" + } + }, + "required": [ + "package", + "error", + "recommendationLevel" + ], + "type": "object" + } + ] + }, + "type": "array" + }, + "packagesWithBreakingChanges": { + "description": "How many packages had at least one breaking change", + "type": "number" + }, + "securityFixesTotal": { + "description": "Total security advisories resolved across the whole batch", + "type": "number" + }, + "totalPackages": { + "description": "Number of package changes submitted", + "type": "number" + } + }, + "required": [ + "totalPackages", + "bySemverClass", + "securityFixesTotal", + "packagesWithBreakingChanges", + "packages" + ], + "type": "object" +}
2 tool updates
v0.1.1- First observed
analyze_package_change - First observed
analyze_packages_bulk
TDQS
Scored across 2 tools
The two tools are clearly separated by scope: one handles a single package upgrade, the other handles multiple packages in bulk. Each description explicitly references the other, so an agent can unambiguously choose based on input size.
Both tools follow the same analyze_package_* pattern, with the singular 'change' and plural 'bulk' clearly differentiating them. This is a consistent and predictable verb_noun convention.
Two tools is minimal, but the server's narrow purpose of dependency upgrade analysis is well served by one individual and one bulk entry point. It is slightly thin but not unreasonable for the domain.
The pair covers both single-package and multi-package analysis, which covers most workflows. A minor gap is the lack of direct lockfile/diff parsing, but users can supply the package list from those sources.
Maintenance
Related MCP Connectors
Check exact npm/PyPI upgrades for evidence-backed breaking changes; query APIs and components.
Scan packages and lockfiles (npm, PyPI, Go, Maven, Cargo, NuGet) for vulnerabilities and malware.
41Risk-scan a diff, flag AI-generated-code tells, find secrets. 5 of 7 tools need no account.
npm/PyPI/Django dependency upgrades: security, runtime compatibility, migration, package ranking.
Related MCP Servers
- AlicenseBqualityDmaintenanceEnables AI agents to safely upgrade JavaScript and TypeScript projects through dependency analysis, upgrade path detection, breaking change identification, codemod application, and PR summary generation.1435 npmMIT
- FlicenseNot gradedqualityDmaintenanceProvides breaking changes analysis for libraries across multiple languages, enabling version upgrade planning, dependency audits, and migration reports through MCP tool access and expert workflows.-
- AlicenseNot gradedqualityCmaintenanceAnalyzes npm dependency upgrades to reveal which files and functions in your codebase break, with confidence tiers and transitive call chain tracing.5 npmMIT
- AlicenseNot gradedqualityBmaintenanceEnables AI coding agents to verify, compare, and safely upgrade npm packages by scoring packages from public signals and sandboxed co-installation, detecting breaking API surface changes, and rewriting code that upgrades break.81 npm8MIT