+33
| // dbpath.js — single source of truth for where the change database lives. | ||
| // | ||
| // Resolution order (read and write use the same rules, so every command | ||
| // agrees on one location): | ||
| // | ||
| // 1. MENDAPI_DB env var — explicit override, used verbatim. | ||
| // 2. <package>/data/sentinel.db — but only when it already exists. | ||
| // This keeps the repo/dev workflow (where the DB is checked into the | ||
| // working tree next to the source) working unchanged, and stays | ||
| // backward compatible with consumers who synced under older versions | ||
| // (their DB sat inside node_modules/mendapi/data). | ||
| // 3. <cwd>/.mendapi/sentinel.db — the default for npm consumers. | ||
| // | ||
| // Rationale for (3): writing into the package install directory | ||
| // (node_modules/mendapi/data) means `npm update` / a reinstall silently | ||
| // deletes the user's synced database. A dot-directory in the project the | ||
| // user runs mendapi from survives dependency churn, and `.mendapi` is | ||
| // already skipped by the scanner/fixer walkers. | ||
| import { existsSync } from 'node:fs'; | ||
| import { dirname, join, resolve } from 'node:path'; | ||
| import { fileURLToPath } from 'node:url'; | ||
| const PKG_ROOT = dirname(fileURLToPath(import.meta.url)); | ||
| const PKG_DB = join(PKG_ROOT, 'data', 'sentinel.db'); | ||
| export function resolveDbPath() { | ||
| if (process.env.MENDAPI_DB) return resolve(process.env.MENDAPI_DB); | ||
| if (existsSync(PKG_DB)) return PKG_DB; | ||
| return join(process.cwd(), '.mendapi', 'sentinel.db'); | ||
| } | ||
| export const DB_PATH = resolveDbPath(); |
+66
-2
@@ -14,2 +14,3 @@ #!/usr/bin/env node | ||
| import { spawnSync } from 'node:child_process'; | ||
| import { readFileSync } from 'node:fs'; | ||
| import { join, dirname } from 'node:path'; | ||
@@ -20,2 +21,11 @@ import { fileURLToPath } from 'node:url'; | ||
| // Single source of truth for the version: package.json (never hand-write it here). | ||
| function pkgVersion() { | ||
| try { | ||
| return JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version; | ||
| } catch { | ||
| return 'unknown'; | ||
| } | ||
| } | ||
| const COMMANDS = { | ||
@@ -30,3 +40,3 @@ sync: { script: 'watcher.js', summary: 'Fetch the latest API change feed from provider release channels (network)' }, | ||
| pr: { script: 'pr.js', summary: 'Turn a fix into a reviewable git branch + PR-ready description (local by default)' }, | ||
| mcp: { script: 'mcp.js', summary: 'Run a Model Context Protocol server on stdio (tools: scan, fix, changes, deps; local only)' }, | ||
| mcp: { script: 'mcp.js', summary: 'Run a Model Context Protocol server on stdio (tools: scan, fix, deps, revalidate, changes; local only)' }, | ||
| }; | ||
@@ -48,4 +58,28 @@ | ||
| console.log('Run `mendapi <command> --help` (or with no args) for command options.'); | ||
| console.log('Run `mendapi --version` to print the installed version.'); | ||
| } | ||
| // Preflight: node:sqlite is only available unflagged on Node >= 22.13.0 (23.4.0 on the 23.x line). | ||
| // Older 22.x passes a naive "22+" check yet crashes with ERR_UNKNOWN_BUILTIN_MODULE — fail loud | ||
| // with a clear message instead of a stack trace. Required version is read from package.json engines. | ||
| // Called after the --help/--version branches: those need no sqlite and must always work. | ||
| function checkNodeVersion() { | ||
| let required = '22.13.0'; | ||
| try { | ||
| const engines = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).engines; | ||
| const m = /([0-9]+\.[0-9]+\.[0-9]+)/.exec(engines?.node || ''); | ||
| if (m) required = m[1]; | ||
| } catch { /* fall back to the documented floor */ } | ||
| const cur = process.versions.node.split('.').map(Number); | ||
| const req = required.split('.').map(Number); | ||
| for (let i = 0; i < 3; i++) { | ||
| if (cur[i] > req[i]) return; | ||
| if (cur[i] < req[i]) { | ||
| console.error(`mendapi requires Node.js >= ${required} (built-in node:sqlite).`); | ||
| console.error(`You are running Node.js ${process.versions.node}. Please upgrade: https://nodejs.org/`); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| } | ||
| const [cmd, ...rest] = process.argv.slice(2); | ||
@@ -58,2 +92,9 @@ | ||
| if (cmd === '--version' || cmd === '-v' || cmd === 'version') { | ||
| console.log(pkgVersion()); | ||
| process.exit(0); | ||
| } | ||
| checkNodeVersion(); | ||
| const target = COMMANDS[cmd]; | ||
@@ -66,3 +107,26 @@ if (!target) { | ||
| const res = spawnSync(process.execPath, [join(ROOT, target.script), ...rest], { stdio: 'inherit' }); | ||
| // Suppress the node:sqlite ExperimentalWarning on every subcommand: it prints two lines of | ||
| // noise to stderr on each run (bad first impression, pollutes MCP stdio logs). The flag | ||
| // exists since Node 21.3.0 and our engines floor is 22.13.0, so it is always available here. | ||
| // CLI convention: explicitly requested help is a success, never a usage error, | ||
| // and its text belongs on stdout (so `mendapi fix --help | grep apply` works). | ||
| // Most subcommands print usage on their usage-error path (stderr, exit 2; | ||
| // review: 1). Normalize the bare `mendapi <cmd> --help` invocation at this | ||
| // single dispatch point: capture the output, emit it on stdout, exit 0. | ||
| // Scoped tight: only when --help/-h is the sole argument — real runs and | ||
| // mixed-flag calls keep inherited stdio and their true exit codes | ||
| // (e.g. `fix --migration bad --help` still fails loud on stderr). | ||
| const helpOnly = rest.length === 1 && (rest[0] === '--help' || rest[0] === '-h'); | ||
| const usageCode = cmd === 'review' ? 1 : 2; | ||
| const res = spawnSync( | ||
| process.execPath, | ||
| ['--disable-warning=ExperimentalWarning', join(ROOT, target.script), ...rest], | ||
| helpOnly ? { encoding: 'utf8' } : { stdio: 'inherit' } | ||
| ); | ||
| if (helpOnly) { | ||
| process.stdout.write((res.stdout || '') + (res.stderr || '')); | ||
| // Subcommands that already handle --help natively exit 0; the rest land on | ||
| // their usage-error code. Both count as a successful help request. | ||
| process.exit(res.status === 0 || res.status === usageCode ? 0 : (res.status ?? 1)); | ||
| } | ||
| process.exit(res.status ?? 1); |
+3
-1
@@ -145,3 +145,5 @@ #!/usr/bin/env node | ||
| const outDir = args['out-dir'] || join(ROOT, '..', 'loop', 'evidence', 'llm-fix-drafts'); | ||
| // Default under cwd/.mendapi — see fixer.js for the rationale (no writes | ||
| // next to the installed package, dot-dir invisible to scans). | ||
| const outDir = args['out-dir'] || join(process.cwd(), '.mendapi', 'llm-fix-drafts'); | ||
| mkdirSync(outDir, { recursive: true }); | ||
@@ -148,0 +150,0 @@ const { unifiedDiff } = await import('./fixer.js'); |
+8
-4
@@ -10,2 +10,3 @@ #!/usr/bin/env node | ||
| // fix — preview (dry-run) or apply a deterministic migration pack | ||
| // revalidate — audit migration packs for staleness against the local DB | ||
| // changes — query the local change database (provider / type filters) | ||
@@ -28,3 +29,3 @@ // | ||
| import { execFileSync } from 'node:child_process'; | ||
| import { existsSync } from 'node:fs'; | ||
| import { existsSync, readFileSync } from 'node:fs'; | ||
| import { join, dirname, resolve } from 'node:path'; | ||
@@ -35,5 +36,8 @@ import { fileURLToPath } from 'node:url'; | ||
| const ROOT = dirname(fileURLToPath(import.meta.url)); | ||
| const DB_PATH = join(ROOT, 'data', 'sentinel.db'); | ||
| import { DB_PATH } from './dbpath.js'; | ||
| const SERVER_INFO = { name: 'mendapi', version: '0.2.0' }; | ||
| // Server identity: version is read from package.json (single source) so the | ||
| // MCP serverInfo can never drift from the published npm version again. | ||
| const PKG_VERSION = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version; | ||
| const SERVER_INFO = { name: 'mendapi', version: PKG_VERSION }; | ||
| // Dual-era version support (MCP spec revision 2026-07-28, "Versioning and | ||
@@ -363,3 +367,3 @@ // Compatibility"): modern versions are served statelessly via per-request | ||
| console.log('Starts a Model Context Protocol server on stdio (JSON-RPC 2.0, newline-delimited).'); | ||
| console.log('Tools: scan, deps, fix, changes. All local; no network code.'); | ||
| console.log('Tools: scan, deps, fix, revalidate, changes. All local; no network code.'); | ||
| process.exit(0); | ||
@@ -366,0 +370,0 @@ } |
+3
-2
| { | ||
| "name": "mendapi", | ||
| "version": "0.5.3", | ||
| "version": "0.5.4", | ||
| "license": "AGPL-3.0-only", | ||
@@ -29,2 +29,3 @@ "type": "module", | ||
| "astlite.js", | ||
| "dbpath.js", | ||
| "pr.js", | ||
@@ -44,3 +45,3 @@ "review.js", | ||
| "engines": { | ||
| "node": ">=22.5.0" | ||
| "node": ">=22.13.0" | ||
| }, | ||
@@ -47,0 +48,0 @@ "author": "mendapi <contact@mendapi.com>", |
+3
-1
@@ -71,3 +71,5 @@ #!/usr/bin/env node | ||
| const outDir = args['out-dir'] || join(ROOT, '..', 'loop', 'evidence', `pr-${migration}`); | ||
| // Default under cwd/.mendapi — see fixer.js for the rationale (no writes | ||
| // next to the installed package, dot-dir invisible to scans). | ||
| const outDir = args['out-dir'] || join(process.cwd(), '.mendapi', `pr-${migration}`); | ||
| mkdirSync(outDir, { recursive: true }); | ||
@@ -74,0 +76,0 @@ |
+4
-4
@@ -21,3 +21,3 @@ # mendapi | ||
| Requires Node.js 22+. Everything runs locally: the scanner, fixer, and review CLIs contain no network code at all. | ||
| Requires Node.js 22.13 or newer. Everything runs locally: the scanner, fixer, and review CLIs contain no network code at all. | ||
@@ -47,3 +47,3 @@ ## Security model (read this first) | ||
| Requires Node.js 22+ (uses built-in `node:sqlite`). Zero npm dependencies. | ||
| Requires Node.js 22.13 or newer (uses built-in `node:sqlite`). Zero npm dependencies. | ||
@@ -139,3 +139,3 @@ ```bash | ||
| Tools exposed: `scan`, `deps`, `fix`, `changes` — the same `schema_version`-stamped JSON as the CLI `--json` flags. See the [For AI agents](https://mendapi.com/docs/agents.html) docs page for the full tool catalog and an autonomous-maintenance recipe. | ||
| Tools exposed: `scan`, `deps`, `fix`, `revalidate`, `changes` — the same `schema_version`-stamped JSON as the CLI `--json` flags. See the [For AI agents](https://mendapi.com/docs/agents.html) docs page for the full tool catalog and an autonomous-maintenance recipe. | ||
@@ -166,3 +166,3 @@ ## Why precision matters | ||
| Published on npm as [`mendapi`](https://www.npmjs.com/package/mendapi) (v0.5.3). Early release — the change database and migration pack registry grow daily; interfaces may still shift before 1.0. | ||
| Published on npm as [`mendapi`](https://www.npmjs.com/package/mendapi) (v0.5.4). Early release — the change database and migration pack registry grow daily; interfaces may still shift before 1.0. | ||
@@ -169,0 +169,0 @@ ## License |
+1
-1
@@ -17,3 +17,3 @@ #!/usr/bin/env node | ||
| const ROOT = dirname(fileURLToPath(import.meta.url)); | ||
| const DB_PATH = join(ROOT, 'data', 'sentinel.db'); | ||
| import { DB_PATH } from './dbpath.js'; | ||
@@ -20,0 +20,0 @@ const VALID_TYPES = new Set(['breaking', 'deprecation', 'additive', 'docs-only', 'fix', 'unknown']); |
+2
-1
@@ -39,3 +39,4 @@ #!/usr/bin/env node | ||
| const ROOT = dirname(fileURLToPath(import.meta.url)); | ||
| export const DEFAULT_DB_PATH = join(ROOT, 'data', 'sentinel.db'); | ||
| import { DB_PATH as DEFAULT_DB_PATH } from './dbpath.js'; | ||
| export { DEFAULT_DB_PATH }; | ||
@@ -42,0 +43,0 @@ // spec-diff source repos carry the version pair in the name |
+1
-1
@@ -40,3 +40,3 @@ #!/usr/bin/env node | ||
| const ROOT = dirname(fileURLToPath(import.meta.url)); | ||
| const DB_PATH = join(ROOT, 'data', 'sentinel.db'); | ||
| import { DB_PATH } from './dbpath.js'; | ||
@@ -43,0 +43,0 @@ const VERDICTS = new Map([ |
+20
-2
@@ -22,3 +22,3 @@ #!/usr/bin/env node | ||
| const ROOT = dirname(fileURLToPath(import.meta.url)); | ||
| const DB_PATH = join(ROOT, 'data', 'sentinel.db'); | ||
| import { DB_PATH } from './dbpath.js'; | ||
@@ -714,3 +714,17 @@ // ---------- provider signatures ---------- | ||
| const hidden = report.impacts.length - shown.length; | ||
| const list = shown.length ? shown : report.impacts.slice(0, 5); | ||
| // Terminal render cap: a repo with one generic SDK import and zero symbol | ||
| // matches can accumulate thousands of medium hits (every breaking change of | ||
| // that provider). Dumping them all makes the first-run report unreadable — | ||
| // the terminal view is a summary, the full data always lives in --json/--out. | ||
| // High-confidence hits are never capped (they are the actionable core). | ||
| const MAX_TERMINAL_IMPACTS = 25; | ||
| let list = shown.length ? shown : report.impacts.slice(0, 5); | ||
| let capped = 0; | ||
| if (list.length > MAX_TERMINAL_IMPACTS) { | ||
| const high = list.filter((im) => im.confidence === 'high'); | ||
| const rest = list.filter((im) => im.confidence !== 'high'); | ||
| const budget = Math.max(MAX_TERMINAL_IMPACTS - high.length, 0); | ||
| capped = rest.length - budget; | ||
| list = [...high, ...rest.slice(0, budget)]; | ||
| } | ||
| for (const im of list) { | ||
@@ -727,2 +741,6 @@ const ch = im.change; | ||
| } | ||
| if (capped > 0) { | ||
| out.push(dim(`${capped} more medium-confidence impact${capped === 1 ? '' : 's'} not shown — use --json or --out for the full report.`)); | ||
| out.push(''); | ||
| } | ||
| if (shown.length && hidden > 0) { | ||
@@ -729,0 +747,0 @@ out.push(dim(`${hidden} low-confidence impact${hidden === 1 ? '' : 's'} hidden — use --json or --out for the full report.`)); |
+19
-3
@@ -13,3 +13,3 @@ #!/usr/bin/env node | ||
| const ROOT = dirname(fileURLToPath(import.meta.url)); | ||
| const DB_PATH = join(ROOT, 'data', 'sentinel.db'); | ||
| import { DB_PATH } from './dbpath.js'; | ||
@@ -118,3 +118,3 @@ // provider -> list of GitHub repos whose releases we watch | ||
| function openDb() { | ||
| mkdirSync(join(ROOT, 'data'), { recursive: true }); | ||
| mkdirSync(dirname(DB_PATH), { recursive: true }); | ||
| const db = new DatabaseSync(DB_PATH); | ||
@@ -271,5 +271,21 @@ db.exec(` | ||
| function printUsage() { | ||
| console.log('Usage: mendapi sync'); | ||
| console.log(''); | ||
| console.log('Fetches the latest API change feed from provider release channels'); | ||
| console.log('(GitHub Releases feeds of official SDK repos) into the local change'); | ||
| console.log('database. This is the only mendapi command that makes network calls,'); | ||
| console.log('and it only runs when you invoke it without --help.'); | ||
| } | ||
| // Run only when invoked directly (keeps `classify` importable for tests without side effects). | ||
| // --help must never trigger the network sync: a user asking for help gets usage text only. | ||
| if (process.argv[1] === fileURLToPath(import.meta.url)) { | ||
| main(); | ||
| const args = process.argv.slice(2); | ||
| if (args.includes('--help') || args.includes('-h')) { | ||
| printUsage(); | ||
| process.exit(0); | ||
| } else { | ||
| main(); | ||
| } | ||
| } |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
930342
0.77%19
5.56%15071
0.91%20
25%