@holdpoint/cli 0.1.0-alpha.21 → 0.1.0-alpha.22

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.
@@ -337,7 +337,7 @@ checks:
337
337
  3. Edit existing code surgically. Do not rewrite what you were not asked to rewrite.
338
338
  4. Do not add logging, tests, types, or abstractions that were not asked for.
339
339
  5. If a test suite exists, it must pass before you stop.
340
- 6. Complete exactly what was asked. Log adjacent issues as TO${"DO"}s, don't fix them.
340
+ 6. Complete exactly what was asked. Log adjacent issues separately, don't fix them.
341
341
 
342
342
  - id: check-test-suite
343
343
  label: "Test suite passes"
@@ -518,4 +518,4 @@ export {
518
518
  spliceBreadcrumb,
519
519
  initCommand
520
520
  };
521
- //# sourceMappingURL=chunk-D7ZF5JJH.js.map
521
+ //# sourceMappingURL=chunk-5TMH2JOD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/init.ts","../src/detect.ts","../src/templates.ts","../src/lib/preflight.ts","../src/claude-settings.ts","../src/cursor-hooks.ts","../src/lib/instructions-breadcrumb.ts"],"sourcesContent":["import { execSync } from \"node:child_process\";\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { buildConfigJson, buildEngine } from \"@holdpoint/engine-copilot\";\nimport { buildEngineJson as buildClaudeEngineJson } from \"@holdpoint/engine-claude\";\nimport {\n buildCheckScript as buildCursorCheckScript,\n buildEngine as buildCursorEngine,\n buildHooksJson as buildCursorHooksJson,\n} from \"@holdpoint/engine-cursor\";\nimport {\n buildConfigToml as buildCodexConfigToml,\n buildHooksJson as buildCodexHooksJson,\n buildCheckScript as buildCodexCheckScript,\n} from \"@holdpoint/engine-codex\";\nimport { parseHoldpointYaml } from \"@holdpoint/yaml-core\";\nimport type { AgentType } from \"@holdpoint/types\";\nimport { detectPackageManager, type PackageManager } from \"../detect.js\";\nimport { ensureBundledFile } from \"../templates.js\";\nimport { runPreflight, printPreflight } from \"../lib/preflight.js\";\nimport { mergeClaudeSettings } from \"../claude-settings.js\";\nimport { mergeCursorHooks } from \"../cursor-hooks.js\";\nimport { spliceBreadcrumb } from \"../lib/instructions-breadcrumb.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nfunction getDefaultTemplatePath(): string {\n const candidates = [\n join(__dirname, \"templates\", \"default.yaml\"), // dist/templates/ (published package)\n join(__dirname, \"../../../templates\", \"default.yaml\"), // monorepo dev fallback\n join(process.cwd(), \"templates\", \"default.yaml\"), // cwd fallback\n ];\n for (const p of candidates) {\n if (existsSync(p)) return p;\n }\n return \"\";\n}\n\n// Offline/zero-dep fallback written only when `templates/default.yaml` cannot be\n// resolved (see getDefaultTemplatePath). `templates/default.yaml` is the canonical\n// source of truth for the shared karpathy-practices / check-test-suite /\n// check-minimal-change checks — keep the wording of those checks here in sync with\n// it whenever they change.\nconst MINIMAL_CHECKS_YAML = `version: 1\ncontext:\n guides: {}\nconditions: []\nchecks:\n - id: karpathy-practices\n label: \"Karpathy coding practices\"\n on: session_start\n inject:\n text: |\n ## Coding practices (enforced by Holdpoint)\n\n 1. Read all relevant files fully before making any change.\n 2. Make the smallest change that satisfies the task. No unsolicited refactors.\n 3. Edit existing code surgically. Do not rewrite what you were not asked to rewrite.\n 4. Do not add logging, tests, types, or abstractions that were not asked for.\n 5. If a test suite exists, it must pass before you stop.\n 6. Complete exactly what was asked. Log adjacent issues separately, don't fix them.\n\n - id: check-test-suite\n label: \"Test suite passes\"\n on: before_done\n prompt: \"If this project has a test suite (package.json test script, pytest, go test, etc.), confirm it passes. If you did not run it, run it now before finishing.\"\n\n - id: check-minimal-change\n label: \"Change is minimal and scoped\"\n on: before_done\n prompt: \"Review every file you changed. Remove any addition that was not explicitly requested — unused imports, debug logs, reformatted blocks, unrequested refactors. Confirm the diff is surgical.\"\n\n - id: lint\n label: \"Lint codebase\"\n cmd: \"echo 'Add your lint command here'\"\n\n - id: jsdoc\n label: \"JSDoc on changed public functions\"\n prompt: \"Ensure all changed public functions and exports have JSDoc comments.\"\n`;\n\nconst MINIMAL_MASTER_PROMPT = `# Holdpoint\n\nRun \\`holdpoint check\\` before marking any task complete.\nSee \\`checks.yaml\\` for the full list of checks.\n`;\n\nconst MINIMAL_HOLDPOINT_REFERENCE = `# Holdpoint reference\n\nRead \\`MASTER_PROMPT.md\\` first for the mandatory workflow, then use this file for deeper project-specific Holdpoint notes.\n`;\n\nconst MINIMAL_PREREQUISITES = `# Holdpoint prerequisites\n\nHoldpoint installed repo-local engine integrations for one or more AI coding agents. Before relying on them locally, review these setup notes:\n\n- **GitHub Copilot CLI** — Holdpoint's \\`.github/extensions/holdpoint/extension.mjs\\` uses the Copilot CLI **EXTENSIONS** feature. Today that feature is gated behind experimental mode. In Copilot CLI, run \\`/experimental on\\` so **EXTENSIONS** appears in the enabled feature set before using Holdpoint locally.\n- **Cursor** — project-level hooks run in trusted workspaces. After opening the repo in Cursor, confirm the workspace is trusted and review Settings → Hooks if hooks do not fire.\n- **OpenAI Codex** — project-level hooks require trust approval. Run \\`codex trust\\` in the Codex TUI or review the hook with \\`/hooks\\`.\n- **General** — Holdpoint expects Node.js 18+ and a git repository so \\`holdpoint init\\`, \\`holdpoint update\\`, and \\`holdpoint check\\` can run normally.\n\nDocs: https://holdpoint.dev/docs\n`;\n\n/**\n * Initialise Holdpoint in the current project.\n *\n * Writes `checks.yaml` (from the unified default template), `checks.immutable.json`,\n * engine files for each target agent (Copilot, Claude, Cursor, Codex),\n * and repo-local handoff docs such as `HOLDPOINT_PREREQUISITES.md`.\n * Defaults to installing all four agents; pass `--agent` to restrict to one.\n */\nexport async function initCommand(options: { agent?: string }): Promise<void> {\n const spinner = ora(\"Initialising Holdpoint…\").start();\n\n // Default: install for all agents. Pass --agent=copilot|claude|cursor to restrict.\n const agentOpt = options.agent;\n const agents: AgentType[] =\n !agentOpt || agentOpt === \"all\"\n ? [\"copilot\", \"claude\", \"cursor\", \"codex\"]\n : [agentOpt as AgentType];\n\n spinner.text = `Installing for: ${chalk.cyan(agents.join(\", \"))}`;\n\n // Detect package manager once — used both for template substitution and devDep install.\n const pm = detectPackageManager();\n\n // 1. Read or create checks.yaml\n let yamlContent = MINIMAL_CHECKS_YAML;\n if (!existsSync(\"checks.yaml\")) {\n const templatePath = getDefaultTemplatePath();\n if (templatePath) {\n yamlContent = readFileSync(templatePath, \"utf8\");\n }\n // Substitute the package manager so checks use the right runner (npm/yarn/pnpm).\n if (pm !== \"pnpm\") {\n yamlContent = yamlContent.replace(/\\bpnpm\\b/g, pm);\n }\n writeFileSync(\"checks.yaml\", yamlContent, \"utf8\");\n } else {\n yamlContent = readFileSync(\"checks.yaml\", \"utf8\");\n }\n\n const config = parseHoldpointYaml(yamlContent);\n\n // 2. Write checks.immutable.json — read by holdpoint-check.mjs at runtime\n const generatedDir = \".github/holdpoint/generated\";\n mkdirSync(generatedDir, { recursive: true });\n writeFileSync(`${generatedDir}/checks.immutable.json`, buildConfigJson(config), \"utf8\");\n\n // 3. Install engine files for each target agent\n if (agents.includes(\"copilot\")) {\n // extension.mjs handles both session context injection (onSessionStart) and\n // check enforcement (onPreToolUse → task_complete). No separate hooks files needed.\n const extDir = \".github/extensions/holdpoint\";\n mkdirSync(extDir, { recursive: true });\n writeFileSync(join(extDir, \"extension.mjs\"), buildEngine(config), \"utf8\");\n spliceBreadcrumb(\".github/copilot-instructions.md\");\n }\n\n if (agents.includes(\"claude\")) {\n mkdirSync(\".claude\", { recursive: true });\n const settingsPath = \".claude/settings.json\";\n let existing: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try {\n existing = JSON.parse(readFileSync(settingsPath, \"utf8\")) as Record<string, unknown>;\n } catch {\n /* ignore */\n }\n }\n const holdpointHooks = JSON.parse(buildClaudeEngineJson(config)) as Record<string, unknown>;\n writeFileSync(\n settingsPath,\n JSON.stringify(mergeClaudeSettings(existing, holdpointHooks), null, 2),\n \"utf8\",\n );\n spliceBreadcrumb(\"CLAUDE.md\");\n }\n\n if (agents.includes(\"cursor\")) {\n mkdirSync(\".cursor\", { recursive: true });\n const cursorHooksPath = \".cursor/hooks.json\";\n let existingHooks: Record<string, unknown> = {};\n if (existsSync(cursorHooksPath)) {\n try {\n existingHooks = JSON.parse(readFileSync(cursorHooksPath, \"utf8\")) as Record<\n string,\n unknown\n >;\n } catch {\n /* ignore */\n }\n }\n const cursorHooks = JSON.parse(buildCursorHooksJson(config)) as Record<string, unknown>;\n writeFileSync(\n cursorHooksPath,\n JSON.stringify(mergeCursorHooks(existingHooks, cursorHooks), null, 2) + \"\\n\",\n \"utf8\",\n );\n writeFileSync(\".cursor/holdpoint-hook.mjs\", buildCursorCheckScript(), \"utf8\");\n\n const cursorRules = buildCursorEngine(config);\n const cursorPath = \".cursorrules\";\n if (existsSync(cursorPath)) {\n const existing = readFileSync(cursorPath, \"utf8\");\n if (!existing.includes(\"Holdpoint Rules\")) {\n writeFileSync(cursorPath, `${existing.trimEnd()}\\n\\n${cursorRules}`, \"utf8\");\n }\n } else {\n writeFileSync(cursorPath, cursorRules, \"utf8\");\n }\n spliceBreadcrumb(\".cursor/rules/holdpoint.md\");\n }\n\n if (agents.includes(\"codex\")) {\n mkdirSync(\".codex\", { recursive: true });\n writeFileSync(\".codex/hooks.json\", buildCodexHooksJson(config), \"utf8\");\n writeFileSync(\".codex/holdpoint-check.mjs\", buildCodexCheckScript(config), \"utf8\");\n writeFileSync(\".codex/config.toml\", buildCodexConfigToml(), \"utf8\");\n spliceBreadcrumb(\"AGENTS.md\");\n }\n\n // 4. Create repo-local guidance files if not present\n ensureBundledFile(\"MASTER_PROMPT.md\", \"MASTER_PROMPT.md\", MINIMAL_MASTER_PROMPT);\n ensureBundledFile(\n \"HOLDPOINT_REFERENCE.md\",\n \"HOLDPOINT_REFERENCE.md\",\n MINIMAL_HOLDPOINT_REFERENCE,\n );\n ensureBundledFile(\n \"HOLDPOINT_PREREQUISITES.md\",\n \"HOLDPOINT_PREREQUISITES.md\",\n MINIMAL_PREREQUISITES,\n );\n\n // Install holdpoint as a devDependency so hooks resolve via node_modules/.bin\n // rather than downloading on every hook fire via npx.\n spinner.text = \"Installing holdpoint as a devDependency…\";\n const installCmds: Record<PackageManager, string> = {\n pnpm: \"pnpm add -D holdpoint\",\n yarn: \"yarn add --dev holdpoint\",\n npm: \"npm install --save-dev holdpoint\",\n };\n const installCmd = installCmds[pm];\n try {\n execSync(installCmd, { stdio: \"pipe\" });\n spinner.succeed(chalk.bold.green(\"Holdpoint initialised!\"));\n } catch {\n spinner.warn(\n chalk.yellow(`Holdpoint initialised, but could not install the package automatically.`) +\n `\\n Run manually: ${chalk.yellow(installCmd)}`,\n );\n }\n\n // Per-agent preflight: surface the Copilot `/experimental on` step and Codex\n // `codex trust` step at install time.\n // instead of burying them in HOLDPOINT_PREREQUISITES.md where users\n // routinely miss them.\n const preflight = runPreflight(agents);\n printPreflight(preflight);\n\n // If `holdpoint` is already on PATH (typically because the user has the\n // global install), use the bare command. Otherwise fall back to `npx\n // holdpoint` so the suggested commands actually work for users who only\n // have the local devDep.\n let holdpointInvocation = \"npx holdpoint\";\n try {\n execSync(\"command -v holdpoint\", { stdio: \"pipe\" });\n holdpointInvocation = \"holdpoint\";\n } catch {\n // not on PATH — keep the npx fallback\n }\n\n console.log(`\n${chalk.cyan(\"Next steps:\")}\n Karpathy coding practices are active via ${chalk.yellow(\"checks.yaml\")}\n 1. Edit ${chalk.yellow(\"checks.yaml\")} to customise your eval checkpoints\n 2. Address any ${chalk.yellow(\"→\")} items above (full notes in ${chalk.yellow(\"HOLDPOINT_PREREQUISITES.md\")})\n 3. Commit ${chalk.yellow(\"checks.yaml\")}, ${chalk.yellow(\"HOLDPOINT_PREREQUISITES.md\")}, and the generated engine files\n 4. Run ${chalk.yellow(holdpointInvocation)} ${chalk.yellow(\"check\")} at any time to validate\n\n Visual builder: ${chalk.yellow(holdpointInvocation + \" builder\")} (opens the daemon at /builder)\n Agents: ${chalk.cyan(agents.join(\", \"))}\n`);\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport type { AgentType } from \"@holdpoint/types\";\n\nexport type PackageManager = \"pnpm\" | \"yarn\" | \"npm\";\n\n/** Detect which package manager owns the current project by checking lock files. */\nexport function detectPackageManager(): PackageManager {\n if (existsSync(\"pnpm-lock.yaml\")) return \"pnpm\";\n if (existsSync(\"yarn.lock\")) return \"yarn\";\n return \"npm\";\n}\n\n/** @deprecated Use detectInstalledAgents() — single-agent detection is no longer the default. */\nexport function detectAgent(): AgentType {\n if (existsSync(\".github/extensions\")) return \"copilot\";\n if (existsSync(\".claude\")) return \"claude\";\n if (existsSync(\".cursor/hooks.json\")) return \"cursor\";\n if (existsSync(\".cursorrules\")) return \"cursor\";\n if (existsSync(\".codex\")) return \"codex\";\n return \"unknown\";\n}\n\n/**\n * Returns every agent whose Holdpoint engine files are already present in the\n * current working directory. Used by `holdpoint update` so it regenerates only\n * the engines that were previously installed.\n */\nexport function detectInstalledAgents(): AgentType[] {\n const agents: AgentType[] = [];\n if (existsSync(\".github/extensions/holdpoint/extension.mjs\")) agents.push(\"copilot\");\n if (existsSync(\".claude/settings.json\")) agents.push(\"claude\");\n if (existsSync(\".cursor/hooks.json\")) {\n try {\n if (readFileSync(\".cursor/hooks.json\", \"utf8\").includes(\"HOLDPOINT_MANAGED=cursor\")) {\n agents.push(\"cursor\");\n }\n } catch {\n /* ignore unreadable file */\n }\n }\n if (existsSync(\".cursorrules\")) {\n try {\n if (\n !agents.includes(\"cursor\") &&\n readFileSync(\".cursorrules\", \"utf8\").includes(\"Holdpoint Rules\")\n ) {\n agents.push(\"cursor\");\n }\n } catch {\n /* ignore unreadable file */\n }\n }\n // Detect Codex by the generated check script (more specific than .codex/ existence)\n if (existsSync(\".codex/holdpoint-check.mjs\")) agents.push(\"codex\");\n return agents;\n}\n","import { copyFileSync, existsSync, writeFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport function getBundledTemplatePath(filename: string): string {\n const candidates = [\n join(__dirname, \"templates\", filename), // dist/templates/ (published package)\n join(__dirname, \"../../../templates\", filename), // monorepo dev fallback\n join(process.cwd(), \"templates\", filename), // cwd fallback\n ];\n for (const candidate of candidates) {\n if (existsSync(candidate)) return candidate;\n }\n return \"\";\n}\n\nexport function ensureBundledFile(\n outputPath: string,\n templateFilename: string,\n fallbackContent: string,\n): boolean {\n if (existsSync(outputPath)) {\n return false;\n }\n\n const templatePath = getBundledTemplatePath(templateFilename);\n if (templatePath) {\n copyFileSync(templatePath, outputPath);\n } else {\n writeFileSync(outputPath, fallbackContent, \"utf8\");\n }\n\n return true;\n}\n","import { execSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport type { AgentType } from \"@holdpoint/types\";\n\n/**\n * Result of a single preflight check for one agent.\n *\n * - `ok`: the agent's toolchain is detected and ready to use.\n * - `action_required`: we can be specific about something the user must do\n * (install a tool, enable a feature, approve a hook) — `command` is set.\n * - `unknown`: detection failed but we can't be sure what the user needs.\n */\nexport type PreflightStatus = \"ok\" | \"action_required\" | \"unknown\";\n\nexport interface PreflightResult {\n agent: AgentType;\n status: PreflightStatus;\n /** One-line human-readable summary, no trailing newline. */\n message: string;\n /** When `action_required`, the exact command the user should run next. */\n command?: string;\n /** When set, points at HOLDPOINT_PREREQUISITES.md or external docs. */\n docs?: string;\n}\n\n/**\n * Run silently — used for tool detection where we expect failure to be common\n * and don't want stderr bleeding into the init spinner output.\n */\nfunction silentExec(cmd: string): { ok: boolean; stdout: string } {\n try {\n const stdout = execSync(cmd, { stdio: [\"ignore\", \"pipe\", \"ignore\"] }).toString();\n return { ok: true, stdout };\n } catch {\n return { ok: false, stdout: \"\" };\n }\n}\n\n/**\n * Copilot CLI preflight.\n *\n * We can detect `gh` and the `gh-copilot` extension reliably, but the\n * `/experimental on` requirement is runtime state inside the Copilot CLI\n * session itself — there's no clean filesystem signal. So we always remind\n * users to enable it, even when everything else looks healthy.\n */\nfunction checkCopilot(): PreflightResult {\n const gh = silentExec(\"gh --version\");\n if (!gh.ok) {\n return {\n agent: \"copilot\",\n status: \"action_required\",\n message: \"GitHub CLI not found on PATH\",\n command: \"brew install gh # or: https://cli.github.com/\",\n };\n }\n\n // `gh copilot --version` exits non-zero if the extension isn't installed.\n const copilot = silentExec(\"gh copilot --version\");\n if (!copilot.ok) {\n return {\n agent: \"copilot\",\n status: \"action_required\",\n message: \"Copilot CLI extension not installed\",\n command: \"gh extension install github/gh-copilot\",\n };\n }\n\n return {\n agent: \"copilot\",\n status: \"action_required\",\n message: \"Copilot CLI detected — experimental mode required for EXTENSIONS\",\n command: \"Inside Copilot CLI, run: /experimental on\",\n };\n}\n\n/**\n * Claude Code preflight. Settings live at `.claude/settings.json`, which we\n * write ourselves during init, so the only failure mode is the binary\n * missing from PATH (in which case the user still wants the settings file\n * written — they may be invoking Claude Code from a different machine).\n */\nfunction checkClaude(): PreflightResult {\n const claude = silentExec(\"claude --version\");\n if (!claude.ok) {\n return {\n agent: \"claude\",\n status: \"unknown\",\n message: \"Claude Code binary not on PATH (hooks still written for when it is)\",\n };\n }\n return {\n agent: \"claude\",\n status: \"ok\",\n message: \"Claude Code detected — hooks installed at .claude/settings.json\",\n };\n}\n\n/**\n * Cursor preflight. Project `.cursor/hooks.json` hooks run only in trusted\n * workspaces, which is runtime state inside Cursor and not reliably readable\n * from disk, so init prints the trust/debugging reminder every time.\n */\nfunction checkCursor(): PreflightResult {\n return {\n agent: \"cursor\",\n status: \"ok\",\n message: \"Cursor — .cursor/hooks.json gate + .cursor/rules breadcrumb installed\",\n docs: \"https://holdpoint.dev/docs#cursor\",\n };\n}\n\n/**\n * Codex preflight. Project-level hooks require trust approval inside the\n * Codex TUI, which is runtime state we can't read from disk reliably across\n * Codex versions — so this mirrors the Copilot pattern: detect the binary,\n * always remind the user about `codex trust`.\n */\nfunction checkCodex(): PreflightResult {\n const codex = silentExec(\"codex --version\");\n if (!codex.ok) {\n return {\n agent: \"codex\",\n status: \"action_required\",\n message: \"Codex CLI not found on PATH\",\n command: \"Install Codex: https://github.com/openai/codex\",\n };\n }\n return {\n agent: \"codex\",\n status: \"action_required\",\n message: \"Codex detected — project-level hooks require trust approval\",\n command: \"In the Codex TUI: codex trust (or /hooks to review)\",\n };\n}\n\n// `AgentType` includes \"unknown\" for the stack-detection fallback path, but\n// only the four real agents have a preflight to run. `Partial` keeps this\n// honest instead of forcing a no-op stub.\nconst CHECKS: Partial<Record<AgentType, () => PreflightResult>> = {\n copilot: checkCopilot,\n claude: checkClaude,\n cursor: checkCursor,\n codex: checkCodex,\n};\n\n/**\n * Run preflight for the given agents and return results in input order.\n * Agents without a registered check are silently skipped — the caller's\n * agent list is the source of truth.\n *\n * Intentionally pure apart from `execSync` so callers can decide whether\n * to print, summarise, or fail-fast on the results.\n */\nexport function runPreflight(agents: readonly AgentType[]): PreflightResult[] {\n return agents.flatMap((agent) => {\n const check = CHECKS[agent];\n return check ? [check()] : [];\n });\n}\n\n/**\n * Pretty-print preflight results to stdout in the style used by `holdpoint init`.\n * Always groups by status so action items don't get lost between OKs.\n */\nexport function printPreflight(results: readonly PreflightResult[]): void {\n if (results.length === 0) return;\n\n const ok = results.filter((r) => r.status === \"ok\");\n const unknown = results.filter((r) => r.status === \"unknown\");\n const action = results.filter((r) => r.status === \"action_required\");\n\n console.log(\"\");\n console.log(chalk.bold(\"Agent preflight:\"));\n\n for (const r of ok) {\n console.log(` ${chalk.green(\"✓\")} ${r.agent.padEnd(7)} ${chalk.dim(r.message)}`);\n }\n for (const r of unknown) {\n console.log(` ${chalk.dim(\"?\")} ${r.agent.padEnd(7)} ${chalk.dim(r.message)}`);\n }\n for (const r of action) {\n console.log(` ${chalk.yellow(\"→\")} ${chalk.bold(r.agent.padEnd(7))} ${r.message}`);\n if (r.command) console.log(` ${chalk.cyan(r.command)}`);\n }\n}\n","type JsonObject = Record<string, unknown>;\n\nconst HOLDPOINT_CLAUDE_HOOK_MARKER = \"HOLDPOINT_MANAGED=claude\";\n\nfunction isObject(value: unknown): value is JsonObject {\n return value != null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction asHookArray(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [];\n}\n\nfunction isManagedHookCommand(value: unknown): boolean {\n return (\n isObject(value) &&\n typeof value.command === \"string\" &&\n value.command.includes(HOLDPOINT_CLAUDE_HOOK_MARKER)\n );\n}\n\nfunction isLegacyManagedHookCommand(value: unknown): boolean {\n if (!isObject(value) || typeof value.command !== \"string\") return false;\n return (\n value.command === \"node_modules/.bin/holdpoint event --engine claude --from-hook || true\" ||\n value.command === \"node_modules/.bin/holdpoint check --staged\"\n );\n}\n\nfunction isManagedHookEntry(value: unknown): boolean {\n if (!isObject(value)) return false;\n const hooks = asHookArray(value.hooks);\n return (\n hooks.length > 0 &&\n (hooks.every(isManagedHookCommand) || hooks.every(isLegacyManagedHookCommand))\n );\n}\n\n/**\n * Merge generated Holdpoint hooks into .claude/settings.json without clobbering\n * user-owned hooks. Re-runs remove only entries carrying Holdpoint's marker.\n */\nexport function mergeClaudeSettings(existing: JsonObject, generated: JsonObject): JsonObject {\n const existingHooks = isObject(existing.hooks) ? existing.hooks : {};\n const generatedHooks = isObject(generated.hooks) ? generated.hooks : {};\n const mergedHooks: JsonObject = {};\n\n for (const eventName of new Set([\n ...Object.keys(existingHooks),\n ...Object.keys(generatedHooks),\n ])) {\n const preserved = asHookArray(existingHooks[eventName]).filter(\n (entry) => !isManagedHookEntry(entry),\n );\n const next = asHookArray(generatedHooks[eventName]);\n if (preserved.length > 0 || next.length > 0) {\n mergedHooks[eventName] = [...preserved, ...next];\n }\n }\n\n return { ...existing, ...generated, hooks: mergedHooks };\n}\n","type JsonObject = Record<string, unknown>;\n\nconst HOLDPOINT_CURSOR_HOOK_MARKER = \"HOLDPOINT_MANAGED=cursor\";\n\nfunction isObject(value: unknown): value is JsonObject {\n return value != null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction asHookArray(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [];\n}\n\nfunction isManagedCursorHook(value: unknown): boolean {\n return (\n isObject(value) &&\n typeof value.command === \"string\" &&\n (value.command.includes(HOLDPOINT_CURSOR_HOOK_MARKER) ||\n value.command.includes(\".cursor/holdpoint-hook.mjs\"))\n );\n}\n\n/**\n * Merge generated Holdpoint Cursor hooks into `.cursor/hooks.json` without\n * clobbering user/partner hooks. Re-runs remove only entries carrying\n * Holdpoint's marker or the generated hook script path.\n */\nexport function mergeCursorHooks(existing: JsonObject, generated: JsonObject): JsonObject {\n const existingHooks = isObject(existing.hooks) ? existing.hooks : {};\n const generatedHooks = isObject(generated.hooks) ? generated.hooks : {};\n const mergedHooks: JsonObject = {};\n\n for (const eventName of new Set([\n ...Object.keys(existingHooks),\n ...Object.keys(generatedHooks),\n ])) {\n const preserved = asHookArray(existingHooks[eventName]).filter(\n (entry) => !isManagedCursorHook(entry),\n );\n const next = asHookArray(generatedHooks[eventName]);\n if (preserved.length > 0 || next.length > 0) {\n mergedHooks[eventName] = [...preserved, ...next];\n }\n }\n\n return { ...existing, ...generated, hooks: mergedHooks };\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nconst START_MARKER =\n \"<!-- HOLDPOINT_MANAGED — content between these markers is auto-generated by holdpoint init / holdpoint update -->\";\nconst END_MARKER = \"<!-- /HOLDPOINT_MANAGED -->\";\n\n/** Canonical breadcrumb content. Same wording for every agent on purpose. */\nexport const BREADCRUMB_BODY = `## Holdpoint workflow\n\nThis repo uses [Holdpoint](https://holdpoint.dev) to gate task completion on deterministic checks.\n\nBefore marking any task done:\n\n1. Run \\`holdpoint check\\` (or it will run automatically via Stop / TaskCompleted hooks).\n2. Fix any failures. Re-run until exit 0.\n3. Never bypass with \\`--no-verify\\` or by skipping the agent's stop hook.\n\nFull workflow reference: [\\`MASTER_PROMPT.md\\`](./MASTER_PROMPT.md) (always injected at session start).\nDeep reference: [\\`HOLDPOINT_REFERENCE.md\\`](./HOLDPOINT_REFERENCE.md) (read on demand).\nActive checks: [\\`checks.yaml\\`](./checks.yaml).`;\n\n/**\n * Idempotently splice the Holdpoint breadcrumb into a markdown instructions file.\n * - If the file is missing and createIfMissing: write a new file containing only the breadcrumb block.\n * - If the file has the markers: replace content between them.\n * - If the file exists but has no markers: append the breadcrumb block separated by a blank line.\n * Content outside the markers is never touched.\n */\nexport function spliceBreadcrumb(\n filePath: string,\n body: string = BREADCRUMB_BODY,\n createIfMissing = true,\n): void {\n const block = `${START_MARKER}\\n\\n${body}\\n\\n${END_MARKER}`;\n if (!existsSync(filePath)) {\n if (!createIfMissing) return;\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, block + \"\\n\", \"utf8\");\n return;\n }\n const existing = readFileSync(filePath, \"utf8\");\n const startIdx = existing.indexOf(START_MARKER);\n const endIdx = existing.indexOf(END_MARKER);\n if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {\n const before = existing.slice(0, startIdx);\n const after = existing.slice(endIdx + END_MARKER.length);\n writeFileSync(filePath, before + block + after, \"utf8\");\n return;\n }\n const sep = existing.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n writeFileSync(filePath, existing + sep + block + \"\\n\", \"utf8\");\n}\n"],"mappings":"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","names":["execSync","existsSync","readFileSync","writeFileSync","mkdirSync","join","dirname","fileURLToPath","chalk","existsSync","isObject","asHookArray","existsSync","readFileSync","writeFileSync","dirname","__dirname","dirname","fileURLToPath","join","existsSync","chalk","readFileSync","writeFileSync","mkdirSync","execSync"]}
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  mergeClaudeSettings,
7
7
  mergeCursorHooks,
8
8
  spliceBreadcrumb
9
- } from "./chunk-D7ZF5JJH.js";
9
+ } from "./chunk-5TMH2JOD.js";
10
10
 
11
11
  // src/index.ts
12
12
  import { Command } from "commander";
@@ -123,7 +123,7 @@ async function checkCommand(options) {
123
123
  console.error(chalk.dim("Skipped. Run `holdpoint init` when you're ready."));
124
124
  process.exit(1);
125
125
  }
126
- const { initCommand: initCommand2 } = await import("./init-Y7NZBMU6.js");
126
+ const { initCommand: initCommand2 } = await import("./init-LXQG4KBI.js");
127
127
  await initCommand2({});
128
128
  console.log(
129
129
  chalk.dim("\nReview ") + chalk.yellow("checks.yaml") + chalk.dim(" and run ") + chalk.yellow("holdpoint check") + chalk.dim(" again when you're ready.")
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ initCommand
4
+ } from "./chunk-5TMH2JOD.js";
5
+ export {
6
+ initCommand
7
+ };
8
+ //# sourceMappingURL=init-LXQG4KBI.js.map
@@ -341,6 +341,33 @@ Provided by Holdpoint — use this to avoid knowledge-cutoff confusion.
341
341
 
342
342
  ---
343
343
 
344
+ ## `security_scan`
345
+
346
+ At the start of a fresh session Holdpoint runs a lightweight security scan and injects
347
+ a banner as session context when something is worth surfacing:
348
+
349
+ - **Unverified MCP servers** — any MCP server in `.mcp.json` / `.claude/mcp.json` whose
350
+ executed package isn't a recognised first-party / well-known server. Trust is bound to
351
+ the package that actually runs (the `npx`/`uvx`/`dlx` target or command binary), not the
352
+ human-set `name`, so a server can't mark itself trusted by label. Non-npm servers (e.g.
353
+ `uvx`-launched Python servers) are listed separately as "source not checkable" rather
354
+ than flagged as unverified.
355
+ - **`high`/`critical` dependency advisories** — from `npm`/`pnpm`/`yarn audit` (Yarn Berry
356
+ uses `yarn npm audit`). Findings are sorted by severity so criticals are never dropped.
357
+
358
+ The scan is **on by default**. It runs only on a genuine fresh session start (not on
359
+ `resume`/`compact` re-fires, and not on subagent starts), so it never re-runs the audit
360
+ subprocess on the hot path. To opt out entirely:
361
+
362
+ ```yaml
363
+ security_scan: false
364
+ ```
365
+
366
+ Setting this to `false` also skips the dependency-audit subprocess, so a session that has
367
+ nothing else to inject won't run a session-start context hook at all.
368
+
369
+ ---
370
+
344
371
  ## Commands
345
372
 
346
373
  | Command | What it does |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holdpoint/cli",
3
- "version": "0.1.0-alpha.21",
3
+ "version": "0.1.0-alpha.22",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -48,18 +48,18 @@
48
48
  "chalk": "^5.3.0",
49
49
  "commander": "^15.0.0",
50
50
  "ora": "^9.4.0",
51
- "@holdpoint/sdk": "0.1.0-alpha.4",
52
- "@holdpoint/live-daemon": "0.1.0-alpha.6",
53
- "@holdpoint/engine-claude": "0.1.0-alpha.15",
54
- "@holdpoint/engine-codex": "0.1.0-alpha.16",
51
+ "@holdpoint/live-daemon": "0.1.0-alpha.7",
55
52
  "@holdpoint/live-protocol": "0.1.0-alpha.4",
56
- "@holdpoint/engine-copilot": "0.1.0-alpha.16",
57
- "@holdpoint/engine-cursor": "0.1.0-alpha.14",
58
- "@holdpoint/types": "0.1.0-alpha.10",
59
- "@holdpoint/yaml-core": "0.1.0-alpha.11"
53
+ "@holdpoint/sdk": "0.1.0-alpha.4",
54
+ "@holdpoint/engine-claude": "0.1.0-alpha.16",
55
+ "@holdpoint/engine-codex": "0.1.0-alpha.17",
56
+ "@holdpoint/engine-copilot": "0.1.0-alpha.17",
57
+ "@holdpoint/engine-cursor": "0.1.0-alpha.15",
58
+ "@holdpoint/types": "0.1.0-alpha.11",
59
+ "@holdpoint/yaml-core": "0.1.0-alpha.12"
60
60
  },
61
61
  "devDependencies": {
62
- "@types/node": "^25.9.1",
62
+ "@types/node": "^25.9.2",
63
63
  "tsup": "^8.3.5",
64
64
  "typescript": "^6.0.3",
65
65
  "vitest": "^4.1.8"
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/init.ts","../src/detect.ts","../src/templates.ts","../src/lib/preflight.ts","../src/claude-settings.ts","../src/cursor-hooks.ts","../src/lib/instructions-breadcrumb.ts"],"sourcesContent":["import { execSync } from \"node:child_process\";\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { buildConfigJson, buildEngine } from \"@holdpoint/engine-copilot\";\nimport { buildEngineJson as buildClaudeEngineJson } from \"@holdpoint/engine-claude\";\nimport {\n buildCheckScript as buildCursorCheckScript,\n buildEngine as buildCursorEngine,\n buildHooksJson as buildCursorHooksJson,\n} from \"@holdpoint/engine-cursor\";\nimport {\n buildConfigToml as buildCodexConfigToml,\n buildHooksJson as buildCodexHooksJson,\n buildCheckScript as buildCodexCheckScript,\n} from \"@holdpoint/engine-codex\";\nimport { parseHoldpointYaml } from \"@holdpoint/yaml-core\";\nimport type { AgentType } from \"@holdpoint/types\";\nimport { detectPackageManager, type PackageManager } from \"../detect.js\";\nimport { ensureBundledFile } from \"../templates.js\";\nimport { runPreflight, printPreflight } from \"../lib/preflight.js\";\nimport { mergeClaudeSettings } from \"../claude-settings.js\";\nimport { mergeCursorHooks } from \"../cursor-hooks.js\";\nimport { spliceBreadcrumb } from \"../lib/instructions-breadcrumb.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nfunction getDefaultTemplatePath(): string {\n const candidates = [\n join(__dirname, \"templates\", \"default.yaml\"), // dist/templates/ (published package)\n join(__dirname, \"../../../templates\", \"default.yaml\"), // monorepo dev fallback\n join(process.cwd(), \"templates\", \"default.yaml\"), // cwd fallback\n ];\n for (const p of candidates) {\n if (existsSync(p)) return p;\n }\n return \"\";\n}\n\nconst MINIMAL_CHECKS_YAML = `version: 1\ncontext:\n guides: {}\nconditions: []\nchecks:\n - id: karpathy-practices\n label: \"Karpathy coding practices\"\n on: session_start\n inject:\n text: |\n ## Coding practices (enforced by Holdpoint)\n\n 1. Read all relevant files fully before making any change.\n 2. Make the smallest change that satisfies the task. No unsolicited refactors.\n 3. Edit existing code surgically. Do not rewrite what you were not asked to rewrite.\n 4. Do not add logging, tests, types, or abstractions that were not asked for.\n 5. If a test suite exists, it must pass before you stop.\n 6. Complete exactly what was asked. Log adjacent issues as TO${\"DO\"}s, don't fix them.\n\n - id: check-test-suite\n label: \"Test suite passes\"\n on: before_done\n prompt: \"If this project has a test suite (package.json test script, pytest, go test, etc.), confirm it passes. If you did not run it, run it now before finishing.\"\n\n - id: check-minimal-change\n label: \"Change is minimal and scoped\"\n on: before_done\n prompt: \"Review every file you changed. Remove any addition that was not explicitly requested — unused imports, debug logs, reformatted blocks, unrequested refactors. Confirm the diff is surgical.\"\n\n - id: lint\n label: \"Lint codebase\"\n cmd: \"echo 'Add your lint command here'\"\n\n - id: jsdoc\n label: \"JSDoc on changed public functions\"\n prompt: \"Ensure all changed public functions and exports have JSDoc comments.\"\n`;\n\nconst MINIMAL_MASTER_PROMPT = `# Holdpoint\n\nRun \\`holdpoint check\\` before marking any task complete.\nSee \\`checks.yaml\\` for the full list of checks.\n`;\n\nconst MINIMAL_HOLDPOINT_REFERENCE = `# Holdpoint reference\n\nRead \\`MASTER_PROMPT.md\\` first for the mandatory workflow, then use this file for deeper project-specific Holdpoint notes.\n`;\n\nconst MINIMAL_PREREQUISITES = `# Holdpoint prerequisites\n\nHoldpoint installed repo-local engine integrations for one or more AI coding agents. Before relying on them locally, review these setup notes:\n\n- **GitHub Copilot CLI** — Holdpoint's \\`.github/extensions/holdpoint/extension.mjs\\` uses the Copilot CLI **EXTENSIONS** feature. Today that feature is gated behind experimental mode. In Copilot CLI, run \\`/experimental on\\` so **EXTENSIONS** appears in the enabled feature set before using Holdpoint locally.\n- **Cursor** — project-level hooks run in trusted workspaces. After opening the repo in Cursor, confirm the workspace is trusted and review Settings → Hooks if hooks do not fire.\n- **OpenAI Codex** — project-level hooks require trust approval. Run \\`codex trust\\` in the Codex TUI or review the hook with \\`/hooks\\`.\n- **General** — Holdpoint expects Node.js 18+ and a git repository so \\`holdpoint init\\`, \\`holdpoint update\\`, and \\`holdpoint check\\` can run normally.\n\nDocs: https://holdpoint.dev/docs\n`;\n\n/**\n * Initialise Holdpoint in the current project.\n *\n * Writes `checks.yaml` (from the unified default template), `checks.immutable.json`,\n * engine files for each target agent (Copilot, Claude, Cursor, Codex),\n * and repo-local handoff docs such as `HOLDPOINT_PREREQUISITES.md`.\n * Defaults to installing all four agents; pass `--agent` to restrict to one.\n */\nexport async function initCommand(options: { agent?: string }): Promise<void> {\n const spinner = ora(\"Initialising Holdpoint…\").start();\n\n // Default: install for all agents. Pass --agent=copilot|claude|cursor to restrict.\n const agentOpt = options.agent;\n const agents: AgentType[] =\n !agentOpt || agentOpt === \"all\"\n ? [\"copilot\", \"claude\", \"cursor\", \"codex\"]\n : [agentOpt as AgentType];\n\n spinner.text = `Installing for: ${chalk.cyan(agents.join(\", \"))}`;\n\n // Detect package manager once — used both for template substitution and devDep install.\n const pm = detectPackageManager();\n\n // 1. Read or create checks.yaml\n let yamlContent = MINIMAL_CHECKS_YAML;\n if (!existsSync(\"checks.yaml\")) {\n const templatePath = getDefaultTemplatePath();\n if (templatePath) {\n yamlContent = readFileSync(templatePath, \"utf8\");\n }\n // Substitute the package manager so checks use the right runner (npm/yarn/pnpm).\n if (pm !== \"pnpm\") {\n yamlContent = yamlContent.replace(/\\bpnpm\\b/g, pm);\n }\n writeFileSync(\"checks.yaml\", yamlContent, \"utf8\");\n } else {\n yamlContent = readFileSync(\"checks.yaml\", \"utf8\");\n }\n\n const config = parseHoldpointYaml(yamlContent);\n\n // 2. Write checks.immutable.json — read by holdpoint-check.mjs at runtime\n const generatedDir = \".github/holdpoint/generated\";\n mkdirSync(generatedDir, { recursive: true });\n writeFileSync(`${generatedDir}/checks.immutable.json`, buildConfigJson(config), \"utf8\");\n\n // 3. Install engine files for each target agent\n if (agents.includes(\"copilot\")) {\n // extension.mjs handles both session context injection (onSessionStart) and\n // check enforcement (onPreToolUse → task_complete). No separate hooks files needed.\n const extDir = \".github/extensions/holdpoint\";\n mkdirSync(extDir, { recursive: true });\n writeFileSync(join(extDir, \"extension.mjs\"), buildEngine(config), \"utf8\");\n spliceBreadcrumb(\".github/copilot-instructions.md\");\n }\n\n if (agents.includes(\"claude\")) {\n mkdirSync(\".claude\", { recursive: true });\n const settingsPath = \".claude/settings.json\";\n let existing: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try {\n existing = JSON.parse(readFileSync(settingsPath, \"utf8\")) as Record<string, unknown>;\n } catch {\n /* ignore */\n }\n }\n const holdpointHooks = JSON.parse(buildClaudeEngineJson(config)) as Record<string, unknown>;\n writeFileSync(\n settingsPath,\n JSON.stringify(mergeClaudeSettings(existing, holdpointHooks), null, 2),\n \"utf8\",\n );\n spliceBreadcrumb(\"CLAUDE.md\");\n }\n\n if (agents.includes(\"cursor\")) {\n mkdirSync(\".cursor\", { recursive: true });\n const cursorHooksPath = \".cursor/hooks.json\";\n let existingHooks: Record<string, unknown> = {};\n if (existsSync(cursorHooksPath)) {\n try {\n existingHooks = JSON.parse(readFileSync(cursorHooksPath, \"utf8\")) as Record<\n string,\n unknown\n >;\n } catch {\n /* ignore */\n }\n }\n const cursorHooks = JSON.parse(buildCursorHooksJson(config)) as Record<string, unknown>;\n writeFileSync(\n cursorHooksPath,\n JSON.stringify(mergeCursorHooks(existingHooks, cursorHooks), null, 2) + \"\\n\",\n \"utf8\",\n );\n writeFileSync(\".cursor/holdpoint-hook.mjs\", buildCursorCheckScript(), \"utf8\");\n\n const cursorRules = buildCursorEngine(config);\n const cursorPath = \".cursorrules\";\n if (existsSync(cursorPath)) {\n const existing = readFileSync(cursorPath, \"utf8\");\n if (!existing.includes(\"Holdpoint Rules\")) {\n writeFileSync(cursorPath, `${existing.trimEnd()}\\n\\n${cursorRules}`, \"utf8\");\n }\n } else {\n writeFileSync(cursorPath, cursorRules, \"utf8\");\n }\n spliceBreadcrumb(\".cursor/rules/holdpoint.md\");\n }\n\n if (agents.includes(\"codex\")) {\n mkdirSync(\".codex\", { recursive: true });\n writeFileSync(\".codex/hooks.json\", buildCodexHooksJson(config), \"utf8\");\n writeFileSync(\".codex/holdpoint-check.mjs\", buildCodexCheckScript(config), \"utf8\");\n writeFileSync(\".codex/config.toml\", buildCodexConfigToml(), \"utf8\");\n spliceBreadcrumb(\"AGENTS.md\");\n }\n\n // 4. Create repo-local guidance files if not present\n ensureBundledFile(\"MASTER_PROMPT.md\", \"MASTER_PROMPT.md\", MINIMAL_MASTER_PROMPT);\n ensureBundledFile(\n \"HOLDPOINT_REFERENCE.md\",\n \"HOLDPOINT_REFERENCE.md\",\n MINIMAL_HOLDPOINT_REFERENCE,\n );\n ensureBundledFile(\n \"HOLDPOINT_PREREQUISITES.md\",\n \"HOLDPOINT_PREREQUISITES.md\",\n MINIMAL_PREREQUISITES,\n );\n\n // Install holdpoint as a devDependency so hooks resolve via node_modules/.bin\n // rather than downloading on every hook fire via npx.\n spinner.text = \"Installing holdpoint as a devDependency…\";\n const installCmds: Record<PackageManager, string> = {\n pnpm: \"pnpm add -D holdpoint\",\n yarn: \"yarn add --dev holdpoint\",\n npm: \"npm install --save-dev holdpoint\",\n };\n const installCmd = installCmds[pm];\n try {\n execSync(installCmd, { stdio: \"pipe\" });\n spinner.succeed(chalk.bold.green(\"Holdpoint initialised!\"));\n } catch {\n spinner.warn(\n chalk.yellow(`Holdpoint initialised, but could not install the package automatically.`) +\n `\\n Run manually: ${chalk.yellow(installCmd)}`,\n );\n }\n\n // Per-agent preflight: surface the Copilot `/experimental on` step and Codex\n // `codex trust` step at install time.\n // instead of burying them in HOLDPOINT_PREREQUISITES.md where users\n // routinely miss them.\n const preflight = runPreflight(agents);\n printPreflight(preflight);\n\n // If `holdpoint` is already on PATH (typically because the user has the\n // global install), use the bare command. Otherwise fall back to `npx\n // holdpoint` so the suggested commands actually work for users who only\n // have the local devDep.\n let holdpointInvocation = \"npx holdpoint\";\n try {\n execSync(\"command -v holdpoint\", { stdio: \"pipe\" });\n holdpointInvocation = \"holdpoint\";\n } catch {\n // not on PATH — keep the npx fallback\n }\n\n console.log(`\n${chalk.cyan(\"Next steps:\")}\n Karpathy coding practices are active via ${chalk.yellow(\"checks.yaml\")}\n 1. Edit ${chalk.yellow(\"checks.yaml\")} to customise your eval checkpoints\n 2. Address any ${chalk.yellow(\"→\")} items above (full notes in ${chalk.yellow(\"HOLDPOINT_PREREQUISITES.md\")})\n 3. Commit ${chalk.yellow(\"checks.yaml\")}, ${chalk.yellow(\"HOLDPOINT_PREREQUISITES.md\")}, and the generated engine files\n 4. Run ${chalk.yellow(holdpointInvocation)} ${chalk.yellow(\"check\")} at any time to validate\n\n Visual builder: ${chalk.yellow(holdpointInvocation + \" builder\")} (opens the daemon at /builder)\n Agents: ${chalk.cyan(agents.join(\", \"))}\n`);\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport type { AgentType } from \"@holdpoint/types\";\n\nexport type PackageManager = \"pnpm\" | \"yarn\" | \"npm\";\n\n/** Detect which package manager owns the current project by checking lock files. */\nexport function detectPackageManager(): PackageManager {\n if (existsSync(\"pnpm-lock.yaml\")) return \"pnpm\";\n if (existsSync(\"yarn.lock\")) return \"yarn\";\n return \"npm\";\n}\n\n/** @deprecated Use detectInstalledAgents() — single-agent detection is no longer the default. */\nexport function detectAgent(): AgentType {\n if (existsSync(\".github/extensions\")) return \"copilot\";\n if (existsSync(\".claude\")) return \"claude\";\n if (existsSync(\".cursor/hooks.json\")) return \"cursor\";\n if (existsSync(\".cursorrules\")) return \"cursor\";\n if (existsSync(\".codex\")) return \"codex\";\n return \"unknown\";\n}\n\n/**\n * Returns every agent whose Holdpoint engine files are already present in the\n * current working directory. Used by `holdpoint update` so it regenerates only\n * the engines that were previously installed.\n */\nexport function detectInstalledAgents(): AgentType[] {\n const agents: AgentType[] = [];\n if (existsSync(\".github/extensions/holdpoint/extension.mjs\")) agents.push(\"copilot\");\n if (existsSync(\".claude/settings.json\")) agents.push(\"claude\");\n if (existsSync(\".cursor/hooks.json\")) {\n try {\n if (readFileSync(\".cursor/hooks.json\", \"utf8\").includes(\"HOLDPOINT_MANAGED=cursor\")) {\n agents.push(\"cursor\");\n }\n } catch {\n /* ignore unreadable file */\n }\n }\n if (existsSync(\".cursorrules\")) {\n try {\n if (\n !agents.includes(\"cursor\") &&\n readFileSync(\".cursorrules\", \"utf8\").includes(\"Holdpoint Rules\")\n ) {\n agents.push(\"cursor\");\n }\n } catch {\n /* ignore unreadable file */\n }\n }\n // Detect Codex by the generated check script (more specific than .codex/ existence)\n if (existsSync(\".codex/holdpoint-check.mjs\")) agents.push(\"codex\");\n return agents;\n}\n","import { copyFileSync, existsSync, writeFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport function getBundledTemplatePath(filename: string): string {\n const candidates = [\n join(__dirname, \"templates\", filename), // dist/templates/ (published package)\n join(__dirname, \"../../../templates\", filename), // monorepo dev fallback\n join(process.cwd(), \"templates\", filename), // cwd fallback\n ];\n for (const candidate of candidates) {\n if (existsSync(candidate)) return candidate;\n }\n return \"\";\n}\n\nexport function ensureBundledFile(\n outputPath: string,\n templateFilename: string,\n fallbackContent: string,\n): boolean {\n if (existsSync(outputPath)) {\n return false;\n }\n\n const templatePath = getBundledTemplatePath(templateFilename);\n if (templatePath) {\n copyFileSync(templatePath, outputPath);\n } else {\n writeFileSync(outputPath, fallbackContent, \"utf8\");\n }\n\n return true;\n}\n","import { execSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport type { AgentType } from \"@holdpoint/types\";\n\n/**\n * Result of a single preflight check for one agent.\n *\n * - `ok`: the agent's toolchain is detected and ready to use.\n * - `action_required`: we can be specific about something the user must do\n * (install a tool, enable a feature, approve a hook) — `command` is set.\n * - `unknown`: detection failed but we can't be sure what the user needs.\n */\nexport type PreflightStatus = \"ok\" | \"action_required\" | \"unknown\";\n\nexport interface PreflightResult {\n agent: AgentType;\n status: PreflightStatus;\n /** One-line human-readable summary, no trailing newline. */\n message: string;\n /** When `action_required`, the exact command the user should run next. */\n command?: string;\n /** When set, points at HOLDPOINT_PREREQUISITES.md or external docs. */\n docs?: string;\n}\n\n/**\n * Run silently — used for tool detection where we expect failure to be common\n * and don't want stderr bleeding into the init spinner output.\n */\nfunction silentExec(cmd: string): { ok: boolean; stdout: string } {\n try {\n const stdout = execSync(cmd, { stdio: [\"ignore\", \"pipe\", \"ignore\"] }).toString();\n return { ok: true, stdout };\n } catch {\n return { ok: false, stdout: \"\" };\n }\n}\n\n/**\n * Copilot CLI preflight.\n *\n * We can detect `gh` and the `gh-copilot` extension reliably, but the\n * `/experimental on` requirement is runtime state inside the Copilot CLI\n * session itself — there's no clean filesystem signal. So we always remind\n * users to enable it, even when everything else looks healthy.\n */\nfunction checkCopilot(): PreflightResult {\n const gh = silentExec(\"gh --version\");\n if (!gh.ok) {\n return {\n agent: \"copilot\",\n status: \"action_required\",\n message: \"GitHub CLI not found on PATH\",\n command: \"brew install gh # or: https://cli.github.com/\",\n };\n }\n\n // `gh copilot --version` exits non-zero if the extension isn't installed.\n const copilot = silentExec(\"gh copilot --version\");\n if (!copilot.ok) {\n return {\n agent: \"copilot\",\n status: \"action_required\",\n message: \"Copilot CLI extension not installed\",\n command: \"gh extension install github/gh-copilot\",\n };\n }\n\n return {\n agent: \"copilot\",\n status: \"action_required\",\n message: \"Copilot CLI detected — experimental mode required for EXTENSIONS\",\n command: \"Inside Copilot CLI, run: /experimental on\",\n };\n}\n\n/**\n * Claude Code preflight. Settings live at `.claude/settings.json`, which we\n * write ourselves during init, so the only failure mode is the binary\n * missing from PATH (in which case the user still wants the settings file\n * written — they may be invoking Claude Code from a different machine).\n */\nfunction checkClaude(): PreflightResult {\n const claude = silentExec(\"claude --version\");\n if (!claude.ok) {\n return {\n agent: \"claude\",\n status: \"unknown\",\n message: \"Claude Code binary not on PATH (hooks still written for when it is)\",\n };\n }\n return {\n agent: \"claude\",\n status: \"ok\",\n message: \"Claude Code detected — hooks installed at .claude/settings.json\",\n };\n}\n\n/**\n * Cursor preflight. Project `.cursor/hooks.json` hooks run only in trusted\n * workspaces, which is runtime state inside Cursor and not reliably readable\n * from disk, so init prints the trust/debugging reminder every time.\n */\nfunction checkCursor(): PreflightResult {\n return {\n agent: \"cursor\",\n status: \"ok\",\n message: \"Cursor — .cursor/hooks.json gate + .cursor/rules breadcrumb installed\",\n docs: \"https://holdpoint.dev/docs#cursor\",\n };\n}\n\n/**\n * Codex preflight. Project-level hooks require trust approval inside the\n * Codex TUI, which is runtime state we can't read from disk reliably across\n * Codex versions — so this mirrors the Copilot pattern: detect the binary,\n * always remind the user about `codex trust`.\n */\nfunction checkCodex(): PreflightResult {\n const codex = silentExec(\"codex --version\");\n if (!codex.ok) {\n return {\n agent: \"codex\",\n status: \"action_required\",\n message: \"Codex CLI not found on PATH\",\n command: \"Install Codex: https://github.com/openai/codex\",\n };\n }\n return {\n agent: \"codex\",\n status: \"action_required\",\n message: \"Codex detected — project-level hooks require trust approval\",\n command: \"In the Codex TUI: codex trust (or /hooks to review)\",\n };\n}\n\n// `AgentType` includes \"unknown\" for the stack-detection fallback path, but\n// only the four real agents have a preflight to run. `Partial` keeps this\n// honest instead of forcing a no-op stub.\nconst CHECKS: Partial<Record<AgentType, () => PreflightResult>> = {\n copilot: checkCopilot,\n claude: checkClaude,\n cursor: checkCursor,\n codex: checkCodex,\n};\n\n/**\n * Run preflight for the given agents and return results in input order.\n * Agents without a registered check are silently skipped — the caller's\n * agent list is the source of truth.\n *\n * Intentionally pure apart from `execSync` so callers can decide whether\n * to print, summarise, or fail-fast on the results.\n */\nexport function runPreflight(agents: readonly AgentType[]): PreflightResult[] {\n return agents.flatMap((agent) => {\n const check = CHECKS[agent];\n return check ? [check()] : [];\n });\n}\n\n/**\n * Pretty-print preflight results to stdout in the style used by `holdpoint init`.\n * Always groups by status so action items don't get lost between OKs.\n */\nexport function printPreflight(results: readonly PreflightResult[]): void {\n if (results.length === 0) return;\n\n const ok = results.filter((r) => r.status === \"ok\");\n const unknown = results.filter((r) => r.status === \"unknown\");\n const action = results.filter((r) => r.status === \"action_required\");\n\n console.log(\"\");\n console.log(chalk.bold(\"Agent preflight:\"));\n\n for (const r of ok) {\n console.log(` ${chalk.green(\"✓\")} ${r.agent.padEnd(7)} ${chalk.dim(r.message)}`);\n }\n for (const r of unknown) {\n console.log(` ${chalk.dim(\"?\")} ${r.agent.padEnd(7)} ${chalk.dim(r.message)}`);\n }\n for (const r of action) {\n console.log(` ${chalk.yellow(\"→\")} ${chalk.bold(r.agent.padEnd(7))} ${r.message}`);\n if (r.command) console.log(` ${chalk.cyan(r.command)}`);\n }\n}\n","type JsonObject = Record<string, unknown>;\n\nconst HOLDPOINT_CLAUDE_HOOK_MARKER = \"HOLDPOINT_MANAGED=claude\";\n\nfunction isObject(value: unknown): value is JsonObject {\n return value != null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction asHookArray(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [];\n}\n\nfunction isManagedHookCommand(value: unknown): boolean {\n return (\n isObject(value) &&\n typeof value.command === \"string\" &&\n value.command.includes(HOLDPOINT_CLAUDE_HOOK_MARKER)\n );\n}\n\nfunction isLegacyManagedHookCommand(value: unknown): boolean {\n if (!isObject(value) || typeof value.command !== \"string\") return false;\n return (\n value.command === \"node_modules/.bin/holdpoint event --engine claude --from-hook || true\" ||\n value.command === \"node_modules/.bin/holdpoint check --staged\"\n );\n}\n\nfunction isManagedHookEntry(value: unknown): boolean {\n if (!isObject(value)) return false;\n const hooks = asHookArray(value.hooks);\n return (\n hooks.length > 0 &&\n (hooks.every(isManagedHookCommand) || hooks.every(isLegacyManagedHookCommand))\n );\n}\n\n/**\n * Merge generated Holdpoint hooks into .claude/settings.json without clobbering\n * user-owned hooks. Re-runs remove only entries carrying Holdpoint's marker.\n */\nexport function mergeClaudeSettings(existing: JsonObject, generated: JsonObject): JsonObject {\n const existingHooks = isObject(existing.hooks) ? existing.hooks : {};\n const generatedHooks = isObject(generated.hooks) ? generated.hooks : {};\n const mergedHooks: JsonObject = {};\n\n for (const eventName of new Set([\n ...Object.keys(existingHooks),\n ...Object.keys(generatedHooks),\n ])) {\n const preserved = asHookArray(existingHooks[eventName]).filter(\n (entry) => !isManagedHookEntry(entry),\n );\n const next = asHookArray(generatedHooks[eventName]);\n if (preserved.length > 0 || next.length > 0) {\n mergedHooks[eventName] = [...preserved, ...next];\n }\n }\n\n return { ...existing, ...generated, hooks: mergedHooks };\n}\n","type JsonObject = Record<string, unknown>;\n\nconst HOLDPOINT_CURSOR_HOOK_MARKER = \"HOLDPOINT_MANAGED=cursor\";\n\nfunction isObject(value: unknown): value is JsonObject {\n return value != null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction asHookArray(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [];\n}\n\nfunction isManagedCursorHook(value: unknown): boolean {\n return (\n isObject(value) &&\n typeof value.command === \"string\" &&\n (value.command.includes(HOLDPOINT_CURSOR_HOOK_MARKER) ||\n value.command.includes(\".cursor/holdpoint-hook.mjs\"))\n );\n}\n\n/**\n * Merge generated Holdpoint Cursor hooks into `.cursor/hooks.json` without\n * clobbering user/partner hooks. Re-runs remove only entries carrying\n * Holdpoint's marker or the generated hook script path.\n */\nexport function mergeCursorHooks(existing: JsonObject, generated: JsonObject): JsonObject {\n const existingHooks = isObject(existing.hooks) ? existing.hooks : {};\n const generatedHooks = isObject(generated.hooks) ? generated.hooks : {};\n const mergedHooks: JsonObject = {};\n\n for (const eventName of new Set([\n ...Object.keys(existingHooks),\n ...Object.keys(generatedHooks),\n ])) {\n const preserved = asHookArray(existingHooks[eventName]).filter(\n (entry) => !isManagedCursorHook(entry),\n );\n const next = asHookArray(generatedHooks[eventName]);\n if (preserved.length > 0 || next.length > 0) {\n mergedHooks[eventName] = [...preserved, ...next];\n }\n }\n\n return { ...existing, ...generated, hooks: mergedHooks };\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nconst START_MARKER =\n \"<!-- HOLDPOINT_MANAGED — content between these markers is auto-generated by holdpoint init / holdpoint update -->\";\nconst END_MARKER = \"<!-- /HOLDPOINT_MANAGED -->\";\n\n/** Canonical breadcrumb content. Same wording for every agent on purpose. */\nexport const BREADCRUMB_BODY = `## Holdpoint workflow\n\nThis repo uses [Holdpoint](https://holdpoint.dev) to gate task completion on deterministic checks.\n\nBefore marking any task done:\n\n1. Run \\`holdpoint check\\` (or it will run automatically via Stop / TaskCompleted hooks).\n2. Fix any failures. Re-run until exit 0.\n3. Never bypass with \\`--no-verify\\` or by skipping the agent's stop hook.\n\nFull workflow reference: [\\`MASTER_PROMPT.md\\`](./MASTER_PROMPT.md) (always injected at session start).\nDeep reference: [\\`HOLDPOINT_REFERENCE.md\\`](./HOLDPOINT_REFERENCE.md) (read on demand).\nActive checks: [\\`checks.yaml\\`](./checks.yaml).`;\n\n/**\n * Idempotently splice the Holdpoint breadcrumb into a markdown instructions file.\n * - If the file is missing and createIfMissing: write a new file containing only the breadcrumb block.\n * - If the file has the markers: replace content between them.\n * - If the file exists but has no markers: append the breadcrumb block separated by a blank line.\n * Content outside the markers is never touched.\n */\nexport function spliceBreadcrumb(\n filePath: string,\n body: string = BREADCRUMB_BODY,\n createIfMissing = true,\n): void {\n const block = `${START_MARKER}\\n\\n${body}\\n\\n${END_MARKER}`;\n if (!existsSync(filePath)) {\n if (!createIfMissing) return;\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, block + \"\\n\", \"utf8\");\n return;\n }\n const existing = readFileSync(filePath, \"utf8\");\n const startIdx = existing.indexOf(START_MARKER);\n const endIdx = existing.indexOf(END_MARKER);\n if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {\n const before = existing.slice(0, startIdx);\n const after = existing.slice(endIdx + END_MARKER.length);\n writeFileSync(filePath, before + block + after, \"utf8\");\n return;\n }\n const sep = existing.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n writeFileSync(filePath, existing + sep + block + \"\\n\", \"utf8\");\n}\n"],"mappings":"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","names":["execSync","existsSync","readFileSync","writeFileSync","mkdirSync","join","dirname","fileURLToPath","chalk","existsSync","isObject","asHookArray","existsSync","readFileSync","writeFileSync","dirname","__dirname","dirname","fileURLToPath","join","existsSync","chalk","readFileSync","writeFileSync","mkdirSync","execSync"]}
@@ -1,14 +0,0 @@
1
- [
2
- "@anthropic-ai/mcp-server-brave-search",
3
- "@anthropic-ai/mcp-server-fetch",
4
- "@modelcontextprotocol/server-filesystem",
5
- "@modelcontextprotocol/server-github",
6
- "@modelcontextprotocol/server-gitlab",
7
- "@modelcontextprotocol/server-google-maps",
8
- "@modelcontextprotocol/server-postgres",
9
- "@modelcontextprotocol/server-slack",
10
- "@modelcontextprotocol/server-memory",
11
- "@modelcontextprotocol/server-puppeteer",
12
- "@modelcontextprotocol/server-sequential-thinking",
13
- "@modelcontextprotocol/server-everything"
14
- ]
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- initCommand
4
- } from "./chunk-D7ZF5JJH.js";
5
- export {
6
- initCommand
7
- };
8
- //# sourceMappingURL=init-Y7NZBMU6.js.map
@@ -1,20 +0,0 @@
1
- interface ScanResult {
2
- mcp: {
3
- server: string;
4
- verified: boolean;
5
- }[];
6
- deps: {
7
- name: string;
8
- severity: string;
9
- title: string;
10
- }[];
11
- }
12
- declare function extractPackageName(value: string): string | undefined;
13
- declare function parseAuditJson(raw: string): ScanResult["deps"];
14
- declare function runScan(root: string): Promise<ScanResult>;
15
- declare const __scanInternalsForTests: {
16
- parseAuditJson: typeof parseAuditJson;
17
- extractPackageName: typeof extractPackageName;
18
- };
19
-
20
- export { type ScanResult, __scanInternalsForTests, runScan };
package/dist/lib/scan.js DELETED
@@ -1,200 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/lib/scan.ts
4
- import { execFile } from "child_process";
5
- import { existsSync, readFileSync } from "fs";
6
- import { dirname, join, resolve } from "path";
7
- import { fileURLToPath } from "url";
8
- import { promisify } from "util";
9
- var execFileAsync = promisify(execFile);
10
- var HIGH_SEVERITIES = /* @__PURE__ */ new Set(["high", "critical"]);
11
- var AUDIT_MAX_FINDINGS = 5;
12
- var AUDIT_TIMEOUT_MS = 8e3;
13
- function readJson(path) {
14
- try {
15
- return JSON.parse(readFileSync(path, "utf8"));
16
- } catch {
17
- return void 0;
18
- }
19
- }
20
- function registryPath() {
21
- const here = dirname(fileURLToPath(import.meta.url));
22
- return join(here, "../data/verified-mcp-registry.json");
23
- }
24
- function loadRegistry() {
25
- const parsed = readJson(registryPath());
26
- const entries = Array.isArray(parsed) ? parsed : [];
27
- return new Set(entries.filter((entry) => typeof entry === "string"));
28
- }
29
- function asStringArray(value) {
30
- return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
31
- }
32
- function mcpServersFromConfig(config) {
33
- if (!config || typeof config !== "object") return [];
34
- const obj = config;
35
- const servers = obj.mcpServers ?? obj.servers;
36
- if (!servers || typeof servers !== "object" || Array.isArray(servers)) return [];
37
- return Object.entries(servers).map(([key, raw]) => {
38
- const server = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
39
- const record = server;
40
- return {
41
- key,
42
- ...typeof record.name === "string" ? { name: record.name } : {},
43
- ...typeof record.command === "string" ? { command: record.command } : {},
44
- args: asStringArray(record.args)
45
- };
46
- });
47
- }
48
- function extractPackageName(value) {
49
- const normalized = value.replace(/\\/g, "/");
50
- const nodeModules = normalized.lastIndexOf("node_modules/");
51
- if (nodeModules >= 0) {
52
- const after = normalized.slice(nodeModules + "node_modules/".length);
53
- const parts = after.split("/").filter(Boolean);
54
- if (parts[0]?.startsWith("@") && parts[1]) return `${parts[0]}/${parts[1]}`;
55
- return parts[0];
56
- }
57
- if (normalized.startsWith("@")) {
58
- const [scope, name] = normalized.split("/");
59
- return scope && name ? `${scope}/${name}` : void 0;
60
- }
61
- if (!normalized.includes("/") && /^[a-z0-9@._-]+$/i.test(normalized)) return normalized;
62
- return void 0;
63
- }
64
- function serverCandidates(entry) {
65
- const values = [entry.name, entry.command, ...entry.args].filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim());
66
- const packages = values.map(extractPackageName).filter((value) => Boolean(value));
67
- return [...values, ...packages];
68
- }
69
- function readMcp(root, registry) {
70
- const files = [join(root, ".mcp.json"), join(root, ".claude/mcp.json")];
71
- const results = [];
72
- for (const file of files) {
73
- if (!existsSync(file)) continue;
74
- for (const entry of mcpServersFromConfig(readJson(file))) {
75
- const verified = serverCandidates(entry).some((candidate) => registry.has(candidate));
76
- results.push({ server: entry.name ?? entry.key, verified });
77
- }
78
- }
79
- return results;
80
- }
81
- function detectPackageManager(root) {
82
- if (existsSync(join(root, "pnpm-lock.yaml"))) return "pnpm";
83
- if (existsSync(join(root, "yarn.lock"))) return "yarn";
84
- if (existsSync(join(root, "package-lock.json")) || existsSync(join(root, "npm-shrinkwrap.json"))) {
85
- return "npm";
86
- }
87
- return void 0;
88
- }
89
- function firstTitle(value) {
90
- if (typeof value === "string") return value;
91
- if (Array.isArray(value)) {
92
- for (const entry of value) {
93
- const title = firstTitle(entry);
94
- if (title) return title;
95
- }
96
- }
97
- if (value && typeof value === "object") {
98
- const record = value;
99
- return typeof record.title === "string" ? record.title : void 0;
100
- }
101
- return void 0;
102
- }
103
- function addFinding(findings, seen, name, severity, title) {
104
- if (!name || typeof severity !== "string") return;
105
- const normalizedSeverity = severity.toLowerCase();
106
- if (!HIGH_SEVERITIES.has(normalizedSeverity)) return;
107
- const normalizedTitle = firstTitle(title) ?? "Security advisory";
108
- const key = `${name}\0${normalizedSeverity}\0${normalizedTitle}`;
109
- if (seen.has(key)) return;
110
- seen.add(key);
111
- findings.push({ name, severity: normalizedSeverity, title: normalizedTitle });
112
- }
113
- function parseAuditJson(raw) {
114
- const findings = [];
115
- const seen = /* @__PURE__ */ new Set();
116
- const parseOne = (value) => {
117
- if (!value || typeof value !== "object") return;
118
- const data = value;
119
- if (data.vulnerabilities && typeof data.vulnerabilities === "object") {
120
- for (const [name, vuln] of Object.entries(data.vulnerabilities)) {
121
- if (!vuln || typeof vuln !== "object") continue;
122
- const v = vuln;
123
- addFinding(findings, seen, name, v.severity, v.via ?? v.title);
124
- }
125
- }
126
- if (data.advisories && typeof data.advisories === "object") {
127
- for (const advisory of Object.values(data.advisories)) {
128
- if (!advisory || typeof advisory !== "object") continue;
129
- const a = advisory;
130
- addFinding(
131
- findings,
132
- seen,
133
- typeof a.module_name === "string" ? a.module_name : void 0,
134
- a.severity,
135
- a.title
136
- );
137
- }
138
- }
139
- if (data.type === "auditAdvisory" && data.data && typeof data.data === "object") {
140
- const advisory = data.data.advisory;
141
- if (advisory && typeof advisory === "object") {
142
- const a = advisory;
143
- addFinding(
144
- findings,
145
- seen,
146
- typeof a.module_name === "string" ? a.module_name : void 0,
147
- a.severity,
148
- a.title
149
- );
150
- }
151
- }
152
- };
153
- try {
154
- parseOne(JSON.parse(raw));
155
- } catch {
156
- for (const line of raw.split(/\r?\n/)) {
157
- const trimmed = line.trim();
158
- if (!trimmed) continue;
159
- try {
160
- parseOne(JSON.parse(trimmed));
161
- } catch {
162
- }
163
- }
164
- }
165
- return findings.slice(0, AUDIT_MAX_FINDINGS);
166
- }
167
- async function runAudit(root) {
168
- const pm = detectPackageManager(root);
169
- if (!pm) return [];
170
- try {
171
- const { stdout } = await execFileAsync(pm, ["audit", "--json"], {
172
- cwd: root,
173
- encoding: "utf8",
174
- maxBuffer: 1024 * 1024 * 10,
175
- timeout: AUDIT_TIMEOUT_MS
176
- });
177
- return parseAuditJson(String(stdout || ""));
178
- } catch (error) {
179
- const stdout = error.stdout;
180
- if (typeof stdout !== "string" || !stdout.trim()) return [];
181
- return parseAuditJson(stdout);
182
- }
183
- }
184
- async function runScan(root) {
185
- const resolvedRoot = resolve(root);
186
- const registry = loadRegistry();
187
- return {
188
- mcp: readMcp(resolvedRoot, registry),
189
- deps: await runAudit(resolvedRoot)
190
- };
191
- }
192
- var __scanInternalsForTests = {
193
- parseAuditJson,
194
- extractPackageName
195
- };
196
- export {
197
- __scanInternalsForTests,
198
- runScan
199
- };
200
- //# sourceMappingURL=scan.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/lib/scan.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\nconst HIGH_SEVERITIES = new Set([\"high\", \"critical\"]);\nconst AUDIT_MAX_FINDINGS = 5;\nconst AUDIT_TIMEOUT_MS = 8_000;\n\nexport interface ScanResult {\n mcp: { server: string; verified: boolean }[];\n deps: { name: string; severity: string; title: string }[];\n}\n\ntype PackageManager = \"pnpm\" | \"yarn\" | \"npm\";\n\ntype McpEntry = {\n key: string;\n name?: string;\n command?: string;\n args: string[];\n};\n\nfunction readJson(path: string): unknown {\n try {\n return JSON.parse(readFileSync(path, \"utf8\"));\n } catch {\n return undefined;\n }\n}\n\nfunction registryPath(): string {\n const here = dirname(fileURLToPath(import.meta.url));\n return join(here, \"../data/verified-mcp-registry.json\");\n}\n\nfunction loadRegistry(): Set<string> {\n const parsed = readJson(registryPath());\n const entries = Array.isArray(parsed) ? parsed : [];\n return new Set(entries.filter((entry): entry is string => typeof entry === \"string\"));\n}\n\nfunction asStringArray(value: unknown): string[] {\n return Array.isArray(value)\n ? value.filter((entry): entry is string => typeof entry === \"string\")\n : [];\n}\n\nfunction mcpServersFromConfig(config: unknown): McpEntry[] {\n if (!config || typeof config !== \"object\") return [];\n const obj = config as Record<string, unknown>;\n const servers = obj.mcpServers ?? obj.servers;\n if (!servers || typeof servers !== \"object\" || Array.isArray(servers)) return [];\n\n return Object.entries(servers as Record<string, unknown>).map(([key, raw]) => {\n const server = raw && typeof raw === \"object\" && !Array.isArray(raw) ? raw : {};\n const record = server as Record<string, unknown>;\n return {\n key,\n ...(typeof record.name === \"string\" ? { name: record.name } : {}),\n ...(typeof record.command === \"string\" ? { command: record.command } : {}),\n args: asStringArray(record.args),\n };\n });\n}\n\nfunction extractPackageName(value: string): string | undefined {\n const normalized = value.replace(/\\\\/g, \"/\");\n const nodeModules = normalized.lastIndexOf(\"node_modules/\");\n if (nodeModules >= 0) {\n const after = normalized.slice(nodeModules + \"node_modules/\".length);\n const parts = after.split(\"/\").filter(Boolean);\n if (parts[0]?.startsWith(\"@\") && parts[1]) return `${parts[0]}/${parts[1]}`;\n return parts[0];\n }\n if (normalized.startsWith(\"@\")) {\n const [scope, name] = normalized.split(\"/\");\n return scope && name ? `${scope}/${name}` : undefined;\n }\n if (!normalized.includes(\"/\") && /^[a-z0-9@._-]+$/i.test(normalized)) return normalized;\n return undefined;\n}\n\nfunction serverCandidates(entry: McpEntry): string[] {\n const values = [entry.name, entry.command, ...entry.args]\n .filter((value): value is string => typeof value === \"string\" && value.trim().length > 0)\n .map((value) => value.trim());\n const packages = values\n .map(extractPackageName)\n .filter((value): value is string => Boolean(value));\n return [...values, ...packages];\n}\n\nfunction readMcp(root: string, registry: Set<string>): ScanResult[\"mcp\"] {\n const files = [join(root, \".mcp.json\"), join(root, \".claude/mcp.json\")];\n const results: ScanResult[\"mcp\"] = [];\n for (const file of files) {\n if (!existsSync(file)) continue;\n for (const entry of mcpServersFromConfig(readJson(file))) {\n const verified = serverCandidates(entry).some((candidate) => registry.has(candidate));\n results.push({ server: entry.name ?? entry.key, verified });\n }\n }\n return results;\n}\n\nfunction detectPackageManager(root: string): PackageManager | undefined {\n if (existsSync(join(root, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (existsSync(join(root, \"yarn.lock\"))) return \"yarn\";\n if (\n existsSync(join(root, \"package-lock.json\")) ||\n existsSync(join(root, \"npm-shrinkwrap.json\"))\n ) {\n return \"npm\";\n }\n return undefined;\n}\n\nfunction firstTitle(value: unknown): string | undefined {\n if (typeof value === \"string\") return value;\n if (Array.isArray(value)) {\n for (const entry of value) {\n const title = firstTitle(entry);\n if (title) return title;\n }\n }\n if (value && typeof value === \"object\") {\n const record = value as Record<string, unknown>;\n return typeof record.title === \"string\" ? record.title : undefined;\n }\n return undefined;\n}\n\nfunction addFinding(\n findings: ScanResult[\"deps\"],\n seen: Set<string>,\n name: string | undefined,\n severity: unknown,\n title: unknown,\n): void {\n if (!name || typeof severity !== \"string\") return;\n const normalizedSeverity = severity.toLowerCase();\n if (!HIGH_SEVERITIES.has(normalizedSeverity)) return;\n const normalizedTitle = firstTitle(title) ?? \"Security advisory\";\n const key = `${name}\\0${normalizedSeverity}\\0${normalizedTitle}`;\n if (seen.has(key)) return;\n seen.add(key);\n findings.push({ name, severity: normalizedSeverity, title: normalizedTitle });\n}\n\nfunction parseAuditJson(raw: string): ScanResult[\"deps\"] {\n const findings: ScanResult[\"deps\"] = [];\n const seen = new Set<string>();\n const parseOne = (value: unknown) => {\n if (!value || typeof value !== \"object\") return;\n const data = value as Record<string, unknown>;\n\n if (data.vulnerabilities && typeof data.vulnerabilities === \"object\") {\n for (const [name, vuln] of Object.entries(data.vulnerabilities as Record<string, unknown>)) {\n if (!vuln || typeof vuln !== \"object\") continue;\n const v = vuln as Record<string, unknown>;\n addFinding(findings, seen, name, v.severity, v.via ?? v.title);\n }\n }\n\n if (data.advisories && typeof data.advisories === \"object\") {\n for (const advisory of Object.values(data.advisories as Record<string, unknown>)) {\n if (!advisory || typeof advisory !== \"object\") continue;\n const a = advisory as Record<string, unknown>;\n addFinding(\n findings,\n seen,\n typeof a.module_name === \"string\" ? a.module_name : undefined,\n a.severity,\n a.title,\n );\n }\n }\n\n if (data.type === \"auditAdvisory\" && data.data && typeof data.data === \"object\") {\n const advisory = (data.data as Record<string, unknown>).advisory;\n if (advisory && typeof advisory === \"object\") {\n const a = advisory as Record<string, unknown>;\n addFinding(\n findings,\n seen,\n typeof a.module_name === \"string\" ? a.module_name : undefined,\n a.severity,\n a.title,\n );\n }\n }\n };\n\n try {\n parseOne(JSON.parse(raw));\n } catch {\n for (const line of raw.split(/\\r?\\n/)) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n parseOne(JSON.parse(trimmed));\n } catch {\n // Ignore non-JSON audit chatter.\n }\n }\n }\n return findings.slice(0, AUDIT_MAX_FINDINGS);\n}\n\nasync function runAudit(root: string): Promise<ScanResult[\"deps\"]> {\n const pm = detectPackageManager(root);\n if (!pm) return [];\n try {\n const { stdout } = await execFileAsync(pm, [\"audit\", \"--json\"], {\n cwd: root,\n encoding: \"utf8\",\n maxBuffer: 1024 * 1024 * 10,\n timeout: AUDIT_TIMEOUT_MS,\n });\n return parseAuditJson(String(stdout || \"\"));\n } catch (error) {\n const stdout = (error as { stdout?: unknown }).stdout;\n if (typeof stdout !== \"string\" || !stdout.trim()) return [];\n return parseAuditJson(stdout);\n }\n}\n\n// Verified MCP registry is bundled statically; community PRs welcome.\nexport async function runScan(root: string): Promise<ScanResult> {\n const resolvedRoot = resolve(root);\n const registry = loadRegistry();\n return {\n mcp: readMcp(resolvedRoot, registry),\n deps: await runAudit(resolvedRoot),\n };\n}\n\nexport const __scanInternalsForTests = {\n parseAuditJson,\n extractPackageName,\n};\n"],"mappings":"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","names":[]}