@rhize/skill-forge 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -4
- package/dist/cli.js +200 -41
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/commands/add.ts","../src/config.ts","../src/errors.ts","../src/gate/scanShared.ts","../src/preferences.ts","../src/install/resolve.ts","../src/util.ts","../src/install/quarantine.ts","../src/install/promote.ts","../src/install/fingerprint.ts","../src/install/promoteMcp.ts","../src/install/mcpCandidate.ts","../src/provenance.ts","../src/commands/gatePipeline.ts","../src/auditState.ts","../src/audit.ts","../src/gate/safety.ts","../src/gate/mcpSafety.ts","../src/gate/overlap.ts","../src/findings.ts","../src/license.ts","../src/gate/profile.ts","../src/gate/mcpCapabilities.ts","../src/gate/mcpOverlap.ts","../src/gate/skillMap.ts","../src/gate/mapOverlap.ts","../src/report/render.ts","../src/queue.ts","../src/handoff.ts","../src/commands/init.ts","../src/agents.ts","../src/commands/audit.ts","../src/decision.ts","../src/commands/scan.ts","../src/commands/list.ts","../src/commands/guide.ts","../src/commands/held.ts","../src/commands/config.ts","../src/commands/status.ts","../src/evolve.ts","../src/commands/evolve.ts","../src/commands/finding.ts","../src/commands/organize.ts","../src/organize.ts","../src/gate/skillsShApi.ts","../src/commands/find.ts","../src/commands/watch.ts","../src/gate/skillMapDrift.ts","../src/commands/routine.ts","../src/commands/ingest.ts","../src/commands/refine.ts","../src/refine/context.ts","../src/refine/models.ts","../src/refine/store.ts","../src/refine/patch.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { createRequire } from 'node:module';\nimport { add } from './commands/add.js';\nimport { scan } from './commands/scan.js';\nimport { list } from './commands/list.js';\nimport { guide } from './commands/guide.js';\nimport { configGet, configList, configPropose, configReview, configSet, configUnset } from './commands/config.js';\nimport { promoteHeld, rejectHeld } from './commands/held.js';\nimport { status } from './commands/status.js';\nimport { init } from './commands/init.js';\nimport { evolveCommand } from './commands/evolve.js';\nimport { auditCommand } from './commands/audit.js';\nimport { findingAcceptCommand, findingListCommand, findingRevokeCommand } from './commands/finding.js';\nimport { organizeCommand } from './commands/organize.js';\nimport { find } from './commands/find.js';\nimport { watchCommand } from './commands/watch.js';\nimport { routineCommand, type FailOn } from './commands/routine.js';\nimport { ingest, queueClose } from './commands/ingest.js';\nimport {\n refineCaptureCommand,\n refineListCommand,\n refinePatternsCommand,\n refinePromoteCommand,\n refineRollbackCommand,\n refineWhichCommand,\n} from './commands/refine.js';\nimport { UserError } from './errors.js';\nimport type { ArtifactType } from './install/resolve.js';\n\nconst require = createRequire(import.meta.url);\nconst pkg = require('../package.json') as { version: string };\n\n/** Commander option parser for `--artifact` — throws UserError (caught below) on anything but skill|mcp. */\nfunction parseArtifact(value: string): ArtifactType {\n if (value !== 'skill' && value !== 'mcp') {\n throw new UserError(`--artifact must be \"skill\" or \"mcp\" (got \"${value}\")`);\n }\n return value;\n}\n\n/** Commander option parser for `--lookback-hours` — throws UserError (caught below) on a non-finite, empty, or negative number. */\nfunction parseLookbackHours(value: string): number {\n const n = Number(value);\n if (value.trim() === '' || !Number.isFinite(n) || n < 0) {\n throw new UserError(`--lookback-hours must be a non-negative number (got \"${value}\")`);\n }\n return n;\n}\n\n/** Commander option parser for `find --limit` — throws UserError (caught below) on a non-positive-integer value. */\nfunction parseLimit(value: string): number {\n const n = Number(value);\n if (value.trim() === '' || !Number.isInteger(n) || n < 1 || n > 50) {\n throw new UserError(`--limit must be an integer between 1 and 50 (got \"${value}\")`);\n }\n return n;\n}\n\n/** Commander option parser for `routine --fail-on` — throws UserError (caught below) on an unknown level. */\nfunction parseFailOn(value: string): FailOn {\n const allowed: FailOn[] = ['none', 'low', 'medium', 'high', 'critical', 'any'];\n if (!(allowed as string[]).includes(value)) {\n throw new UserError(`--fail-on must be one of ${allowed.join('|')} (got \"${value}\")`);\n }\n return value as FailOn;\n}\n\n/** Commander option parser for `routine --prune-days` — throws UserError (caught below) on a non-positive value. */\nfunction parsePruneDays(value: string): number {\n const n = Number(value);\n if (value.trim() === '' || !Number.isFinite(n) || n < 1) {\n throw new UserError(`--prune-days must be a positive number (got \"${value}\")`);\n }\n return n;\n}\n\nconst program = new Command();\n\nprogram\n .name('skill-forge')\n .description('The supply-chain gate for agent skills')\n .version(pkg.version);\n\nprogram\n .command('add <source>')\n .description('Quarantine-install a skill and run it through the safety gate')\n .option('-y, --yes', 'skip the interactive prompt; honor the gate verdict automatically')\n .option('-t, --target <dir>', 'skills root to promote into (defaults to config defaultTarget, then skillsRoots[0])')\n .option('--json', 'print the gate result as JSON instead of the terminal report')\n .option('--ingest', 'after a successful promote, hand off the bundled ingest prompt to a coding agent')\n .option('--artifact <type>', 'gate as a \"skill\" or an \"mcp\" server (default: skill)', parseArtifact)\n .option(\n '--mcp-target <file>',\n 'MCP config file to promote into (mcp artifacts only; defaults to config.mcpTargets[0], then <cwd>/.mcp.json)'\n )\n .option('--force', 'overwrite an existing same-name MCP server entry when promoting (mcp artifacts only)')\n .option(\n '--skill-map <path>',\n 'path to a generated rhize-plugins skill map; reports existing-capability overlap against every skill node in it before promotion (curation-rule check)'\n )\n .action(add);\n\nprogram\n .command('scan <source>')\n .description('Run the safety gate against a skill without installing it')\n .option('--json', 'print the gate result as JSON instead of the terminal report')\n .option('--artifact <type>', 'gate as a \"skill\" or an \"mcp\" server (default: skill)', parseArtifact)\n .option(\n '--skill-map <path>',\n 'path to a generated rhize-plugins skill map; reports existing-capability overlap against every skill node in it (curation-rule check)'\n )\n .action(scan);\n\nprogram\n .command('list')\n .description('List skills currently in quarantine')\n .action(list);\n\nprogram\n .command('status')\n .description('Show skill-forge configuration and quarantine status')\n .action(status);\n\nprogram\n .command('promote <id>')\n .description('Re-gate a held quarantine entry and promote it (finishes a decision left on hold)')\n .option('-t, --target <dir>', 'skills root to promote into (defaults to config defaultTarget, then skillsRoots[0])')\n .option('--force', 'overwrite an existing same-name skill dir / MCP server entry in the target')\n .option('--mcp-target <file>', 'MCP config file to promote a held MCP candidate into (defaults to config.mcpTargets[0], then <cwd>/.mcp.json)')\n .option('-y, --yes', 'skip the prompt; honor the re-gate verdict automatically')\n .option('--json', 'print the re-gate result as JSON instead of the terminal report')\n .action(promoteHeld);\n\nprogram\n .command('reject <id>')\n .description('Discard a held quarantine entry without promoting it')\n .option('-y, --yes', 'skip the confirmation prompt')\n .action(rejectHeld);\n\nconst configCmd = program\n .command('config')\n .description('Read and write open-ended preferences (agent suggestions go to a review queue, never straight in)');\nconfigCmd\n .command('list', { isDefault: true })\n .description('Show accepted preferences and any pending proposals')\n .option('--json', 'print as JSON')\n .action(configList);\nconfigCmd\n .command('get <key>')\n .description('Print one preference value')\n .option('--json', 'print the full entry as JSON')\n .action(configGet);\nconfigCmd\n .command('set <key> <value>')\n .description('Set a preference you are stating yourself (takes effect immediately)')\n .option('--note <text>', 'why this is true')\n .option('--origin <text>', 'where it came from')\n .action(configSet);\nconfigCmd\n .command('unset <key>')\n .description('Remove a preference')\n .action(configUnset);\nconfigCmd\n .command('propose <key> <value>')\n .description('Queue a preference for review — the agent-facing door; never takes effect on its own')\n .option('--note <text>', 'why the agent believes this')\n .option('--origin <text>', 'which agent/session proposed it')\n .action(configPropose);\nconfigCmd\n .command('review')\n .description('Review pending agent-proposed preferences one at a time')\n .option('--accept-all', 'accept every pending proposal without prompting')\n .option('--reject-all', 'reject every pending proposal without prompting')\n .action(configReview);\n\nprogram\n .command('guide [topic]')\n .description('What this tool does, what state you are in, and the next command to run')\n .action(guide);\n\nprogram\n .command('evolve <target-skill-dir>')\n .description('Self-evolve a skill via SkillOpt-Sleep, re-gate the proposal, then promote/hold/reject (Pro)')\n .option('--project <dir>', 'project dir passed to SkillOpt-Sleep as --project (defaults to cwd)')\n .option('--dry-run', 'SkillOpt-Sleep dry-run: report only, nothing staged')\n .option('--backend <name>', 'SkillOpt-Sleep backend (default: mock, offline — a real backend requires --yes or interactive confirmation)')\n .option('--lookback-hours <n>', 'hours of session history for SkillOpt-Sleep to harvest', parseLookbackHours)\n .option('-y, --yes', 'skip interactive prompts; honor the re-gate verdict automatically')\n .option('--json', 'print the re-gate result as JSON instead of the terminal report')\n .option('--force', 'allow re-adopting a staging dir whose proposed content already matches the live skill')\n .action(evolveCommand);\n\nprogram\n .command('audit')\n .alias('doctor')\n .description('Doctor-style health check over the configured skill/MCP set: inventory, hygiene findings, and consolidation/refinement opportunities')\n .option('--json', 'print the audit report as JSON instead of the terminal summary (non-interactive: skips the business-profile prompt)')\n .option('-y, --yes', 'skip interactive prompts (business profile, foundation, handoff); never implies --foundation or --handoff')\n .option('--report <file>', 'write the report to this path instead of the default ~/.skill-forge/reports/audit-<timestamp>.md (refuses an existing path)')\n .option('--foundation', 'authorizes exactly one skills-root write: scaffold a business-foundation skill from the captured business profile')\n .option('--handoff', 'authorizes launching the configured agent with the bundled curation prompt against the written report')\n .action(auditCommand);\n\nconst finding = program\n .command('finding')\n .description('Review and acknowledge audit findings — human-only, never a gate input (see docs/accepted-findings-schema.md)');\nfinding\n .command('accept <fingerprint-prefix>')\n .description('Accept a currently-active LOW/MEDIUM finding (no override for HIGH/CRITICAL); re-runs the audit engine to resolve it')\n .requiredOption('--reason <text>', 'why this finding is accepted (mandatory, 1-200 chars, no control characters)')\n .option('--json', 'print the accepted record as JSON')\n .action(findingAcceptCommand);\nfinding\n .command('revoke <fingerprint-prefix>')\n .description('Remove a stored acceptance so the finding reports again')\n .option('--json', 'print the removed record as JSON')\n .action(findingRevokeCommand);\nfinding\n .command('list', { isDefault: true })\n .description('List every accepted finding')\n .option('--stale', 'only records whose target no longer exists on disk')\n .option('--json', 'print records as JSON')\n .action(findingListCommand);\n\nprogram\n .command('organize')\n .description('Build the set-level capability registry + dependency graph across configured skills roots (Pro)')\n .option('--json', 'print the registry+graph report as JSON instead of the terminal summary')\n .option('--out <file>', 'write the full report JSON to this path (refuses an existing path)')\n .option('--usage-snapshot <file>', 'join per-skill usage counts from a skill-monitor snapshot JSON')\n .action(organizeCommand);\n\nprogram\n .command('find [query]')\n .description('Discover skills via skills.sh and check partner security audits (requires VERCEL_OIDC_TOKEN)')\n .option('--limit <n>', 'max search results (default 10)', parseLimit)\n .option('--audit <id>', 'show partner audit verdicts (Socket/Snyk/…) for a skills.sh id')\n .option('--get <id>', 'show detail + file tree for a skills.sh id')\n .option('--curated', 'list skills.sh curated skills')\n .option('--json', 'print the raw API payload as JSON')\n .action(find);\n\nprogram\n .command('watch')\n .description('Drift check across every SOURCES.md provenance ledger: which ingested upstreams have moved (Pro)')\n .option('--offline', 'skip all network checks; list entries for manual comparison only')\n .option('--json', 'print the drift report as JSON instead of the terminal summary')\n .option(\n '--skill-map <path>',\n 'also drift-check every fork-of edge in a generated rhize-plugins skill map (contentHash compare against upstream)'\n )\n .action(watchCommand);\n\nprogram\n .command('routine')\n .description('One scheduled maintenance pass: audit + drift + registry, one report, cron-friendly exit codes (Pro)')\n .option('--json', 'print the routine result as JSON instead of the terminal summary')\n .option('--offline', 'skip every network check (drift entries are reported unchecked)')\n .option('--skip-watch', 'skip the drift check')\n .option('--skip-organize', 'skip the capability registry')\n .option('--fail-on <level>', 'exit 1 when a finding at or above this severity exists: none|low|medium|high|critical|any (default none)', parseFailOn)\n .option('--housekeeping', 'opt in to bounded writes: prune expired quarantine sandboxes, close orphaned queue entries')\n .option('--prune-days <n>', 'quarantine age threshold in days for --housekeeping (default 30)', parsePruneDays)\n .option('--dry-run', 'with --housekeeping: report what would change, change nothing')\n .option('--handoff', 'hand the written report to the configured agent (curation prompt)')\n .option('--report <file>', 'write the report here instead of ~/.skill-forge/reports/routine-<timestamp>.md (refuses an existing path)')\n .action(routineCommand);\n\nprogram\n .command('ingest')\n .description(\n 'Hand the pending-ingestion queue to the configured coding agent for the decide/absorb pass (Pro). ' +\n 'For a single new source, use `skill-forge add <source> --ingest` instead.'\n )\n .option('--list', 'print pending queue entries and exit — no handoff')\n .option('--json', 'with --list: print pending entries as JSON')\n .action(ingest);\n\nconst queue = program.command('queue').description('Manage the pending-ingestion queue (Pro)');\nqueue\n .command('close <id>')\n .description('Close a queue entry after the decide pass — agents use this instead of hand-editing queue.json')\n .requiredOption('--status <status>', 'final status: \"ingested\" or \"dismissed\"')\n .action(queueClose);\n\nconst refine = program\n .command('refine')\n .description('Capture, apply, and generalize improvements to installed skills from usage feedback (Pro)');\nrefine\n .command('capture', { isDefault: true })\n .description('Record a refinement and write a project-scope override artifact (never touches the base skill)')\n .option('--skill <name>', 'target skill name')\n .option('--category <cat>', 'trigger|content|hook|tool|pattern|config|new')\n .option('--target <section>', 'heading path or file the refinement targets, e.g. \"Hooks > duplicate-check\"')\n .option('--override-type <type>', 'patch|extend|config|full|hook|script|new')\n .option('--action <action>', 'patch action: append|prepend|replace-section|insert-after|insert-before|delete-section')\n .option('--marker <text>', 'marker text for insert-* / replace-section / delete-section (filename for full|hook|script)')\n .option('--content <text>', 'override content inline')\n .option('--content-file <file>', 'read override content from a file')\n .option('--expected <text>', 'expected behavior')\n .option('--actual <text>', 'actual observed behavior')\n .option('--example <text>', 'concrete example of the gap')\n .option('--outcome <text>', 'desired outcome')\n .option('--root-cause <text>', 'root cause per the gap analysis')\n .option('--pattern-id <id>', 'join an existing pattern (PAT-xxxx) instead of fingerprint matching')\n .option('--scope <scope>', 'where to write the artifact: \"local\" (.claude/skills, default) or \"shared\" (./skills)')\n .option('--dry-run', 'preview the artifact diff; write nothing')\n .option('--json', 'print the result as JSON')\n .option('-y, --yes', 'skip the interactive confirm; never implies --handoff')\n .option('--handoff', 'authorizes launching the configured agent with the bundled refine prompt')\n .action(refineCaptureCommand);\nrefine\n .command('list')\n .description('Show refinement history from the user-scope store')\n .option('--status <status>', 'filter by refinement status')\n .option('--skill <name>', 'filter by skill')\n .option('--project <name>', 'filter by project')\n .option('--json', 'print records as JSON')\n .action(refineListCommand);\nrefine\n .command('patterns')\n .description('Review tracked patterns and the generalization queue')\n .option('--status <status>', 'filter: tracking|ready|generalized|dismissed')\n .option('--skill <name>', 'filter by affected skill')\n .option('--project <name>', 'filter by project')\n .option('--json', 'print patterns as JSON')\n .action(refinePatternsCommand);\nrefine\n .command('which <skill>')\n .description('Print override-scope resolution order and which override files exist at each scope')\n .option('--user-root <dir>', 'explicit user-scope skills root')\n .option('--json', 'print the resolution as JSON')\n .action(refineWhichCommand);\nrefine\n .command('promote <pattern-id>')\n .description('Materialize a READY pattern into the user-scope skill default (backup + manifest first)')\n .option('--force', 'promote a pattern that is not READY')\n .option('--dry-run', 'preview the user-scope diff; write nothing')\n .option('--user-root <dir>', 'explicit user-scope skills root')\n .option('--json', 'print the result as JSON')\n .option('-y, --yes', 'skip the interactive confirm')\n .action(refinePromoteCommand);\nrefine\n .command('rollback <backup-id>')\n .description('Restore a promotion backup (refuses if files changed since promotion unless --force)')\n .option('--force', 'restore even if current files differ from the post-promotion state')\n .option('--json', 'print the result as JSON')\n .option('-y, --yes', 'skip the interactive confirm')\n .action(refineRollbackCommand);\n\nprogram\n .command('init')\n .description('Detect installed agents and set gate targets, default promotion target, and handoff agent')\n .option('--defaults', 'non-interactive: use all detected agents as gate targets, the first as the default target')\n .option('--list', 'print detected agent skill roots and exit — no writes')\n .option('--all-agents', 'pre-select every detected root, not just those of agents whose CLI is on PATH')\n .action(init);\n\ntry {\n await program.parseAsync();\n} catch (err) {\n if (err instanceof UserError) {\n console.error(`skill-forge: ${err.message}`);\n process.exit(1);\n }\n\n if (process.env.DEBUG === 'skill-forge') {\n console.error(err);\n } else {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`skill-forge: unexpected error: ${message}`);\n console.error('Re-run with DEBUG=skill-forge to see the full stack trace.');\n }\n process.exit(1);\n}\n","import { join } from 'node:path';\nimport { loadConfig, type Config } from '../config.js';\nimport { resolve as resolveSource, type ArtifactType } from '../install/resolve.js';\nimport { quarantine } from '../install/quarantine.js';\nimport { promote, reject, hold } from '../install/promote.js';\nimport { fingerprintDirs } from '../install/fingerprint.js';\nimport { promoteMcp } from '../install/promoteMcp.js';\nimport type { McpServerProfile } from '../install/promoteMcp.js';\nimport { resolveMcpCandidateDetail } from '../install/mcpCandidate.js';\nimport { appendProvenance } from '../provenance.js';\nimport { printGateOutput, runGate, resolveMcpCandidatePath } from './gatePipeline.js';\nimport { formatSafetyFinding } from '../gate/scanShared.js';\nimport { suggestVerbCode } from '../gate/overlap.js';\nimport { appendQueueEntry } from '../queue.js';\nimport { runHandoff } from '../handoff.js';\nimport { maybeOfferInit } from './init.js';\nimport { logOutcome, promptDecision, yesDecision } from '../decision.js';\n\nconst PRO_FEATURE_LABEL = 'overlap analysis, the provenance ledger, and the pending-ingestion queue';\nconst MCP_PRO_FEATURE_LABEL = 'overlap analysis and the pending-ingestion queue';\n\nexport interface AddOptions {\n yes?: boolean;\n target?: string;\n json?: boolean;\n ingest?: boolean;\n /** Gate/promote as a skill (default) or an MCP server — v0.5. */\n artifact?: ArtifactType;\n /** MCP config file to promote into (mcp artifacts only). Defaults to config.mcpTargets[0], then <cwd>/.mcp.json. */\n mcpTarget?: string;\n /** Overwrite an existing same-name MCP server entry when promoting (mcp artifacts only). */\n force?: boolean;\n /** Path to a generated rhize-plugins skill map; reports existing-capability overlap against it before promotion (skill artifacts only). */\n skillMap?: string;\n}\n\n/**\n * `skill-forge add <source>` — quarantine-install a skill and run it through\n * the safety gate (profile -> safety scan -> overlap analysis -> report ->\n * promote/hold/reject). `--yes` skips the interactive prompt and honors the\n * gate verdict; otherwise the decision is asked interactively.\n */\nexport async function add(source: string, options: AddOptions = {}): Promise<void> {\n await maybeOfferInit({ json: options.json, yes: options.yes });\n\n const config = loadConfig();\n const artifact: ArtifactType = options.artifact ?? 'skill';\n\n if (artifact === 'mcp') {\n await addMcp(source, config, options);\n return;\n }\n\n const targetRoot = options.target ?? config.defaultTarget ?? config.skillsRoots[0];\n if (!targetRoot) {\n throw new Error('no target skills root configured; pass --target <dir> or set skillsRoots in the config');\n }\n\n const resolved = resolveSource(source, artifact);\n const entry = await quarantine(resolved, config.quarantineDir);\n // Captured BEFORE the gate reads anything, and re-checked inside promote(): together they prove\n // the bytes that were scanned are the bytes that get installed, across both the scan and the\n // (potentially long) interactive decision. See src/install/fingerprint.ts.\n const gatedFingerprint = fingerprintDirs(entry.skillDirs).digest;\n\n if (entry.skillDirs.length === 0) {\n console.error(`[skill-forge] no SKILL.md found in installed source: ${source}`);\n reject(entry);\n process.exitCode = 1;\n return;\n }\n if (entry.skillDirs.length > 1) {\n console.error(\n `[skill-forge] ${entry.skillDirs.length} skills found in ${source}; gating the first — the whole ` +\n 'quarantine entry is promoted/rejected together:\\n' +\n entry.skillDirs.map((d) => ` - ${d}`).join('\\n')\n );\n }\n\n const { safety, gateResult, proEnabled } = await runGate(entry.skillDirs[0], targetRoot, {\n proFeatureLabel: PRO_FEATURE_LABEL,\n strictness: config.strictness,\n artifact,\n skillMapPath: options.skillMap,\n });\n const { profile, overlap } = gateResult;\n if (profile.kind !== 'skill') {\n // Unreachable in practice: runGate()'s asProfile() already throws before\n // returning a non-skill profile for artifact 'skill'. Narrows the type.\n throw new Error(`expected a skill candidate, found a ${profile.kind}`);\n }\n\n printGateOutput(gateResult, options.json);\n\n // --json implies non-interactive: honor the gate verdict the same way --yes does, never prompt.\n const decision =\n options.yes || options.json ? yesDecision(safety.verdict) : await promptDecision(safety.verdict, 'this skill');\n\n let installedPath: string | null = null;\n\n if (decision === 'promote') {\n const result = promote(entry, targetRoot, { expectedFingerprint: gatedFingerprint });\n logOutcome(`[skill-forge] promoted -> ${result.promoted.join(', ')}`, options.json);\n // Free-tier `add` makes no absorb/fork judgment — it either installs the\n // candidate as-is or it doesn't. DEFER is the matching verb from the\n // five-verb matrix (\"adopt as-is; just install/keep it\").\n // Provenance recording is Pro — see the notice printed above the report.\n if (proEnabled) {\n // Wrapped for the same reason commands/held.ts wraps its copy: a ledger-write failure AFTER\n // a successful promote must not surface as a bare throw that hides the promote that did\n // happen. Name the target instead, so the entry can be recorded by hand.\n try {\n const ledgerPath = appendProvenance(\n {\n name: profile.name ?? entry.id,\n source,\n version: profile.version,\n license: profile.license,\n verb: 'DEFER',\n target: targetRoot,\n took: `installed as-is (${result.promoted.length} skill dir${result.promoted.length === 1 ? '' : 's'})`,\n notes:\n `gate: safety=${safety.verdict}` +\n (overlap ? `, overlap nearest=${overlap.nearest} (${overlap.nearestScore})` : ''),\n },\n targetRoot\n );\n logOutcome(`[skill-forge] provenance recorded -> ${ledgerPath}`, options.json);\n } catch (err) {\n console.error(\n `[skill-forge] promoted into ${targetRoot}, but recording provenance failed: ${(err as Error).message}`\n );\n }\n }\n installedPath = result.promoted[0] ?? null;\n } else if (decision === 'reject') {\n reject(entry);\n logOutcome('[skill-forge] rejected — quarantine sandbox removed', options.json);\n if (safety.verdict === 'block') process.exitCode = 1;\n return; // rejected candidates are gone — nothing to queue for ingestion\n } else {\n hold(entry);\n logOutcome(`[skill-forge] held in quarantine -> ${entry.quarantinePath}`, options.json);\n }\n\n // Pending-ingestion queue and --ingest handoff are Pro; free `add` stops\n // at promote/hold, which is the complete free-tier flow. (Unlicensed/locked\n // case is covered by the notice printed above the report, not here.)\n if (proEnabled) {\n // The queue entry records the gate result at decision time so a later\n // ingest pass doesn't need to re-run it.\n appendQueueEntry({\n source,\n sourceType: resolved.type,\n installedPath,\n quarantinePath: entry.quarantinePath,\n gate: {\n license: profile.license ?? null,\n safetyVerdict: safety.verdict,\n safetyFindings: safety.findings.map(formatSafetyFinding),\n overlapTop: overlap ? overlap.ranking.slice(0, 3).map((r) => ({ skill: r.skill, score: r.score })) : [],\n suggestedVerb: overlap ? suggestVerbCode(overlap.nearestScore) : null,\n },\n status: 'pending',\n });\n\n if (options.ingest && installedPath) {\n // Runs the bundled agent-neutral ingest prompt (assets/ingest-prompt.md) via the\n // configured handoffCommand template, or a detected-agent fallback. See src/handoff.ts.\n runHandoff(config, installedPath);\n }\n }\n}\n\n/**\n * MCP branch of `add` (v0.5, `--artifact mcp`): quarantine-install an MCP\n * server (local dir / git URL / npm package — see `install/resolve.ts` and\n * `install/quarantine.ts`), gate it (`mcpScan` + MCP overlap via\n * `runGate`'s `artifact: 'mcp'` routing), then promote/hold/reject. Promote\n * writes the server entry into an MCP config file via `promoteMcp` instead of\n * moving anything into a skills root — there is no skill-style provenance\n * ledger for MCP artifacts (`SOURCES.md` is a skill-ingestion ledger; nothing\n * analogous exists for MCP config writes), but the pending-ingestion queue\n * and `--ingest` handoff both apply the same way, keyed on the written config\n * file path.\n */\nasync function addMcp(source: string, config: Config, options: AddOptions): Promise<void> {\n const resolved = resolveSource(source, 'mcp');\n const entry = await quarantine(resolved, config.quarantineDir);\n const candidatePath = resolveMcpCandidatePath(entry);\n\n const { safety, gateResult, proEnabled } = await runGate(candidatePath, '', {\n proFeatureLabel: MCP_PRO_FEATURE_LABEL,\n strictness: config.strictness,\n artifact: 'mcp',\n mcpTargets: config.mcpTargets ?? [],\n });\n const { profile, overlap } = gateResult;\n if (profile.kind !== 'mcp') {\n // Unreachable in practice: runGate()'s asProfile() already throws before\n // returning a non-mcp profile for artifact 'mcp'. Narrows the type.\n throw new Error(`expected an MCP candidate, found a ${profile.kind}`);\n }\n if (profile.serverCount > 1) {\n console.error(\n `[skill-forge] ${profile.serverCount} MCP servers found in ${source}; gating/promoting the first — ` +\n `the whole quarantine entry is promoted/rejected together:\\n ${profile.servers.join(', ')}`\n );\n }\n\n printGateOutput(gateResult, options.json);\n\n // --json implies non-interactive: honor the gate verdict the same way --yes does, never prompt.\n const decision =\n options.yes || options.json ? yesDecision(safety.verdict) : await promptDecision(safety.verdict, 'this skill');\n\n let installedPath: string | null = null;\n\n if (decision === 'promote') {\n const detail = resolveMcpCandidateDetail(profile);\n const serverProfile: McpServerProfile = {\n name: detail.name,\n // version may be absent — promoteMcp refuses the unpinnable npx fallback\n // rather than writing \"npx pkg@unknown\"; see deriveServerEntry in promoteMcp.ts.\n version: detail.version,\n // packageName (v0.7) — the candidate's own scanned package.json name, so\n // deriveServerEntry can auto-pin an unpinned npx spec in candidateEntry\n // only when it provably matches the package that was actually scanned.\n packageName: detail.packageName,\n envVars: detail.env ? Object.keys(detail.env) : undefined,\n candidateEntry: detail.command ? { command: detail.command, args: detail.args, env: detail.env } : undefined,\n };\n const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join(process.cwd(), '.mcp.json');\n\n const result = promoteMcp(entry, serverProfile, mcpTargetPath, { force: options.force });\n reject(entry); // promoteMcp doesn't touch the quarantine sandbox — clean it up now that the entry is written\n\n logOutcome(`[skill-forge] promoted -> ${result.target} (server \"${result.serverName}\")`, options.json);\n if (result.autoPinned) {\n logOutcome(`[skill-forge] pinned launch spec ${result.autoPinned.from} -> ${result.autoPinned.to}`, options.json);\n }\n if (result.backupPath) {\n logOutcome(`[skill-forge] backed up existing config -> ${result.backupPath}`, options.json);\n }\n if (result.envVarsToFill.length > 0) {\n logOutcome(`[skill-forge] fill in these env vars before use: ${result.envVarsToFill.join(', ')}`, options.json);\n }\n installedPath = result.target;\n } else if (decision === 'reject') {\n reject(entry);\n logOutcome('[skill-forge] rejected — quarantine sandbox removed', options.json);\n if (safety.verdict === 'block') process.exitCode = 1;\n return; // rejected candidates are gone — nothing to queue for ingestion\n } else {\n hold(entry);\n logOutcome(`[skill-forge] held in quarantine -> ${entry.quarantinePath}`, options.json);\n }\n\n // Pending-ingestion queue and --ingest handoff are Pro; free `add` stops\n // at promote/hold, which is the complete free-tier flow.\n if (proEnabled) {\n appendQueueEntry({\n source,\n sourceType: resolved.type,\n installedPath,\n quarantinePath: entry.quarantinePath,\n gate: {\n license: null,\n safetyVerdict: safety.verdict,\n safetyFindings: safety.findings.map(formatSafetyFinding),\n overlapTop: overlap ? overlap.ranking.slice(0, 3).map((r) => ({ skill: r.skill, score: r.score })) : [],\n suggestedVerb: overlap ? suggestVerbCode(overlap.nearestScore) : null,\n },\n status: 'pending',\n artifactType: 'mcp',\n capabilities: profile.capabilities,\n });\n\n if (options.ingest && installedPath) {\n // Same generic handoff as the skill path — {path} substitutes to the\n // written MCP config file rather than an installed skill dir.\n runHandoff(config, installedPath);\n }\n }\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport {\n normalizePreferences,\n normalizeProposals,\n type PreferenceEntry,\n type PreferenceProposal,\n} from './preferences.js';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nexport type Strictness = 'block-high' | 'block-medium' | 'warn-only';\n\n/** Single source of truth for the strictness fallback — used by `defaultConfig()` and the gate's default parameters. */\nexport const DEFAULT_STRICTNESS: Strictness = 'block-high';\n\n/**\n * Business context captured interactively by `skill-forge audit` (v0.8, never invented) —\n * grounds the business-foundation skill scaffold and the curation handoff prompt. See\n * src/audit.ts.\n */\nexport interface BusinessProfile {\n businessName?: string;\n industry?: string;\n audiences: string[];\n workflows: string[];\n constraints: string[];\n /** ISO-8601 timestamp of the last time this profile was captured/confirmed. */\n updatedAt: string;\n}\n\nexport interface Config {\n skillsRoots: string[];\n quarantineDir: string;\n strictness: Strictness;\n /** Pro license key (skf1.<payload>.<signature>). SKILL_FORGE_LICENSE env takes precedence — see src/license.ts. */\n licenseKey?: string;\n /** Default skills root to promote into when `add`'s `--target` is omitted. Set by `skill-forge init`. */\n defaultTarget?: string;\n /** Agent-to-skillsRoot mapping recorded by `skill-forge init` (see src/agents.ts for the agent matrix). */\n agents?: { id: string; skillsRoot: string }[];\n /** Argv-style template for handing a prompt to the chosen handoff agent, e.g. `['claude', '-p', '{prompt}']`. Set by `skill-forge init`. See src/handoff.ts. */\n handoffCommand?: string[];\n /** MCP config file targets recorded by `skill-forge init` (see Agent#mcpConfig in src/agents.ts). Consulted by MCP `promote` as a fallback when `--mcp-target` is omitted. */\n mcpTargets?: string[];\n /** Business context captured by `skill-forge audit`'s interactive Q&A. See BusinessProfile above. */\n businessProfile?: BusinessProfile;\n /** Path to the scaffolded business-foundation skill, once created by `skill-forge audit --foundation`. See src/audit.ts. */\n foundationSkillPath?: string;\n /**\n * Open-ended learned/stated facts about this user, keyed by a kebab-case name (v0.11). The\n * schema-free counterpart to `businessProfile`'s five fixed fields — see src/preferences.ts.\n * Accepted values only; agent suggestions live in `proposedPreferences` until reviewed.\n */\n preferences?: Record<string, PreferenceEntry>;\n /** Agent-proposed preferences awaiting `skill-forge config review`. NEVER read as effective values. */\n proposedPreferences?: PreferenceProposal[];\n}\n\n/**\n * Resolves the skill-forge home directory. Honors SKILL_FORGE_HOME so tests\n * (and users) can redirect config/quarantine storage away from ~/.skill-forge.\n */\nexport function getHomeDir(): string {\n return process.env.SKILL_FORGE_HOME ?? join(homedir(), '.skill-forge');\n}\n\nexport function getConfigPath(): string {\n return join(getHomeDir(), 'config.json');\n}\n\n/**\n * Repairs a possibly hand-edited `businessProfile` rather than letting a malformed one crash\n * later: `audiences`/`workflows`/`constraints` MUST be arrays of strings (consumed by\n * `bulletList()` in `src/audit.ts`'s foundation-skill renderer) — a missing or wrong-shaped\n * value is coerced to `[]` instead of propagating a raw TypeError out of `renderFoundationSkillMd`.\n * `businessName`/`industry`/`updatedAt` pass through unchanged.\n */\nfunction normalizeBusinessProfile(raw: BusinessProfile | undefined): BusinessProfile | undefined {\n if (!raw || typeof raw !== 'object') return undefined;\n const toStringArray = (v: unknown): string[] => (Array.isArray(v) ? v.filter((x): x is string => typeof x === 'string') : []);\n return {\n ...raw,\n audiences: toStringArray(raw.audiences),\n workflows: toStringArray(raw.workflows),\n constraints: toStringArray(raw.constraints),\n };\n}\n\n/**\n * Collapses a path list to its distinct entries, first occurrence wins.\n *\n * Repairs configs written before init deduped its own detection (v0.11): many agents legitimately\n * share one skills root (18 of the 73 entries in src/agents.ts use `.agents/skills`), and the\n * pre-fix wizard wrote one entry per AGENT — so `skillsRoots` could hold the same path 18 times,\n * making every root-iterating command (`watch`, `organize`, `refine rollback`, `ingest`) do the\n * same work, and report the same finding, once per duplicate. String-identical only: this is\n * cheap normalization, not a symlink/realpath equivalence check.\n */\nfunction uniquePaths(paths: string[] | undefined): string[] | undefined {\n return paths ? [...new Set(paths)] : paths;\n}\n\nexport function defaultConfig(): Config {\n const home = getHomeDir();\n return {\n skillsRoots: [join(process.cwd(), '.claude', 'skills')],\n quarantineDir: join(home, 'quarantine'),\n strictness: DEFAULT_STRICTNESS,\n };\n}\n\n/**\n * Loads config from disk, falling back to defaults for any missing keys.\n * Does not write anything to disk.\n */\nexport function loadConfig(): Config {\n const configPath = getConfigPath();\n const defaults = defaultConfig();\n\n if (!existsSync(configPath)) {\n return defaults;\n }\n\n const raw = readFileSync(configPath, 'utf-8');\n const parsed = JSON.parse(raw) as Partial<Config>;\n\n return {\n skillsRoots: uniquePaths(parsed.skillsRoots) ?? defaults.skillsRoots,\n quarantineDir: parsed.quarantineDir ?? defaults.quarantineDir,\n strictness: parsed.strictness ?? defaults.strictness,\n licenseKey: parsed.licenseKey ?? defaults.licenseKey,\n defaultTarget: parsed.defaultTarget ?? defaults.defaultTarget,\n agents: parsed.agents ?? defaults.agents,\n handoffCommand: parsed.handoffCommand ?? defaults.handoffCommand,\n mcpTargets: uniquePaths(parsed.mcpTargets) ?? defaults.mcpTargets,\n businessProfile: normalizeBusinessProfile(parsed.businessProfile) ?? defaults.businessProfile,\n foundationSkillPath: parsed.foundationSkillPath ?? defaults.foundationSkillPath,\n // Both are repaired on read (invalid/secret-shaped entries dropped) — config.json is a plain\n // file anything can edit, and these render straight into a terminal. See src/preferences.ts.\n preferences: normalizePreferences(parsed.preferences) ?? defaults.preferences,\n proposedPreferences: normalizeProposals(parsed.proposedPreferences) ?? defaults.proposedPreferences,\n };\n}\n\nexport function saveConfig(config: Config): void {\n const home = getHomeDir();\n if (!existsSync(home)) {\n mkdirSync(home, { recursive: true });\n }\n writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n}\n","/**\n * Friendly, expected-failure errors — bad input, unreachable sources, missing\n * config, etc. Thrown anywhere in the pipeline and caught once at the top\n * level (src/cli.ts), which prints just the message and exits 1 instead of a\n * raw stack trace. Anything that is NOT a UserError is treated as a bug: the\n * top-level handler prints a short message plus a hint to re-run with\n * DEBUG=skill-forge to see the full stack.\n */\nexport class UserError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'UserError';\n }\n}\n","import { readFileSync, readdirSync, statSync } from 'node:fs';\nimport { extname, join } from 'node:path';\nimport type { Strictness } from '../config.js';\n\n/**\n * Helpers shared between the gate scanners — `gate/safety.ts` (the base\n * deny-ruleset scanner), `gate/mcpSafety.ts` (MCP-specific findings layered on\n * top of it), and `gate/mcpCapabilities.ts` (static capability profiling): the\n * file-tree walker, safe text-file reader, and the severity->verdict mapping.\n * These modules import from here instead of maintaining their own copies —\n * previously these were duplicated per file and risked drifting out of sync.\n */\n\nexport type Severity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';\nexport type Verdict = 'pass' | 'warn' | 'block';\n\nexport interface SafetyFinding {\n severity: Severity;\n rule: string;\n file: string;\n detail: string;\n /**\n * Content-derived identity component for accepted-finding fingerprinting (v0.12, `src/findings.ts`)\n * — the offending line's text, or a rule-specific stable token. Deliberately additive/optional: a\n * rule that doesn't set it falls back to `detail` at the fingerprinting boundary, never to `''`\n * (an unpopulated context must still fail open when the message changes). See\n * `docs/accepted-findings-schema.md`'s per-rule context table for what each rule sets here and why.\n */\n context?: string;\n}\n\nexport interface SafetyResult {\n findings: SafetyFinding[];\n verdict: Verdict;\n}\n\nconst SKIP_DIRS = new Set(['.git', 'node_modules', 'dist', 'build', '__pycache__', '.venv', 'venv']);\nconst BINARY_EXT = new Set([\n '.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.pdf', '.zip', '.tar', '.gz',\n '.woff', '.woff2', '.ttf', '.eot', '.mp4', '.mp3', '.wasm', '.bin', '.exe',\n]);\nconst MAX_FILE_BYTES = 2 * 1024 * 1024; // 2MB — skills/servers ship small text/scripts\n\nexport function* walk(dir: string): Generator<string> {\n let entries;\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue;\n yield* walk(full);\n } else if (entry.isFile()) {\n yield full;\n }\n }\n}\n\nexport function readTextFile(file: string): string | undefined {\n if (BINARY_EXT.has(extname(file).toLowerCase())) return undefined;\n try {\n if (statSync(file).size > MAX_FILE_BYTES) return undefined;\n const content = readFileSync(file, 'utf-8');\n if (content.slice(0, 512).includes('\\0')) return undefined; // crude binary sniff\n return content;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Ported from skill_safety.py's SkillSpector severity->verdict mapping\n * (LOW allow, MEDIUM caution, HIGH/CRITICAL block).\n *\n * `config.strictness` (see src/config.ts) selects which severities block:\n * `block-high` (default) is the mapping above; `block-medium` also blocks on\n * MEDIUM; `warn-only` never blocks (findings still surface, capped at warn).\n */\nconst BLOCK_SEVERITIES_BY_STRICTNESS: Record<Strictness, ReadonlySet<Severity>> = {\n 'block-high': new Set(['HIGH', 'CRITICAL']),\n 'block-medium': new Set(['MEDIUM', 'HIGH', 'CRITICAL']),\n 'warn-only': new Set(),\n};\n\nexport function verdictFor(findings: SafetyFinding[], strictness: Strictness): Verdict {\n const blockSeverities = BLOCK_SEVERITIES_BY_STRICTNESS[strictness];\n if (findings.some((f) => blockSeverities.has(f.severity))) return 'block';\n if (findings.length > 0) return 'warn';\n return 'pass';\n}\n\n/** Formats a single finding as `SEVERITY [rule] file: detail` — shared by the pending-queue `safetyFindings` formatters in commands/add.ts and evolve.ts. */\nexport function formatSafetyFinding(f: SafetyFinding): string {\n return `${f.severity} [${f.rule}] ${f.file}: ${f.detail}`;\n}\n\n/**\n * Scoped packages pin as `@scope/name@version`; unscoped as `name@version`.\n * A spec only counts as pinned when the version after the final `@` is\n * digit-leading (semver-ish, e.g. `1.2.3`) — moving/dist tags like `latest`,\n * `next`, `beta`, `canary`, or `dev` (or any other non-numeric tag) resolve to\n * whatever the registry currently points at and must be treated as unpinned.\n * Shared by `gate/mcpSafety.ts`'s scan and `install/promoteMcp.ts`'s\n * documented-candidateEntry version-pin enforcement (v0.7).\n */\nexport function isPinnedPackageSpec(spec: string): boolean {\n const withoutScope = spec.startsWith('@') ? spec.slice(spec.indexOf('/') + 1) : spec;\n const atIdx = withoutScope.lastIndexOf('@');\n if (atIdx <= 0) return false;\n return /^\\d/.test(withoutScope.slice(atIdx + 1));\n}\n\n/** Strips a leading directory and a trailing `.cmd`/`.exe`, case-insensitively, so `/usr/bin/npx`, `npx.cmd`, and `NPX.CMD` all count. Shared by `gate/mcpSafety.ts` and `install/promoteMcp.ts` (v0.7). */\nexport function isNpxCommand(command: string): boolean {\n const base = command.split(/[\\\\/]/).pop() ?? command;\n return base.replace(/\\.(cmd|exe)$/i, '').toLowerCase() === 'npx';\n}\n\n/** `name@latest` -> `name` (drops a trailing `@tag`, preserving any `@scope/`) for suggestion text. Shared by `gate/mcpSafety.ts` and `install/promoteMcp.ts` (v0.7). */\nexport function packageSpecBase(spec: string): string {\n const scopeEnd = spec.startsWith('@') ? spec.indexOf('/') + 1 : 0;\n const atIdx = spec.lastIndexOf('@');\n return atIdx > scopeEnd ? spec.slice(0, atIdx) : spec;\n}\n\n/**\n * npx's own value-consuming CLI flags — verified against the installed npm\n * 12.0.1's `node_modules/npm/bin/npx-cli.js`. That file hardcodes an `opts`\n * set of flags that ALWAYS consume the next token (or an attached\n * `=value`) as their value rather than letting it become the launched\n * command: `package`/`p`, `cache`, `userconfig`, `call`/`c`, `shell`,\n * `node-arg`/`n`, and the deprecated-but-still-parsed `npm`. `registry` is\n * added here deliberately even though it is NOT in that hardcoded set:\n * npx-cli.js's parser has a generic fallback (any unrecognized flag that\n * isn't a known boolean switch consumes the next token as a value unless\n * that token itself looks like a flag) which makes `--registry <url>`\n * behave the same way in practice — verified empirically. This is a\n * DELIBERATE, DOCUMENTED SIMPLIFICATION, not a full reimplementation of\n * npx's parser: that fallback rule also consults npm's complete,\n * version-dependent set of boolean switches (`@npmcli/config`'s\n * `definitions`), which this stdlib-only tool does not bundle. Every other\n * `-`-prefixed token is treated as a boolean flag.\n */\nconst NPX_VALUE_FLAGS = new Set(['p', 'package', 'c', 'call', 'cache', 'userconfig', 'shell', 'n', 'node-arg', 'npm', 'registry']);\n\n/** Of NPX_VALUE_FLAGS, only `-p`/`--package`'s value(s) are themselves package specs — the rest (cache paths, config files, shell binaries, registry URLs, ...) are not. */\nconst NPX_PACKAGE_SPEC_FLAGS = new Set(['p', 'package']);\n\n/**\n * Every `-p`/`--package` value found (repeatable — npx installs ALL of\n * them, in addition to the launched command, before running it), and the\n * positional launch spec if one was found — kept separate rather than a\n * flat list because policy (`install/promoteMcp.ts`) treats them\n * differently: only the positional (the package actually being launched)\n * is ever eligible for auto-pinning; a `-p` dependency, even alone and\n * even if it happens to match the candidate's own package, is not.\n */\nexport interface NpxSpecCollection {\n packageFlagSpecs: string[];\n positionalSpec?: string;\n}\n\n/**\n * Collects every package spec that `npx -y <args>` would actually resolve\n * and install: every repeated `-p`/`--package` value, plus the positional\n * launch spec — the first token that's neither a recognized flag nor a\n * value a recognized flag consumed. Collection stops at that positional\n * token (mirroring npx-cli.js: once the positional is found, `--` is\n * spliced in front of it and everything after belongs to the *launched*\n * command's own argv, not npx's argument parsing). See NPX_VALUE_FLAGS\n * above for the documented scope/limits of this model. Shared by\n * `gate/mcpSafety.ts`'s scan and `install/promoteMcp.ts`'s\n * documented-candidateEntry version-pin enforcement (v0.7).\n */\nexport function collectNpxPackageSpecs(args: string[]): NpxSpecCollection {\n const packageFlagSpecs: string[] = [];\n let positionalSpec: string | undefined;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === '--') break;\n\n if (arg.startsWith('-')) {\n const eqIdx = arg.indexOf('=');\n const key = (eqIdx === -1 ? arg : arg.slice(0, eqIdx)).replace(/^-+/, '');\n const attached = eqIdx === -1 ? undefined : arg.slice(eqIdx + 1);\n\n if (NPX_VALUE_FLAGS.has(key)) {\n let value = attached;\n if (value === undefined) {\n // No attached =value — the next token is consumed as this flag's\n // value, whatever it looks like (mirrors npx-cli.js's unconditional\n // consumption for a known value-flag).\n value = args[i + 1];\n i++;\n }\n if (NPX_PACKAGE_SPEC_FLAGS.has(key) && value !== undefined) {\n packageFlagSpecs.push(value);\n }\n continue;\n }\n\n // Unrecognized flag — treated as boolean (see NPX_VALUE_FLAGS's doc\n // comment for this simplification's documented limits).\n continue;\n }\n\n // First token that's neither a flag nor a consumed value — the\n // positional launch spec. Collect it and stop.\n positionalSpec = arg;\n break;\n }\n\n return { packageFlagSpecs, positionalSpec };\n}\n\n/**\n * Heuristic \"this looks like a random secret\" test, shared by `gate/mcpSafety.ts`'s inline-credential\n * finding and `src/preferences.ts`'s hard refusal (v0.11). Lives here for the same reason the walker\n * and severity helpers do: credential recognition is shared scan policy, and a second private copy\n * drifts — before this was shared, a 40-char random hex value was flagged by the MCP scanner and\n * silently accepted into config.json by preferences.\n *\n * The two CONSUMERS keep their own policies (an advisory finding vs. a refusal); only the\n * recognizer is shared.\n */\n/** ISO-8601 date or date-time, the shape a `last_updated`/`generatedAt` field carries. */\nconst ISO_TIMESTAMP_RE = /^\\d{4}-\\d{2}-\\d{2}(?:[T ]\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d+)?)?(?:Z|[+-]\\d{2}:?\\d{2})?)?$/;\n\n/** A conservative path segment: no `+`, `=`, or `%`, so a base64 blob starting with `/` never qualifies. */\nconst PATH_SEGMENT_RE = /^[A-Za-z0-9._-]+$/;\n\nfunction looksLikeFsPath(v: string): boolean {\n if (!/^(?:\\/|~\\/|\\.\\.?\\/)/.test(v)) return false;\n const segments = v.replace(/^~/, '').split('/').filter(Boolean);\n // >= 2 segments: `/opt` alone is too weak a signal, and a secret is never `a/b/c`-shaped.\n return segments.length >= 2 && segments.every((seg) => PATH_SEGMENT_RE.test(seg));\n}\n\nfunction looksLikeBareUrl(v: string): boolean {\n let url: URL;\n try {\n url = new URL(v);\n } catch {\n return false;\n }\n if (!['http:', 'https:', 'ws:', 'wss:', 'file:'].includes(url.protocol)) return false;\n // `https://user:pass@host` embeds a credential, and a query string can carry a token\n // (`?access_token=...`). Neither is exempt — they fall through to the entropy test.\n if (url.username !== '' || url.password !== '') return false;\n if (url.search !== '') return false;\n return true;\n}\n\n/**\n * True when a value is structurally recognizable as something that is NOT a secret: an ISO\n * timestamp, a credential-free URL, or a filesystem path.\n *\n * Exists because `looksHighEntropy` alone has terrible precision on real config files. Measured\n * against a live `~/.codex/config.toml` (2026-08-09), the entropy test alone reported 15 HIGH\n * \"inline credential\" findings, of which ZERO were credentials — 8 were `last_updated` timestamps,\n * the rest were paths and plain URLs. A security scan whose HIGH findings are 94% noise is one\n * people learn to skim, which costs more than the rule earns.\n *\n * Deliberately narrow: anything with embedded userinfo or a query string is NOT exempt, and the\n * path form rejects `+`/`=`/`%`, so base64-ish secrets are never mistaken for paths.\n */\nexport function looksStructuredNonSecret(value: string): boolean {\n // Strip TOML/JSON array and quote punctuation the line-based scanners capture along with the\n // value, e.g. `notify = [\"/Users/...` yields `[\"/Users/...`.\n const v = value.trim().replace(/^[[\\s'\"]+/, '').replace(/[\\]\\s'\",]+$/, '');\n if (v === '') return false;\n return ISO_TIMESTAMP_RE.test(v) || looksLikeBareUrl(v) || looksLikeFsPath(v);\n}\n\nexport function looksHighEntropy(value: string): boolean {\n if (looksStructuredNonSecret(value)) return false;\n if (/\\s/.test(value)) return false;\n if (value.length < 20) return false;\n const hasLower = /[a-z]/.test(value);\n const hasUpper = /[A-Z]/.test(value);\n const hasDigit = /[0-9]/.test(value);\n const varietyCount = [hasLower, hasUpper, hasDigit].filter(Boolean).length;\n const uniqueChars = new Set(value).size;\n return varietyCount >= 2 && uniqueChars / value.length > 0.4;\n}\n","import { UserError } from './errors.js';\nimport { looksHighEntropy } from './gate/scanShared.js';\nimport type { Config } from './config.js';\n\n/**\n * Open-ended, growable user context — the half of the config that has no fixed schema.\n *\n * `businessProfile` (v0.8) is five typed fields, so anything learned that isn't one of those five\n * has nowhere to live and gets forgotten between sessions. Preferences are free-form key/value\n * facts (\"prefers-typescript\", \"deploys-on-vercel\", \"never-auto-promote-mcp\") that accumulate\n * without a schema change.\n *\n * ## Why there are two stores, not one\n *\n * `preferences` is what the tool believes. `proposedPreferences` is what something has SUGGESTED\n * it believe. Agents write only to the second one.\n *\n * An agent draining the ingest/curation queue reads untrusted skill content, so anything it\n * concludes about you is downstream of text an attacker may have written. If an agent could write\n * `preferences` directly, a malicious SKILL.md could talk it into persisting an instruction into\n * your config, where every later run would read it back as your stated preference — a durable\n * prompt-injection foothold with a laundering step in the middle. Routing agent writes through a\n * review queue means the worst case is a proposal you decline.\n *\n * Effective reads NEVER consult the proposal queue: `getPreference` returns accepted values only.\n */\n\nexport type PreferenceSource = 'user' | 'agent';\n\ndeclare const acceptedBrand: unique symbol;\n\n/**\n * A preference value a human has accepted — nominally distinct from a raw `string`.\n *\n * The accepted/proposed split above is a security boundary, but until this brand existed it was\n * enforced only by which accessor a caller happened to reach for. Both stores held a plain\n * `string`, so the natural-looking \"effective value, or the latest claim about it\":\n *\n * ```ts\n * config.preferences?.[k]?.value ?? config.proposedPreferences?.find(p => p.key === k)?.value\n * ```\n *\n * type-checked cleanly and silently promoted an unreviewed agent claim to an effective preference\n * — precisely the laundering this module exists to prevent. `AcceptedValue` is assignable TO\n * `string` (reading is unaffected), but a plain `string` is NOT assignable to it, so any consumer\n * that declares it needs an accepted value cannot be handed a proposal's.\n *\n * Minting is deliberately confined to `markAccepted` below: grep it to audit every place a value\n * becomes trusted.\n */\nexport type AcceptedValue = string & { readonly [acceptedBrand]: true };\n\n/**\n * The ONLY place a raw string becomes an `AcceptedValue`. Every caller is a point where a human\n * has either typed the value (`config set`) or accepted it (`config review`), or where a value\n * previously accepted is being re-read from disk after re-validation.\n */\nfunction markAccepted(value: string): AcceptedValue {\n return value as AcceptedValue;\n}\n\nexport interface PreferenceEntry {\n value: AcceptedValue;\n /** Who asserted it. `user` means a human typed `config set`; `agent` means a reviewed proposal. */\n source: PreferenceSource;\n updatedAt: string;\n /** Free-text provenance: which command, agent, or session produced it. */\n origin?: string;\n note?: string;\n}\n\nexport interface PreferenceProposal {\n key: string;\n /** Deliberately a bare `string`, never `AcceptedValue` — nothing here has been reviewed. */\n value: string;\n proposedAt: string;\n origin?: string;\n note?: string;\n}\n\n/** Lowercase kebab/dot segments, 1-64 chars — a stable, greppable, filename-safe key space. */\nexport const PREFERENCE_KEY_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;\n\nexport const MAX_PREFERENCE_VALUE_LENGTH = 500;\n\n/**\n * Cap on the proposal queue. An agent in a loop must not be able to grow config.json without\n * bound — at the cap, new proposals are refused (loudly) rather than evicting older ones, since\n * silently dropping the oldest would let a spammer flush a real proposal out of the queue.\n */\nexport const MAX_PENDING_PROPOSALS = 50;\n\n/**\n * Key names and value shapes that look like credentials.\n *\n * config.json is plain text at default permissions, so a secret landing here is a leak, and the\n * most likely way one arrives is an agent \"helpfully\" persisting a token it saw. Both the key and\n * the value are checked: `api-key` is refused whatever its value, and an `sk-`/`ghp_`/`AKIA`\n * shaped value is refused whatever its key. This is a blunt filter on an open field, not a\n * secret-detection engine — it is deliberately biased toward refusing.\n */\nconst SECRET_KEY_PATTERN = /(^|[.\\-_])(password|passwd|secret|token|api[.\\-_]?key|apikey|credential|private[.\\-_]?key|auth)([.\\-_]|$)/i;\nconst SECRET_VALUE_PATTERNS: { pattern: RegExp; label: string }[] = [\n { pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----/, label: 'a PEM private key' },\n // sk-ant- first: the broader sk- pattern below also matches it, and the more specific label wins.\n { pattern: /\\bsk-ant-[A-Za-z0-9_-]{16,}/, label: 'an Anthropic-style secret key' },\n { pattern: /\\bsk-[A-Za-z0-9_-]{16,}/, label: 'an OpenAI-style secret key' },\n { pattern: /\\bgh[pousr]_[A-Za-z0-9]{20,}/, label: 'a GitHub token' },\n { pattern: /\\bxox[baprs]-[A-Za-z0-9-]{10,}/, label: 'a Slack token' },\n { pattern: /\\bAKIA[0-9A-Z]{16}\\b/, label: 'an AWS access key id' },\n { pattern: /\\bsntry[su]_[A-Za-z0-9]{20,}/, label: 'a Sentry token' },\n { pattern: /\\beyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\./, label: 'a JWT' },\n];\n\nexport const MAX_PREFERENCE_NOTE_LENGTH = 200;\n\n/**\n * Validates a free-text provenance/annotation field: length-capped, no C0/C1 control characters.\n *\n * Originally private and scoped to `origin`/`note` — supplied by whatever ran `config propose`\n * (i.e. by an agent that has been reading untrusted skill content) and rendered back at the\n * `config review` prompt, exactly the moment a human is deciding whether to trust the proposal.\n * Unfiltered escape sequences there could overwrite or disguise the key/value being reviewed,\n * which would defeat the whole propose/accept boundary.\n *\n * **Exported (v0.12)** and widened from the `'origin' | 'note'` literal union to a bare `field:\n * string` so `src/findings.ts`'s `validateAcceptReason` can reuse it for `finding accept`'s\n * mandatory `--reason` — same rendering-into-a-review-moment rationale, different field name. The\n * non-negotiable this satisfies: there is exactly one control-char/length validator for this shape\n * of field, never a second copy that could silently drift from this one.\n */\nexport function validateProvenanceText(field: string, text: string | undefined): void {\n if (text === undefined) return;\n if (text.length > MAX_PREFERENCE_NOTE_LENGTH) {\n throw new UserError(`--${field} is ${text.length} chars — the limit is ${MAX_PREFERENCE_NOTE_LENGTH}`);\n }\n if (/[\\u0000-\\u001F\\u007F-\\u009F]/.test(text)) {\n throw new UserError(`--${field} contains control characters — refusing to store terminal escape sequences`);\n }\n}\n\n/** Throws a UserError when the key/value pair must not be persisted. Applied to BOTH set and propose. */\nexport function validatePreference(key: string, value: string): void {\n if (!PREFERENCE_KEY_PATTERN.test(key)) {\n throw new UserError(\n `invalid preference key \"${key}\" — use lowercase letters, digits, dot, dash or underscore (max 64 chars), e.g. \"prefers-typescript\"`\n );\n }\n if (SECRET_KEY_PATTERN.test(key)) {\n throw new UserError(\n `refusing to store \"${key}\": the key names a credential, and config.json is plain text. Keep secrets in your OS keychain or an env var.`\n );\n }\n if (value.length === 0) {\n throw new UserError(`preference \"${key}\" has an empty value — use \\`skill-forge config unset ${key}\\` to remove it`);\n }\n if (value.length > MAX_PREFERENCE_VALUE_LENGTH) {\n throw new UserError(\n `preference \"${key}\" is ${value.length} chars — the limit is ${MAX_PREFERENCE_VALUE_LENGTH}. Preferences are short facts, not documents.`\n );\n }\n // C0/C1 controls would smuggle ANSI escape sequences into every later render of this value.\n if (/[\\u0000-\\u001F\\u007F-\\u009F]/.test(value)) {\n throw new UserError(`preference \"${key}\" contains control characters — refusing to store terminal escape sequences`);\n }\n for (const { pattern, label } of SECRET_VALUE_PATTERNS) {\n if (pattern.test(value)) {\n throw new UserError(\n `refusing to store \"${key}\": the value looks like ${label}, and config.json is plain text. Keep secrets in your OS keychain or an env var.`\n );\n }\n }\n // Catches the secret shapes the prefix table above cannot: a long random token with no\n // recognizable vendor prefix. Shared with the MCP scanner's inline-credential rule\n // (gate/scanShared.ts) so the two cannot disagree about the same string — before they were\n // shared, this exact value was flagged there and accepted here.\n if (looksHighEntropy(value)) {\n throw new UserError(\n `refusing to store \"${key}\": the value looks like a random secret, and config.json is plain text. ` +\n 'Keep secrets in your OS keychain or an env var; if this really is a preference, use a shorter, human-readable value.'\n );\n }\n}\n\n/** Effective entry for a key — accepted preferences only; proposals are never read as truth. */\nexport function getPreference(config: Config, key: string): PreferenceEntry | undefined {\n return config.preferences?.[key];\n}\n\n/**\n * The sanctioned way to read an effective value. Returns `AcceptedValue`, so a consumer that\n * declares this as its input type cannot be handed a proposal's raw string by mistake — the\n * brand is only useful if the read path hands it out, otherwise every caller lands on `string`\n * and the distinction evaporates at the first assignment.\n */\nexport function getAcceptedValue(config: Config, key: string): AcceptedValue | undefined {\n return config.preferences?.[key]?.value;\n}\n\n/**\n * Records a preference the USER stated. Accepted immediately: a human typing `config set` IS the\n * review step. Returns a new Config; the caller saves it.\n */\nexport function setPreference(\n config: Config,\n key: string,\n value: string,\n options: { origin?: string; note?: string; source?: PreferenceSource; now?: Date } = {}\n): Config {\n validatePreference(key, value);\n validateProvenanceText('origin', options.origin);\n validateProvenanceText('note', options.note);\n const entry: PreferenceEntry = {\n value: markAccepted(value),\n source: options.source ?? 'user',\n updatedAt: (options.now ?? new Date()).toISOString(),\n origin: options.origin,\n note: options.note,\n };\n return { ...config, preferences: { ...config.preferences, [key]: entry } };\n}\n\nexport function unsetPreference(config: Config, key: string): Config {\n if (!config.preferences?.[key]) {\n throw new UserError(`no preference named \"${key}\" — \\`skill-forge config list\\` shows what is set`);\n }\n const next = { ...config.preferences };\n delete next[key];\n return { ...config, preferences: next };\n}\n\n/**\n * Queues an agent-proposed preference for review. Never becomes effective here — see the module\n * doc for why agent writes are separated from accepted values.\n *\n * A second proposal for the same key REPLACES the queued one rather than adding a duplicate, so a\n * chatty agent can refine its suggestion without flooding the review list.\n */\nexport function proposePreference(\n config: Config,\n key: string,\n value: string,\n options: { origin?: string; note?: string; now?: Date } = {}\n): Config {\n validatePreference(key, value);\n validateProvenanceText('origin', options.origin);\n validateProvenanceText('note', options.note);\n const existing = config.proposedPreferences ?? [];\n const withoutKey = existing.filter((p) => p.key !== key);\n if (withoutKey.length >= MAX_PENDING_PROPOSALS) {\n throw new UserError(\n `the preference proposal queue is full (${MAX_PENDING_PROPOSALS}) — run \\`skill-forge config review\\` before proposing more`\n );\n }\n const proposal: PreferenceProposal = {\n key,\n value,\n proposedAt: (options.now ?? new Date()).toISOString(),\n origin: options.origin,\n note: options.note,\n };\n return { ...config, proposedPreferences: [...withoutKey, proposal] };\n}\n\n/** Moves a queued proposal into the accepted set, tagged `source: 'agent'` so its origin stays visible. */\nexport function acceptProposal(config: Config, key: string, now: Date = new Date()): Config {\n const proposal = (config.proposedPreferences ?? []).find((p) => p.key === key);\n if (!proposal) {\n throw new UserError(`no pending proposal for \"${key}\" — \\`skill-forge config review\\` lists what is pending`);\n }\n const withPreference = setPreference(config, proposal.key, proposal.value, {\n source: 'agent',\n origin: proposal.origin,\n note: proposal.note,\n now,\n });\n return { ...withPreference, proposedPreferences: (config.proposedPreferences ?? []).filter((p) => p.key !== key) };\n}\n\nexport function rejectProposal(config: Config, key: string): Config {\n const pending = config.proposedPreferences ?? [];\n if (!pending.some((p) => p.key === key)) {\n throw new UserError(`no pending proposal for \"${key}\" — \\`skill-forge config review\\` lists what is pending`);\n }\n return { ...config, proposedPreferences: pending.filter((p) => p.key !== key) };\n}\n\n/**\n * Repairs a hand-edited or foreign `preferences` map on load: anything that fails validation, or\n * isn't shaped like an entry, is DROPPED rather than propagated. config.json is a plain file a\n * user (or a stray process) can edit, and everything downstream renders these into a terminal.\n */\nexport function normalizePreferences(raw: unknown): Record<string, PreferenceEntry> | undefined {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;\n const out: Record<string, PreferenceEntry> = {};\n for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {\n if (!value || typeof value !== 'object') continue;\n const entry = value as Partial<PreferenceEntry>;\n if (typeof entry.value !== 'string') continue;\n try {\n validatePreference(key, entry.value);\n } catch {\n continue;\n }\n const origin = typeof entry.origin === 'string' ? entry.origin : undefined;\n const note = typeof entry.note === 'string' ? entry.note : undefined;\n try {\n validateProvenanceText('origin', origin);\n validateProvenanceText('note', note);\n } catch {\n continue;\n }\n out[key] = {\n // Re-minted only after `validatePreference` passed above: a hand-edited config.json cannot\n // smuggle in a value that would fail today's rules.\n value: markAccepted(entry.value),\n source: entry.source === 'agent' ? 'agent' : 'user',\n updatedAt: typeof entry.updatedAt === 'string' ? entry.updatedAt : new Date(0).toISOString(),\n origin,\n note,\n };\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n/** Same repair pass for the proposal queue, plus the queue cap. */\nexport function normalizeProposals(raw: unknown): PreferenceProposal[] | undefined {\n if (!Array.isArray(raw)) return undefined;\n const out: PreferenceProposal[] = [];\n for (const item of raw) {\n if (!item || typeof item !== 'object') continue;\n const proposal = item as Partial<PreferenceProposal>;\n if (typeof proposal.key !== 'string' || typeof proposal.value !== 'string') continue;\n try {\n validatePreference(proposal.key, proposal.value);\n } catch {\n continue;\n }\n if (out.some((p) => p.key === proposal.key)) continue;\n const origin = typeof proposal.origin === 'string' ? proposal.origin : undefined;\n const note = typeof proposal.note === 'string' ? proposal.note : undefined;\n try {\n validateProvenanceText('origin', origin);\n validateProvenanceText('note', note);\n } catch {\n continue;\n }\n out.push({\n key: proposal.key,\n value: proposal.value,\n proposedAt: typeof proposal.proposedAt === 'string' ? proposal.proposedAt : new Date(0).toISOString(),\n origin,\n note,\n });\n if (out.length >= MAX_PENDING_PROPOSALS) break;\n }\n return out.length > 0 ? out : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Compile-time guards\n//\n// `tsconfig.json` includes only `src`, so a `@ts-expect-error` in a test file would not be\n// checked by `tsc --noEmit`. These assertions live here so removing the brand — or widening\n// `PreferenceEntry.value` back to `string` — fails the typecheck instead of silently restoring\n// the laundering path described on `AcceptedValue`.\n// ---------------------------------------------------------------------------\n\ntype Assert<T extends true> = T;\n\n/**\n * The entry's value must actually BE branded. This is the guard that bites if someone widens\n * `PreferenceEntry.value` back to `string` — testing only \"a bare string isn't an AcceptedValue\"\n * would still pass in that case, since the brand type would survive unused.\n */\ntype _EntryValueIsBranded = Assert<PreferenceEntry['value'] extends AcceptedValue ? true : false>;\n\n/** A proposal's value must NOT satisfy whatever type the entry's value has. */\ntype _ProposalValueIsNotEntryValue = Assert<PreferenceProposal['value'] extends PreferenceEntry['value'] ? false : true>;\n\n/** An accepted value must still be usable anywhere a plain string is wanted. */\ntype _AcceptedIsAString = Assert<PreferenceEntry['value'] extends string ? true : false>;\n\n/** A whole proposal must not be assignable where an entry is required. */\ntype _ProposalIsNotAnEntry = Assert<PreferenceProposal extends PreferenceEntry ? false : true>;\n","import { existsSync } from 'node:fs';\nimport { UserError } from '../errors.js';\nimport { expandHome } from '../util.js';\n\nexport type SourceType = 'skills.sh' | 'git' | 'local' | 'npm';\nexport type ArtifactType = 'skill' | 'mcp';\n\nexport interface ResolvedSource {\n type: SourceType;\n value: string;\n}\n\nconst GIT_URL_PATTERN = /^(git@|https?:\\/\\/|ssh:\\/\\/)/;\n// Unambiguous filesystem-path prefixes: no skills.sh `owner/name` slug starts\n// with any of these, so a missing path here is a friendly \"not found\" error\n// rather than a slow (network-dependent) skills.sh install attempt.\nconst LOCAL_PATH_PATTERN = /^(\\.{1,2}\\/|\\/|~\\/|~$)/;\n// A scoped npm package spec: \"@scope/name\", optionally with a version/tag\n// suffix on the name half (e.g. \"@scope/name@1.2.3\"). Never collides with a\n// skills.sh `owner/name` slug, which has no leading \"@\".\nconst NPM_SCOPED_PATTERN = /^@[^/]+\\/[^/]+$/;\n\n/**\n * Classifies a skill source string so the installer knows which quarantine\n * strategy to use:\n * - an http(s)/git@/ssh:// URL, or one ending in `.git`, is a git remote\n * - an existing filesystem path is a local skill\n * - a nonexistent path that's unambiguously path-shaped (./, ../, /, ~/) is\n * still local, so it fails fast with a friendly error instead of falling\n * through to skills.sh\n * - with `artifact: 'mcp'`, a scoped \"@scope/name\" package or a bare\n * (no-slash) package name is npm — an \"owner/name\" two-segment slug (no\n * leading \"@\") still falls through to skills.sh below, same as\n * `artifact: 'skill'`/absent: no ambiguity guessing between the two\n * owner/name-shaped source kinds\n * - everything else is treated as a skills.sh `owner/name` slug\n *\n * Skill classification (`artifact` 'skill' or absent) is unchanged from v0.4.\n */\nexport function resolve(source: string, artifact: ArtifactType = 'skill'): ResolvedSource {\n const value = source.trim();\n\n // Reject anything that looks like a CLI flag rather than a source: quarantine.ts\n // passes `value` straight through as a git-clone/npx/npm-pack argv positional, so\n // a source starting with \"-\" (e.g. \"--upload-pack=...\") could otherwise be\n // smuggled in as an injected flag. Fail fast with a friendly error instead of\n // reaching exec().\n if (value.startsWith('-')) {\n throw new UserError(`skill source cannot start with \"-\" (looks like a flag, not a source): ${value}`);\n }\n\n if (GIT_URL_PATTERN.test(value) || value.endsWith('.git')) {\n return { type: 'git', value };\n }\n\n // Expand ~ before existence checks so a real \"~/some-skill\" resolves instead\n // of erroring \"not found\" on the literal tilde string.\n const localCandidate = expandHome(value);\n if (existsSync(localCandidate) || LOCAL_PATH_PATTERN.test(value)) {\n return { type: 'local', value: localCandidate };\n }\n\n if (artifact === 'mcp' && (NPM_SCOPED_PATTERN.test(value) || !value.includes('/'))) {\n return { type: 'npm', value };\n }\n\n return { type: 'skills.sh', value };\n}\n","import { accessSync, constants, mkdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { basename, delimiter, dirname, join } from 'node:path';\nimport { randomUUID } from 'node:crypto';\nimport { UserError } from './errors.js';\n\n/** Expands a leading `~` / `~/` to the user's home directory. */\nexport function expandHome(p: string): string {\n if (p === '~') return homedir();\n if (p.startsWith('~/')) return join(homedir(), p.slice(2));\n return p;\n}\n\n/** Strips wrapping single/double quotes from a frontmatter value. */\nexport function stripQuotes(s: string): string {\n return s.replace(/^['\"]+/, '').replace(/['\"]+$/, '');\n}\n\n/** True if `p` exists and is a directory (false on any stat error). */\nexport function isDir(p: string): boolean {\n try {\n return statSync(p).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Resolves a bare command name against PATH and returns the first executable file that matches,\n * or undefined. Used by `skill-forge init` (v0.11) to tell an agent that is actually installed\n * from one that merely shares a skills directory with an installed agent.\n *\n * Look-up only: this NEVER executes the candidate — no `--version` probe, no spawn of anything\n * found on PATH — matching the static-only discipline the gate modules hold to. A name containing\n * a path separator is rejected outright rather than resolved relative to the cwd, so nothing here\n * can be steered into `./claude`.\n *\n * `pathEnv`/`platform`/`pathExt` are injectable so tests can probe a temp PATH instead of the\n * machine's real one.\n */\nexport function resolveBinaryOnPath(\n binary: string,\n pathEnv: string | undefined = process.env.PATH,\n platform: NodeJS.Platform = process.platform,\n pathExt: string | undefined = process.env.PATHEXT,\n): string | undefined {\n if (binary === '' || binary.includes('/') || binary.includes('\\\\')) return undefined;\n const dirs = (pathEnv ?? '').split(platform === 'win32' ? ';' : delimiter).filter(Boolean);\n const exts = platform === 'win32' ? (pathExt ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean) : [''];\n for (const dir of dirs) {\n for (const ext of exts) {\n const candidate = join(dir, binary + ext);\n try {\n // isFile guards against a directory named like the binary — a dir is \"executable\" to access().\n if (!statSync(candidate).isFile()) continue;\n accessSync(candidate, constants.X_OK);\n return candidate;\n } catch {\n // not there, or not executable by us — keep looking\n }\n }\n }\n return undefined;\n}\n\n/**\n * Writes JSON atomically and owner-only: serialize to a temp file in the SAME directory (so the\n * rename is same-filesystem and therefore atomic), then rename it over the target. A reader can\n * never observe a half-written file, and a crash mid-write leaves the previous file intact plus an\n * orphaned temp rather than a corrupted one.\n *\n * Single home for a primitive that had drifted into three copies (queue.ts, refine/store.ts,\n * auditState.ts) — the copies had already diverged on file mode, which is exactly the kind of\n * silent inconsistency a shared helper prevents.\n */\nexport function writeJsonAtomic(path: string, data: unknown): void {\n const dir = dirname(path);\n mkdirSync(dir, { recursive: true });\n const tempPath = join(dir, `.${basename(path)}.tmp-${randomUUID()}`);\n try {\n writeFileSync(tempPath, JSON.stringify(data, null, 2) + '\\n', { encoding: 'utf-8', mode: 0o600 });\n renameSync(tempPath, path);\n } catch (err) {\n rmSync(tempPath, { force: true });\n throw err;\n }\n}\n\n/**\n * Creates a file exclusively (`wx`) and owner-only (`0600`), refusing to overwrite an existing\n * path. This pairing is a documented privacy posture for everything this CLI writes reports to,\n * not an incidental flag choice — enforcing it by copy-paste is how it decays, so audit, organize,\n * and routine all route through here. `hint` names the flag a caller can use to pick another path.\n */\nexport function writeNewFile(path: string, contents: string, hint: string): void {\n mkdirSync(dirname(path), { recursive: true });\n try {\n writeFileSync(path, contents, { flag: 'wx', mode: 0o600 });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\n throw new UserError(`file already exists: ${path} — pass a different ${hint} path`);\n }\n throw err;\n }\n}\n","import { randomUUID } from 'node:crypto';\nimport { spawn } from 'node:child_process';\nimport { cpSync, existsSync, mkdirSync, readdirSync } from 'node:fs';\nimport type { Dirent } from 'node:fs';\nimport { basename, join, resolve as resolvePath } from 'node:path';\nimport type { ResolvedSource } from './resolve.js';\nimport { UserError } from '../errors.js';\n\nexport interface ExecResult {\n stdout: string;\n stderr: string;\n code: number;\n}\n\nexport type ExecFn = (\n command: string,\n args: string[],\n options: { cwd: string }\n) => Promise<ExecResult>;\n\nexport interface QuarantineEntry {\n id: string;\n quarantinePath: string;\n skillDirs: string[];\n /** Present only for npm-sourced (MCP) entries: the extracted `package/` dir inside the sandbox. */\n packageDir?: string;\n source: ResolvedSource;\n}\n\nexport interface QuarantineDeps {\n exec?: ExecFn;\n}\n\nconst SKILL_MANIFEST = 'SKILL.md';\nconst SKIP_DIRS = new Set(['.git', 'node_modules']);\n\n/**\n * Installs `source` into `<quarantineDir>/<id>/` — never into a target\n * skills root. skills.sh sources run `npx -y skills@latest add <slug>`\n * with cwd pinned to the sandbox; git sources run a shallow clone into a\n * named subdir of the sandbox; local sources are recursively copied in.\n * Returns the sandbox id/path plus every directory inside it that looks\n * like an installed skill (contains SKILL.md).\n */\nexport async function quarantine(\n source: ResolvedSource,\n quarantineDir: string,\n deps: QuarantineDeps = {}\n): Promise<QuarantineEntry> {\n const exec = deps.exec ?? defaultExec;\n const id = randomUUID();\n const quarantinePath = join(quarantineDir, id);\n mkdirSync(quarantinePath, { recursive: true });\n let packageDir: string | undefined;\n\n switch (source.type) {\n case 'skills.sh': {\n const result = await exec('npx', ['-y', 'skills@latest', 'add', source.value], {\n cwd: quarantinePath,\n });\n if (result.code !== 0) {\n throw new Error(\n `skills.sh install failed for \"${source.value}\" (exit ${result.code}): ${result.stderr || result.stdout}`\n );\n }\n break;\n }\n case 'git': {\n const clonePath = join(quarantinePath, deriveGitDirName(source.value));\n // `--` separates flags from positionals so a source value that looks like a\n // git flag (e.g. \"--upload-pack=...\") is always treated as the repo\n // positional, never parsed as an option — belt-and-suspenders alongside\n // resolve.ts's leading-dash rejection.\n const result = await exec('git', ['clone', '--depth', '1', '--', source.value, clonePath], {\n cwd: quarantinePath,\n });\n if (result.code !== 0) {\n throw new UserError(\n `git clone failed for \"${source.value}\" (exit ${result.code}): ${result.stderr || result.stdout}`\n );\n }\n break;\n }\n case 'local': {\n if (!existsSync(source.value)) {\n throw new UserError(`local path not found: ${source.value}`);\n }\n const dirName = basename(resolvePath(source.value));\n cpSync(source.value, join(quarantinePath, dirName), { recursive: true });\n break;\n }\n case 'npm': {\n // Fetch only — never `npm install`, never run scripts (`--ignore-scripts`\n // belt-and-suspenders alongside `npm pack` never running lifecycle\n // scripts on its own). Before extracting, list the tarball's members and\n // refuse to proceed if any member path could escape the sandbox\n // (path-traversal guard) — only once every member is confirmed safe do\n // we extract with system `tar`, so nothing from the package ever runs\n // during quarantine and nothing can be written outside the sandbox.\n const packResult = await exec(\n 'npm',\n ['pack', source.value, '--ignore-scripts', '--pack-destination', quarantinePath],\n { cwd: quarantinePath }\n );\n if (packResult.code !== 0) {\n throw new UserError(\n `npm pack failed for \"${source.value}\" (exit ${packResult.code}): ${packResult.stderr || packResult.stdout}`\n );\n }\n\n const tarballPath = join(quarantinePath, findTarball(quarantinePath, source.value));\n\n const listResult = await exec('tar', ['-tzf', tarballPath], { cwd: quarantinePath });\n if (listResult.code !== 0) {\n throw new UserError(\n `could not list tarball contents for \"${source.value}\" (exit ${listResult.code}): ${listResult.stderr || listResult.stdout}`\n );\n }\n assertSafeTarMembers(listResult.stdout, source.value);\n\n const extractResult = await exec('tar', ['-xzf', tarballPath, '-C', quarantinePath], {\n cwd: quarantinePath,\n });\n if (extractResult.code !== 0) {\n throw new UserError(\n `tarball extraction failed for \"${source.value}\" (exit ${extractResult.code}): ${extractResult.stderr || extractResult.stdout}`\n );\n }\n\n // `npm pack` tarballs always extract to a top-level `package/` dir.\n packageDir = join(quarantinePath, 'package');\n if (!existsSync(packageDir)) {\n throw new UserError(\n `npm pack for \"${source.value}\" did not produce the expected package/ dir after extraction`\n );\n }\n break;\n }\n default: {\n throw new UserError(`unknown source type: ${(source as ResolvedSource).type}`);\n }\n }\n\n return { id, quarantinePath, skillDirs: findSkillDirs(quarantinePath), packageDir, source };\n}\n\n/** IDs (sandbox dir names) of entries currently held in quarantine; [] when the dir doesn't exist. */\nexport function listQuarantineIds(quarantineDir: string): string[] {\n if (!existsSync(quarantineDir)) return [];\n return readdirSync(quarantineDir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n}\n\n/** Recursively finds every directory under `root` that directly contains SKILL.md. */\nexport function findSkillDirs(root: string): string[] {\n const found: string[] = [];\n\n function walk(dir: string): void {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n if (entries.some((entry) => entry.isFile() && entry.name === SKILL_MANIFEST)) {\n found.push(dir);\n }\n\n for (const entry of entries) {\n if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {\n walk(join(dir, entry.name));\n }\n }\n }\n\n walk(root);\n return found;\n}\n\n/**\n * Path-traversal guard: throws a UserError if any member path listed by\n * `tar -tzf` is absolute or contains a `..` path segment, BEFORE extraction\n * ever runs. (This checks member *names* only; link-type member safety still\n * rests on the system tar's own symlink safeguards.)\n */\nfunction assertSafeTarMembers(tarListOutput: string, sourceValue: string): void {\n for (const raw of tarListOutput.split('\\n')) {\n const member = raw.trim();\n if (!member) continue;\n if (member.startsWith('/') || member.split('/').includes('..')) {\n throw new UserError(\n `refusing to extract tarball for \"${sourceValue}\": unsafe member path \"${member}\" ` +\n '(absolute path or \"..\" segment — possible path traversal)'\n );\n }\n }\n}\n\n/** Locates the .tgz produced by `npm pack --pack-destination <dir>` (exactly one is expected, since each quarantine sandbox is freshly created before use). */\nfunction findTarball(dir: string, sourceValue: string): string {\n const tarball = readdirSync(dir).find((name) => name.endsWith('.tgz'));\n if (!tarball) {\n throw new UserError(`npm pack for \"${sourceValue}\" did not produce a .tgz file in ${dir}`);\n }\n return tarball;\n}\n\n/** Derives a filesystem-friendly dir name from a git URL (scp-like or http(s)). */\nfunction deriveGitDirName(url: string): string {\n const stripped = url.replace(/\\.git$/, '');\n const segments = stripped.split(/[/:]/).filter(Boolean);\n return segments[segments.length - 1] || 'repo';\n}\n\n/** Real argv-array subprocess runner (no shell) — the default ExecFn, and shared by evolve.ts's skillopt-sleep invocations. */\nexport function defaultExec(command: string, args: string[], options: { cwd: string }): Promise<ExecResult> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(command, args, { cwd: options.cwd });\n let stdout = '';\n let stderr = '';\n child.stdout?.on('data', (chunk: Buffer) => (stdout += chunk.toString()));\n child.stderr?.on('data', (chunk: Buffer) => (stderr += chunk.toString()));\n child.on('error', reject);\n child.on('close', (code) => resolvePromise({ stdout, stderr, code: code ?? 1 }));\n });\n}\n","import { cpSync, existsSync, mkdirSync, renameSync, rmSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport { UserError } from '../errors.js';\nimport { fingerprintDirs, isRealDirectory } from './fingerprint.js';\nimport type { QuarantineEntry } from './quarantine.js';\n\nexport interface PromoteOptions {\n force?: boolean;\n /**\n * Digest captured (via `fingerprintDirs`) when the candidate was gated, re-verified immediately\n * before anything moves. A mismatch means the vetted bytes are not the bytes about to be\n * installed, and promotion is refused. See src/install/fingerprint.ts for why this matters most\n * to `promote <id>`, where the gap between gate and promote can be days.\n */\n expectedFingerprint?: string;\n}\n\nexport interface PromoteResult {\n promoted: string[];\n}\n\n/**\n * Moves every skill dir found during quarantine into `targetSkillsRoot`.\n * Refuses to overwrite an existing dir of the same name unless `force` is\n * set — checked up front for every skill dir before anything is moved, so\n * a naming clash never leaves a partial promotion behind. Cleans up the\n * quarantine sandbox once every skill dir has been moved out.\n *\n * Pre-flight also re-validates the candidate against the state it was gated in (v0.11): each skill\n * dir must still be a REAL directory rather than a symlink, no symlink inside it may resolve out\n * of the sandbox, and — when the caller captured one — the content fingerprint must still match.\n * All three exist because the gate and the move are separated in time, and `renameSync` moves a\n * symlink as a link: a link planted in quarantine after the scan would otherwise land in the\n * trusted skills root still pointing at content its author can keep changing.\n */\nexport function promote(\n entry: QuarantineEntry,\n targetSkillsRoot: string,\n options: PromoteOptions = {}\n): PromoteResult {\n const force = options.force ?? false;\n\n for (const skillDir of entry.skillDirs) {\n if (!isRealDirectory(skillDir)) {\n throw new UserError(\n `refusing to promote ${skillDir}: it is no longer a real directory (a symlink or a deleted path) — ` +\n 're-run the gate on this candidate'\n );\n }\n const dest = join(targetSkillsRoot, basename(skillDir));\n if (existsSync(dest) && !force) {\n throw new Error(`Refusing to overwrite existing skill dir: ${dest} (pass force to override)`);\n }\n }\n\n const fingerprint = fingerprintDirs(entry.skillDirs);\n if (fingerprint.escaping.length > 0) {\n throw new UserError(\n `refusing to promote ${entry.id}: ${fingerprint.escaping.length} symlink(s) inside the candidate resolve outside ` +\n `the quarantine sandbox, and would land in your skills root still pointing there:\\n ` +\n fingerprint.escaping.join('\\n ')\n );\n }\n if (options.expectedFingerprint !== undefined && options.expectedFingerprint !== fingerprint.digest) {\n throw new UserError(\n `refusing to promote ${entry.id}: the candidate's contents changed after it was gated — ` +\n 'what was scanned is not what would be installed. Re-run `skill-forge scan` on the source.'\n );\n }\n\n mkdirSync(targetSkillsRoot, { recursive: true });\n\n const promoted: string[] = [];\n for (const skillDir of entry.skillDirs) {\n const dest = join(targetSkillsRoot, basename(skillDir));\n if (existsSync(dest)) {\n rmSync(dest, { recursive: true, force: true });\n }\n moveDir(skillDir, dest);\n promoted.push(dest);\n }\n\n rmSync(entry.quarantinePath, { recursive: true, force: true });\n\n return { promoted };\n}\n\n/** Deletes the quarantine sandbox without promoting anything into a skills root. */\nexport function reject(entry: QuarantineEntry): void {\n rmSync(entry.quarantinePath, { recursive: true, force: true });\n}\n\n/** No-op: leaves the quarantine sandbox exactly as-is for later review. */\nexport function hold(_entry: QuarantineEntry): void {\n // intentionally does nothing — quarantine dir is left in place\n}\n\nfunction moveDir(src: string, dest: string): void {\n try {\n renameSync(src, dest);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EXDEV') {\n cpSync(src, dest, { recursive: true });\n rmSync(src, { recursive: true, force: true });\n } else {\n throw err;\n }\n }\n}\n","import { createHash } from 'node:crypto';\nimport { lstatSync, readFileSync, readdirSync, readlinkSync, realpathSync } from 'node:fs';\nimport { isAbsolute, join, relative, sep } from 'node:path';\n\n/**\n * Content fingerprinting for quarantined candidates — the check that makes \"what was scanned is\n * what gets installed\" true rather than merely likely.\n *\n * The gate reads a candidate, then some time passes, then `install/promote.ts` moves it into a\n * trusted skills root. Everything in between is a TOCTOU window: whoever can write to the\n * quarantine directory can swap the vetted content for something else, and the move installs the\n * swap under the vetted content's clean bill of health. `skill-forge promote <id>` (v0.11) widened\n * that window from one process lifetime to however long a hold sits — days, per its own docs — so\n * the window is now large enough to be worth closing rather than just noting.\n *\n * Two distinct protections live here:\n *\n * 1. **The digest** detects any content change between capture and promote. It is not a security\n * boundary against a racing attacker (nothing short of fd-based syscalls Node doesn't expose\n * would be), but it turns a silent substitution into a refusal.\n * 2. **Symlink escape detection** is the sharper one. A symlink is the only way a swap survives\n * promotion as a *live* reference the attacker keeps controlling afterward: `renameSync` moves\n * a symlink as a link, so a link planted in quarantine lands in the trusted root still pointing\n * at attacker-controlled content. Links that stay inside the candidate are fine and are simply\n * hashed by target.\n *\n * Read-only and static — nothing here executes, resolves, or follows anything outside the tree it\n * is asked about.\n */\n\nexport interface FingerprintResult {\n /** sha256 over a canonical listing of the tree: entry type, relative path, and content/target hash. */\n digest: string;\n /** `relPath -> target` for every symlink found, whether or not it escapes. Recorded so a swap changes the digest. */\n symlinks: string[];\n /** Relative paths of symlinks whose target resolves OUTSIDE `root` — promotion refuses these. */\n escaping: string[];\n}\n\n/** Directories never worth hashing: huge, and not part of what a skill declares. */\nconst SKIP_DIRS = new Set(['.git', 'node_modules']);\n\nfunction isInside(child: string, root: string): boolean {\n const rel = relative(root, child);\n // `isAbsolute(rel)` catches the different-root case (Windows drives); `..`/`../` catch escapes.\n return rel === '' || (!rel.startsWith('..' + sep) && rel !== '..' && !isAbsolute(rel));\n}\n\n/**\n * Hashes a directory tree deterministically. Entries are sorted, so the digest depends only on\n * content and structure — never on readdir order, which is filesystem-dependent.\n */\nexport function fingerprintTree(root: string): FingerprintResult {\n const hash = createHash('sha256');\n const symlinks: string[] = [];\n const escaping: string[] = [];\n const realRoot = realpathSync(root);\n\n function walk(dir: string): void {\n const entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));\n\n for (const entry of entries) {\n const full = join(dir, entry.name);\n const rel = relative(root, full);\n\n if (entry.isSymbolicLink()) {\n const target = readlinkSync(full);\n symlinks.push(`${rel} -> ${target}`);\n hash.update(`L\\t${rel}\\t${target}\\n`);\n // Resolve the link against its own directory, then test containment. `realpathSync` on a\n // dangling link throws — a link we cannot resolve is treated as escaping, since we cannot\n // prove it stays inside.\n try {\n if (!isInside(realpathSync(full), realRoot)) escaping.push(rel);\n } catch {\n escaping.push(rel);\n }\n continue;\n }\n\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue;\n hash.update(`D\\t${rel}\\n`);\n walk(full);\n continue;\n }\n\n if (entry.isFile()) {\n const contentHash = createHash('sha256').update(readFileSync(full)).digest('hex');\n hash.update(`F\\t${rel}\\t${contentHash}\\n`);\n continue;\n }\n\n // Sockets, FIFOs, devices: record their presence so a swap is still detected, but never read.\n hash.update(`O\\t${rel}\\n`);\n }\n }\n\n walk(root);\n return { digest: hash.digest('hex'), symlinks, escaping };\n}\n\n/**\n * Fingerprints every skill directory a quarantine entry will promote, in a stable order.\n * `escaping` is unioned across the dirs, prefixed with the dir each came from.\n */\nexport function fingerprintDirs(dirs: string[]): FingerprintResult {\n const hash = createHash('sha256');\n const symlinks: string[] = [];\n const escaping: string[] = [];\n\n for (const dir of [...dirs].sort()) {\n let result: FingerprintResult;\n try {\n result = fingerprintTree(dir);\n } catch (err) {\n // A dir that vanished (or became unreadable) between capture and verify is itself a change:\n // fold the failure into the digest so the comparison fails loudly instead of throwing here.\n hash.update(`MISSING\\t${dir}\\t${(err as Error).message}\\n`);\n continue;\n }\n hash.update(`DIR\\t${dir}\\t${result.digest}\\n`);\n symlinks.push(...result.symlinks.map((s) => `${dir}: ${s}`));\n escaping.push(...result.escaping.map((s) => `${dir}: ${s}`));\n }\n\n return { digest: hash.digest('hex'), symlinks, escaping };\n}\n\n/** True if `path` exists and is a real directory — NOT a symlink to one. */\nexport function isRealDirectory(path: string): boolean {\n try {\n return lstatSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport type { QuarantineEntry } from './quarantine.js';\nimport { UserError } from '../errors.js';\nimport { collectNpxPackageSpecs, isNpxCommand, isPinnedPackageSpec, packageSpecBase } from '../gate/scanShared.js';\n\n/**\n * Minimal shape promoteMcp needs to derive a server config entry. Deliberately\n * decoupled from the skill/plugin/mcp-config profiling in gate/profile.ts —\n * this describes an MCP *server* candidate (name/version + whatever launch\n * info the profile/gate steps found), not a directory that merely ships an\n * .mcp.json referencing other servers.\n */\nexport interface McpServerProfile {\n /** Server name — used as the `mcpServers` config key (e.g. \"@scope/name\" or \"name\"). */\n name: string;\n /**\n * Pinned version to launch via `npx <name>@<version>` — used on the\n * npx-fallback path (no `candidateEntry`; see `deriveServerEntry` for the\n * refusal behavior when both are absent) and, when it matches\n * `packageName`, to auto-pin an unpinned `npx` spec found in a documented\n * `candidateEntry` (v0.7).\n */\n version?: string;\n /**\n * The package name from the candidate's own scanned, self-declared\n * `package.json` (v0.7, from `install/mcpCandidate.ts`'s sidecar read) —\n * for an npm-sourced candidate this IS the registry artifact being\n * profiled, but for a git/local source it's just whatever the candidate's\n * own `package.json` claims, unverified against any registry. Only when a\n * `candidateEntry`'s unpinned `npx` launch spec's package matches this\n * name is `version` above trusted to auto-pin it — a spec for some other\n * package is never pinned to a version scanned for a different package.\n */\n packageName?: string;\n /** Env var NAMES the server declares/needs. Values are never read or copied. */\n envVars?: string[];\n /**\n * The candidate's own documented server entry (e.g. from a README example or a\n * shipped .mcp.json), used for command/args when present instead of the\n * npx-derived default. Any `env` values on it are always stripped before use.\n * An unpinned `npx` launch spec on this entry is auto-pinned when it matches\n * the candidate's own scanned (self-declared) package (see\n * `packageName`/`version` above), otherwise promotion is refused — see\n * `deriveServerEntry` (v0.7).\n */\n candidateEntry?: { command: string; args?: string[]; env?: Record<string, unknown> };\n}\n\nexport interface PromoteMcpOptions {\n force?: boolean;\n}\n\nexport interface PromoteMcpResult {\n /** Absolute path to the target MCP config file that was written. */\n target: string;\n /** Server name (the `mcpServers` key) that was written. */\n serverName: string;\n /** Env var names the user must fill in before the server will work — values were never copied. */\n envVarsToFill: string[];\n /** Backup path created before modifying an existing target file; undefined when the file didn't exist. */\n backupPath?: string;\n /** Set when an unpinned `npx` spec in a documented `candidateEntry` was auto-pinned (v0.7) — the exact spec text before/after. */\n autoPinned?: { from: string; to: string };\n}\n\ninterface McpConfigFile {\n mcpServers?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * Writes `profile`'s derived server entry into `targetConfigPath`'s `mcpServers`\n * map — the only place an MCP candidate is written outside its quarantine\n * sandbox, mirroring install/promote.ts's target-untouched-until-promote\n * invariant for skills. Creates the file (and any missing parent dirs) if\n * absent. Every refusal path (unpinnable version, same-name clash without\n * `force`) is checked before anything is backed up, so a refusal never leaves\n * a partial write behind. When an existing file *is* about to be\n * modified, it's backed up first to `<file>.bak-<timestamp>`. Env values are\n * NEVER copied from the candidate — they're always emitted as empty strings,\n * and the var names the user must fill in are returned.\n */\nexport function promoteMcp(\n _entry: QuarantineEntry,\n profile: McpServerProfile,\n targetConfigPath: string,\n options: PromoteMcpOptions = {}\n): PromoteMcpResult {\n const force = options.force ?? false;\n const serverName = profile.name;\n\n // Can throw (version refusal) — derived before any read/backup so a refusal\n // never leaves a stray .bak file behind.\n const { command, args, envVarsToFill, autoPinned } = deriveServerEntry(profile);\n\n let config: McpConfigFile = {};\n let rawExisting: string | undefined;\n\n if (existsSync(targetConfigPath)) {\n rawExisting = readFileSync(targetConfigPath, 'utf-8');\n try {\n config = JSON.parse(rawExisting) as McpConfigFile;\n } catch (err) {\n throw new UserError(`could not parse existing MCP config ${targetConfigPath}: ${(err as Error).message}`);\n }\n }\n\n const servers = { ...(config.mcpServers ?? {}) };\n if (Object.prototype.hasOwnProperty.call(servers, serverName) && !force) {\n throw new UserError(\n `Refusing to overwrite existing MCP server entry \"${serverName}\" in ${targetConfigPath} (pass force to override)`\n );\n }\n\n let backupPath: string | undefined;\n if (rawExisting !== undefined) {\n backupPath = `${targetConfigPath}.bak-${Date.now()}`;\n writeFileSync(backupPath, rawExisting);\n }\n\n const env: Record<string, string> = {};\n for (const name of envVarsToFill) env[name] = '';\n\n servers[serverName] = { command, args, env };\n config.mcpServers = servers;\n\n mkdirSync(dirname(targetConfigPath), { recursive: true });\n writeFileSync(targetConfigPath, `${JSON.stringify(config, null, 2)}\\n`);\n\n return { target: targetConfigPath, serverName, envVarsToFill, backupPath, autoPinned };\n}\n\n/**\n * Derives {command, args} from the candidate's own documented entry when\n * present, else `npx <name>@<version>` pinned to the profiled version. Env\n * values are always stripped from whichever source is used — only var names\n * are returned. When there's no documented entry AND no resolved version,\n * throws a UserError instead of ever writing an unpinnable `npx pkg@unknown`\n * launch command.\n *\n * A documented `candidateEntry` is used verbatim EXCEPT when its own launch\n * command is `npx` and it carries an unpinned package spec (v0.7 —\n * previously written through unchanged, only ever flagged as a MEDIUM\n * safety warning). `gate/scanShared.ts`'s `collectNpxPackageSpecs()` parses\n * the args the way npx itself does — including every repeatable `-p`/\n * `--package` value, not just the first non-flag token (a naive \"first\n * non-flag arg\" heuristic misses that `-p`/`--package` consumes its value\n * even when that value doesn't start with `-`, which real npx/npm-exec\n * still installs — see that function's own doc comment):\n * - **no specs at all, or every spec found is already pinned** → verbatim.\n * - **exactly one spec total, it's the positional launch spec (not a `-p`\n * value), and it's unpinned**: auto-pinned to `<packageName>@<version>`\n * when the spec's package matches the candidate's own scanned\n * (self-declared) `packageName` and a `version` was scanned for it\n * (`autoPinned` records the change); otherwise refused.\n * - **anything else with an unpinned spec** (an unpinned `-p` value, or\n * more than one spec at all) — refused, naming every unpinned spec.\n * Under-checking a more complex shape must fail closed: there is no\n * auto-pin for a `-p` dependency (it isn't \"the launched package,\" even\n * if it happens to match) or for a multi-spec shape.\n * For an npm-sourced candidate, `packageName`/`version` come from the\n * registry artifact itself; for a git/local source they're only what the\n * candidate's own `package.json` self-declares. Auto-pinning there reduces\n * moving-tag drift (the spec can't silently resolve to something different\n * later) — it does not authenticate registry ownership of that name.\n * There is no override flag for either refusal. Both still run before any\n * read/backup (see promoteMcp() above), so a refusal leaves nothing behind.\n */\nfunction deriveServerEntry(profile: McpServerProfile): {\n command: string;\n args: string[];\n envVarsToFill: string[];\n autoPinned?: { from: string; to: string };\n} {\n const envNames = new Set(profile.envVars ?? []);\n if (profile.candidateEntry?.env) {\n for (const key of Object.keys(profile.candidateEntry.env)) envNames.add(key);\n }\n\n if (profile.candidateEntry) {\n const { command, args = [] } = profile.candidateEntry;\n\n if (isNpxCommand(command)) {\n const { packageFlagSpecs, positionalSpec } = collectNpxPackageSpecs(args);\n const allSpecs = positionalSpec ? [...packageFlagSpecs, positionalSpec] : packageFlagSpecs;\n const unpinned = allSpecs.filter((spec) => !isPinnedPackageSpec(spec));\n\n if (unpinned.length > 0) {\n const isSoleUnpinnedPositional =\n packageFlagSpecs.length === 0 && allSpecs.length === 1 && positionalSpec === unpinned[0];\n\n if (isSoleUnpinnedPositional) {\n const spec = unpinned[0];\n const specPackage = packageSpecBase(spec);\n if (profile.packageName && specPackage === profile.packageName && profile.version) {\n const to = `${profile.packageName}@${profile.version}`;\n const specIdx = args.indexOf(spec);\n const pinnedArgs = [...args];\n pinnedArgs[specIdx] = to;\n return {\n command,\n args: pinnedArgs,\n envVarsToFill: Array.from(envNames),\n autoPinned: { from: spec, to },\n };\n }\n\n const reason =\n profile.packageName && specPackage !== profile.packageName\n ? `the launch spec names package \"${specPackage}\", but the candidate's own package.json is \"${profile.packageName}\" — ` +\n 'pinning it to a version scanned for a different package would be unsafe'\n : \"no version was found in the candidate's own package.json to pin it to\";\n throw new UserError(\n `cannot promote MCP server \"${profile.name}\": its documented launch command \"npx ${spec}\" is unpinned ` +\n `and can't be safely auto-pinned (${reason}). Pin the spec to an exact version in the candidate's ` +\n `config (e.g. \\`${specPackage}@<version>\\`) and try again — there is no override for this.`\n );\n }\n\n throw new UserError(\n `cannot promote MCP server \"${profile.name}\": its documented launch command \"npx\" has unpinned package ` +\n `spec(s) — ${unpinned.map((s) => `\"${s}\"`).join(', ')} — and this shape (a -p/--package dependency, ` +\n \"or more than one spec) can't be safely auto-pinned. Pin every spec to an exact version in the \" +\n \"candidate's config and try again — there is no override for this.\"\n );\n }\n }\n\n return {\n command,\n args,\n envVarsToFill: Array.from(envNames),\n };\n }\n\n if (!profile.version) {\n throw new UserError(\n `cannot promote MCP server \"${profile.name}\": no version was found in its package.json, and there is ` +\n 'no documented launch command to fall back to — refusing to write an unpinnable \"npx ' +\n `${profile.name}@unknown\" command`\n );\n }\n\n return {\n command: 'npx',\n args: ['-y', `${profile.name}@${profile.version}`],\n envVarsToFill: Array.from(envNames),\n };\n}\n","import { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport type { McpProfile } from '../gate/profile.js';\n\n/**\n * Launch details for the ONE MCP server being gated/promoted, derived from a\n * `McpProfile` (`gate/profile.ts`) plus — when the profile came from an\n * explicit `.mcp.json`/`mcp.json` — a second read of that file's chosen\n * server entry. `McpProfile` intentionally only tracks server NAMES (see\n * `profileMcp` in `gate/profile.ts`), not launch command/args/env, since its\n * source config may list more than one server; this is the one place that\n * resolves a profile down to a single server's launch details. Shared by\n * `commands/gatePipeline.ts`'s MCP overlap step and `install/promoteMcp.ts`'s\n * promote step so both derive from exactly the same read.\n */\nexport interface McpCandidateDetail {\n name: string;\n version?: string;\n command?: string;\n args?: string[];\n env?: Record<string, unknown>;\n /**\n * The package `name` from the candidate's own sibling `package.json`\n * (v0.7), when one exists next to an explicit `.mcp.json`/`mcp.json`. Used\n * by `install/promoteMcp.ts` to auto-pin an unpinned `npx` launch spec in\n * a documented `candidateEntry` — only when the spec's package matches\n * this name is the candidate's own scanned `version` trusted for pinning.\n * On the package.json-profile branch, `name` above already comes from\n * this same file, so `packageName` is left unset there (redundant).\n */\n packageName?: string;\n}\n\ninterface ConfigEntry {\n command?: string;\n args?: string[];\n env?: Record<string, unknown>;\n}\n\ninterface PackageInfo {\n name?: string;\n version?: string;\n}\n\n/** Tolerant read of a package.json's `name`/`version` — never throws; returns {} if missing/malformed. */\nfunction readPackageInfo(pkgPath: string): PackageInfo {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as Record<string, unknown>;\n return {\n name: typeof pkg.name === 'string' ? pkg.name : undefined,\n version: typeof pkg.version === 'string' ? pkg.version : undefined,\n };\n } catch {\n return {};\n }\n}\n\nfunction readConfigEntry(configPath: string, name: string): ConfigEntry | undefined {\n try {\n const cfg = JSON.parse(readFileSync(configPath, 'utf-8')) as Record<string, unknown>;\n const servers = (cfg.mcpServers ?? cfg.servers ?? {}) as Record<string, unknown>;\n const raw = (servers[name] ?? {}) as Record<string, unknown>;\n return {\n command: typeof raw.command === 'string' ? raw.command : undefined,\n args: Array.isArray(raw.args) ? raw.args.filter((a): a is string => typeof a === 'string') : undefined,\n env:\n raw.env && typeof raw.env === 'object' && !Array.isArray(raw.env)\n ? (raw.env as Record<string, unknown>)\n : undefined,\n };\n } catch {\n return undefined;\n }\n}\n\n/**\n * Resolves launch details for `profile.servers[0]` — when a config lists more\n * than one server, gates/promotes the first, matching `add`'s/`scan`'s\n * existing \"N skills found — using the first\" convention for multi-skill\n * sources. A package.json-shaped profile (npm-sourced server) has no\n * command/args/env of its own — `promoteMcp` derives the launch command from\n * `name`/`version` (`npx <name>@<version>`) when both are absent here.\n *\n * On the explicit `.mcp.json`/`mcp.json` branch (v0.7), also tolerantly reads\n * a sibling `package.json` next to the config file for its `name`/`version`\n * — populating `version`/`packageName` there too — so `promoteMcp.ts` can\n * auto-pin an unpinned `npx` spec in a documented `candidateEntry` when it\n * provably matches the candidate's own scanned package.\n */\nexport function resolveMcpCandidateDetail(profile: McpProfile): McpCandidateDetail {\n const name = profile.servers[0];\n\n if (profile.config.endsWith('package.json')) {\n return { name, version: readPackageInfo(profile.config).version };\n }\n\n const entry = readConfigEntry(profile.config, name);\n const sidecar = readPackageInfo(join(dirname(profile.config), 'package.json'));\n return {\n name,\n command: entry?.command,\n args: entry?.args,\n env: entry?.env,\n version: sidecar.version,\n packageName: sidecar.name,\n };\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/**\n * Provenance ledger. Appends a structured entry to `<skillsRoot>/SOURCES.md`, the audit\n * ledger of every external-skill ingestion decision. Field names and formatting stay fixed\n * so an existing SOURCES.md ledger stays consistent across every entry ever appended to it.\n */\n\nexport const FORGE_VERBS = ['DEFER', 'ABSORB', 'FORK', 'REJECT', 'WATCH'] as const;\nexport type ForgeVerb = (typeof FORGE_VERBS)[number];\n\nexport interface ProvenanceEntry {\n name: string;\n source: string;\n verb: ForgeVerb;\n version?: string;\n license?: string;\n target?: string;\n took?: string;\n verified?: string;\n driftCheck?: string;\n notes?: string;\n}\n\nconst LEDGER_HEADER = '# Skill Forge — Provenance Ledger\\n\\nOne entry per external-skill ingestion decision.\\n';\n\nfunction todayLocalIsoDate(): string {\n const d = new Date();\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, '0');\n const day = String(d.getDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Appends a provenance entry to `<skillsRoot>/SOURCES.md`, creating the\n * ledger with its header on first write. Throws if `verb` isn't a valid\n * forge verb or `skillsRoot` isn't an existing directory — mirrors the\n * Python script's fail-loud `fail()` calls. Returns the ledger path.\n */\nexport function appendProvenance(entry: ProvenanceEntry, skillsRoot: string): string {\n if (!FORGE_VERBS.includes(entry.verb)) {\n throw new Error(`verb must be one of ${FORGE_VERBS.join(', ')}`);\n }\n if (!existsSync(skillsRoot) || !statSync(skillsRoot).isDirectory()) {\n throw new Error(`skills-root not a directory: ${skillsRoot}`);\n }\n\n const today = todayLocalIsoDate();\n const driftCheck = entry.driftCheck ?? `# define how to detect upstream change for ${entry.name}`;\n\n const block =\n `\\n## ${entry.name} — ${today}\\n` +\n `- **Source:** ${entry.source}\\n` +\n `- **Upstream ref:** ${entry.version ?? 'n/a'}\\n` +\n `- **License:** ${entry.license ?? 'UNKNOWN'}\\n` +\n `- **Verb:** ${entry.verb}\\n` +\n `- **Target:** ${entry.target ?? 'n/a'}\\n` +\n `- **Took:** ${entry.took ?? 'nothing'}\\n` +\n `- **Verified:** ${entry.verified ?? 'n/a'}\\n` +\n `- **Drift check:** \\`${driftCheck}\\`\\n` +\n `- **Notes:** ${entry.notes ?? ''}\\n`;\n\n const ledgerPath = join(skillsRoot, 'SOURCES.md');\n if (!existsSync(ledgerPath)) {\n writeFileSync(ledgerPath, LEDGER_HEADER, 'utf-8');\n }\n appendFileSync(ledgerPath, block, 'utf-8');\n\n return ledgerPath;\n}\n\n// ---------------------------------------------------------------------------\n// Ledger PARSING — inverse of appendProvenance's format, tolerant of hand-edits.\n// Backs `skill-forge watch` (src/commands/watch.ts). Never throws on a single\n// malformed block: it's skipped and a warning is recorded, so one bad\n// hand-edit can't take down the whole drift check.\n// ---------------------------------------------------------------------------\n\n/** One parsed `## <name> — <date>` block from a SOURCES.md ledger. Field values are raw ledger text — untrusted, and must be sanitized (see audit.ts's `sanitizeForMarkdown`) before landing in human-readable output. */\nexport interface ProvenanceLedgerEntry {\n name: string;\n date: string;\n source: string;\n upstreamRef: string;\n license: string;\n verb: ForgeVerb;\n target: string;\n took: string;\n verified: string;\n /** The stored drift-check command string, exactly as recorded — DATA ONLY. Never execute this; it is attacker-influenceable ledger content (see src/commands/watch.ts). */\n driftCheck: string;\n notes: string;\n}\n\nexport interface ParsedProvenanceLedger {\n entries: ProvenanceLedgerEntry[];\n /** One line per skipped/malformed block, describing why it was skipped. Parsing never throws — a bad block is reported here instead. */\n warnings: string[];\n}\n\n/** Extracts a single `**Label:** value` field's value (up to end of line) from a ledger block, or undefined if absent. */\nfunction extractField(block: string, label: string): string | undefined {\n const escaped = label.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const match = block.match(new RegExp(`\\\\*\\\\*${escaped}:\\\\*\\\\*\\\\s*(.*)`));\n return match ? match[1].trim() : undefined;\n}\n\n/**\n * Parses a SOURCES.md ledger's full text into structured entries — the inverse of\n * `appendProvenance`'s block format. Tolerant of hand-edits: a block missing a name/date\n * header or a missing/invalid `Verb` is skipped with a warning rather than thrown. The\n * leading document header (everything before the first `## `) is ignored.\n */\nexport function parseProvenanceLedger(text: string): ParsedProvenanceLedger {\n const entries: ProvenanceLedgerEntry[] = [];\n const warnings: string[] = [];\n\n // Mirrors record_provenance.py's `re.split(r\"\\n## \", text)` — blocks[0] is\n // the document header (or leading garbage before the first entry) and is\n // never treated as an entry.\n const blocks = text.split(/\\n## /);\n\n for (const block of blocks.slice(1)) {\n const headerLine = block.split('\\n', 1)[0] ?? '';\n const [namePart, ...dateParts] = headerLine.split(' — ');\n const name = namePart?.trim();\n if (!name) {\n warnings.push(`skipped malformed ledger block: missing \"## <name> — <date>\" header (saw \"${headerLine.slice(0, 80)}\")`);\n continue;\n }\n const date = dateParts.join(' — ').trim();\n\n const verbRaw = extractField(block, 'Verb');\n if (!verbRaw || !(FORGE_VERBS as readonly string[]).includes(verbRaw)) {\n warnings.push(`skipped malformed ledger block for \"${name}\": missing or invalid Verb (saw \"${verbRaw ?? ''}\")`);\n continue;\n }\n\n const driftCheckMatch = block.match(/\\*\\*Drift check:\\*\\*\\s*`([^`]*)`/);\n\n entries.push({\n name,\n date,\n source: extractField(block, 'Source') ?? '',\n upstreamRef: extractField(block, 'Upstream ref') ?? '',\n license: extractField(block, 'License') ?? '',\n verb: verbRaw as ForgeVerb,\n target: extractField(block, 'Target') ?? '',\n took: extractField(block, 'Took') ?? '',\n verified: extractField(block, 'Verified') ?? '',\n driftCheck: driftCheckMatch ? driftCheckMatch[1] : '',\n notes: extractField(block, 'Notes') ?? '',\n });\n }\n\n return { entries, warnings };\n}\n\nexport interface ReadLedgerResult extends ParsedProvenanceLedger {\n ledgerPath: string;\n exists: boolean;\n}\n\n/**\n * Reads and parses `<skillsRoot>/SOURCES.md`. A missing ledger is not an error here (unlike\n * `appendProvenance`, which is a write path) — `exists: false` with empty entries/warnings lets\n * callers (e.g. `skill-forge watch`) report \"no ledger yet\" for a root cleanly.\n */\nexport function readProvenanceLedger(skillsRoot: string): ReadLedgerResult {\n const ledgerPath = join(skillsRoot, 'SOURCES.md');\n if (!existsSync(ledgerPath)) {\n return { ledgerPath, exists: false, entries: [], warnings: [] };\n }\n const text = readFileSync(ledgerPath, 'utf-8');\n const parsed = parseProvenanceLedger(text);\n return { ledgerPath, exists: true, ...parsed };\n}\n","import { readdirSync } from 'node:fs';\nimport { formatAuditAdvisory, readAuditState } from '../auditState.js';\nimport { join } from 'node:path';\nimport { DEFAULT_STRICTNESS, type Strictness } from '../config.js';\nimport { asProfile, type McpProfile } from '../gate/profile.js';\nimport { scanSafety, type SafetyResult as GateSafetyResult } from '../gate/safety.js';\nimport { scanOverlap, type OverlapResult } from '../gate/overlap.js';\nimport { mcpScan } from '../gate/mcpSafety.js';\nimport { scanMcpOverlap } from '../gate/mcpOverlap.js';\nimport { verdictFor } from '../gate/scanShared.js';\nimport { loadSkillMap } from '../gate/skillMap.js';\nimport { scanMapOverlap, mapOverlapFinding } from '../gate/mapOverlap.js';\nimport { resolveMcpCandidateDetail } from '../install/mcpCandidate.js';\nimport type { ArtifactType } from '../install/resolve.js';\nimport type { QuarantineEntry } from '../install/quarantine.js';\nimport { proFeatureStatus } from '../license.js';\nimport { SAFETY_VERDICT_MAP, mapSafetyFindings, type GateResult } from '../report/render.js';\n\nexport { printGateOutput } from '../report/render.js';\n\n/**\n * Shared profile -> safety -> overlap pipeline used by both `add` and `scan`.\n * Bridges gate/safety.ts's SafetyResult (verdict: pass|warn|block, findings\n * with file/detail) into report/render.ts's SafetyResult shape (verdict:\n * ALLOW|CAUTION|BLOCK, findings with message) so both commands render through\n * the same Step-3 report box.\n */\n\nexport interface GateRunResult {\n /** Gate-level safety result (verdict: pass|warn|block) — drives the promote/reject decision. */\n safety: GateSafetyResult;\n /** Report input; also carries the profile, overlap, and any Pro-feature notice produced by the gate. */\n gateResult: GateResult;\n /** Whether Pro features should run right now (valid license, or the 0.x beta-free policy) — checked once here so callers don't re-verify. */\n proEnabled: boolean;\n}\n\n/** Best-effort overlap: a missing/empty skills root is reported to stderr, not fatal to the gate. */\nfunction tryOverlap(candidatePath: string, skillsRoot: string): OverlapResult | undefined {\n try {\n return scanOverlap(candidatePath, skillsRoot);\n } catch (err) {\n console.error(`[skill-forge] overlap scan skipped: ${(err as Error).message}`);\n return undefined;\n }\n}\n\n/**\n * MCP counterpart of `tryOverlap`: ranks the candidate against every server\n * entry found across `mcpTargets` (see `gate/mcpOverlap.ts`). `scanMcpOverlap`\n * itself already tolerates missing/malformed/empty target files (returns an\n * empty ranking rather than throwing) — this try/catch is defensive only.\n */\nfunction tryMcpOverlap(profile: McpProfile, mcpTargets: string[]): OverlapResult | undefined {\n try {\n const detail = resolveMcpCandidateDetail(profile);\n return scanMcpOverlap(\n {\n name: detail.name,\n command: detail.command,\n args: detail.args,\n packageName: detail.command ? undefined : detail.name,\n },\n mcpTargets\n );\n } catch (err) {\n console.error(`[skill-forge] mcp overlap scan skipped: ${(err as Error).message}`);\n return undefined;\n }\n}\n\n/**\n * Best-effort skill-map overlap (Phase 4, `skill-map-graph-substrate.md`): loads the\n * generated `rhize-plugins` skill map from `skillMapPath` and ranks the candidate's\n * profiled name/description against every `skill` node it contains — see\n * `gate/mapOverlap.ts`'s doc comment for why this is a separate check from\n * `gate/overlap.ts`'s local-skillsRoot overlap. A missing/unreadable/malformed map is\n * reported to stderr and treated as \"no map available\", never fatal to the gate — same\n * failure posture as `tryOverlap`/`tryMcpOverlap` above.\n */\nfunction tryMapOverlapFinding(\n skillMapPath: string | undefined,\n candidate: { name?: string; description?: string }\n) {\n if (!skillMapPath) return undefined;\n try {\n const map = loadSkillMap(skillMapPath);\n return mapOverlapFinding(scanMapOverlap(candidate, map));\n } catch (err) {\n console.error(`[skill-forge] skill-map overlap check skipped: ${(err as Error).message}`);\n return undefined;\n }\n}\n\n/**\n * Candidate directory to profile/gate for an MCP artifact (v0.5): the\n * extracted npm package dir when the source was npm, else the single\n * subdirectory `quarantine()` copied/cloned the source into — local dir and\n * git URL, the other two v0.5 MCP source forms, both land in exactly one\n * subdirectory of a freshly created quarantine sandbox (see\n * `install/quarantine.ts`). Falls back to the sandbox root itself if that\n * can't be determined unambiguously.\n */\nexport function resolveMcpCandidatePath(entry: QuarantineEntry): string {\n if (entry.packageDir) return entry.packageDir;\n let children: string[] = [];\n try {\n children = readdirSync(entry.quarantinePath, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => d.name);\n } catch {\n // fall through to the sandbox root\n }\n return children.length === 1 ? join(entry.quarantinePath, children[0]) : entry.quarantinePath;\n}\n\n/**\n * Runs profile -> safety -> overlap for a single quarantined candidate and\n * builds the report input. Overlap analysis (and any other Pro feature the\n * caller gates on `proEnabled`) is checked exactly once via `proFeatureStatus`\n * — during the 0.x beta this is `enabled: true` for everyone, with an\n * optional notice surfaced through `gateResult.notices`; post-1.0 (or with an\n * invalid/expired key once the beta flag flips) it's `enabled: false` and\n * `overlapLocked` is set so the report shows the upgrade notice in place of\n * the overlap lines. `proFeatureLabel` names the Pro feature(s) this\n * particular caller exercises, for the printed/JSON notice text — callers\n * that also gate other Pro features off the same `proEnabled` result (e.g.\n * `add`'s provenance ledger and pending-queue write) should pass a label\n * covering all of them so only one notice is produced per invocation.\n * `strictness` selects which finding severities produce a `block` safety\n * verdict (see `src/gate/safety.ts`); `add`/`scan` pass the value from the\n * config they already load, so the gate honors whatever is configured without\n * re-reading `config.json` here. Callers that omit it (e.g. unit tests) get\n * the `block-high` default. `artifact` (v0.5) routes the whole pipeline: for\n * `'mcp'` (explicit `--artifact mcp` only — never auto-detected here, see\n * `install/resolve.ts`'s \"no ambiguity guessing\" source-resolution policy),\n * safety runs `mcpScan` (base ruleset + MCP-specific rules) instead of\n * `scanSafety`, and overlap ranks against `mcpTargets` (every configured MCP\n * config file) via `scanMcpOverlap` instead of `scanOverlap`-against-\n * `skillsRoot`; `skillsRoot`/`mcpTargets` are simply unused by whichever\n * branch doesn't apply. Defaults to `'skill'`, the pre-v0.5 behavior.\n */\nexport async function runGate(\n candidatePath: string,\n skillsRoot: string,\n {\n proFeatureLabel = 'overlap analysis',\n strictness = DEFAULT_STRICTNESS,\n artifact = 'skill',\n mcpTargets = [],\n skillMapPath,\n }: {\n proFeatureLabel?: string;\n strictness?: Strictness;\n artifact?: ArtifactType;\n mcpTargets?: string[];\n /** Path to the generated `rhize-plugins` skill map (Phase 4) — enables the marketplace-wide curation-overlap check. Skill/plugin artifacts only; unused for 'mcp'. Optional — omitted means no map check runs. */\n skillMapPath?: string;\n } = {}\n): Promise<GateRunResult> {\n const profile = asProfile(candidatePath, artifact);\n const proStatus = proFeatureStatus(proFeatureLabel);\n\n const safety = artifact === 'mcp' ? await mcpScan(candidatePath, strictness) : await scanSafety(candidatePath, strictness);\n const overlap = !proStatus.enabled\n ? undefined\n : artifact === 'mcp'\n ? tryMcpOverlap(profile as McpProfile, mcpTargets)\n : tryOverlap(candidatePath, skillsRoot);\n\n // Free (not Pro-gated) — this enforces the marketplace's own curation rule, a policy\n // check, not a premium ranking feature. Not applicable to 'mcp' artifacts: the map only\n // carries `skill` nodes today.\n const mapFinding =\n artifact === 'mcp'\n ? undefined\n : tryMapOverlapFinding(skillMapPath, {\n name: 'name' in profile ? profile.name : undefined,\n description: 'description' in profile ? profile.description : undefined,\n });\n\n // A strong map-overlap match is folded into the same findings list `verdictFor` already\n // grades safety findings against, so it can escalate the verdict (hold/block) rather than\n // sit off to the side as informational-only text nobody has to act on.\n const combinedSafety = mapFinding\n ? { verdict: verdictFor([...safety.findings, mapFinding], strictness), findings: [...safety.findings, mapFinding] }\n : safety;\n\n // Gathered HERE rather than at each call site: `add` (skill), `add --artifact mcp`, `scan`, and\n // `promote <id>` all funnel through runGate, and when each built this itself they immediately\n // drifted — the MCP path silently had no audit context at all.\n const advisory = formatAuditAdvisory(readAuditState(), {\n targetRoot: skillsRoot || undefined,\n candidateName: 'name' in profile ? profile.name : undefined,\n });\n\n const gateResult: GateResult = {\n profile,\n overlap,\n overlapLocked: !proStatus.enabled,\n notices: proStatus.notice ? [proStatus.notice] : undefined,\n safety: {\n verdict: SAFETY_VERDICT_MAP[combinedSafety.verdict],\n findings: mapSafetyFindings(combinedSafety.findings),\n },\n advisory,\n };\n\n return { safety: combinedSafety, gateResult, proEnabled: proStatus.enabled };\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\nimport type { AuditReport } from './audit.js';\nimport { sanitizeForMarkdown } from './audit.js';\nimport type { SuggestedVerbCode } from './gate/overlap.js';\nimport type { Severity } from './gate/scanShared.js';\nimport { getHomeDir } from './config.js';\nimport { writeJsonAtomic } from './util.js';\n\n/**\n * The audit's machine-readable residue: a small summary every `audit` run leaves behind at\n * `~/.skill-forge/audit-state.json`, so the rest of the CLI can cite it.\n *\n * Before v0.11 `audit` was a dead end — it printed a report, wrote a markdown file, and nothing\n * else ever read either. `add`/`scan` therefore gated candidates into roots whose known problems\n * they couldn't mention, and nothing anywhere could tell you your last audit was four months old.\n *\n * **Advisory only, by deliberate design.** Nothing derived from this file may change a safety\n * verdict or auto-reject a candidate. A gate verdict is a function of the CANDIDATE; audit\n * findings describe the SET YOU ALREADY HAVE, and letting the latter silently veto the former\n * would make `add` non-deterministic for the same input and give a stale report the power to\n * block work. It surfaces context next to the decision and leaves the decision where it was.\n */\n\n/** Finding counts by severity — the same `Record<Severity, number>` shape `AuditSummary.findingCounts` uses, so nothing is lost or remapped in translation. */\nexport type AuditStateCounts = Record<Severity, number>;\n\nexport interface AuditStateCluster {\n /** Skill NAMES (not paths) — the report holds paths; names are what a candidate can be compared against. */\n members: string[];\n topScore: number;\n /** Kept as the narrow union rather than widened to `string` at the persistence boundary. */\n suggestedVerb: SuggestedVerbCode;\n}\n\nexport interface AuditState {\n schemaVersion: 1;\n /** ISO-8601 timestamp of the audit run that produced this. */\n lastRunAt: string;\n /** Path of the markdown report that run wrote, when it wrote one. */\n reportPath?: string;\n skillCount: number;\n mcpTargetCount: number;\n /** Whole-set finding counts by severity. */\n findings: AuditStateCounts;\n /** Finding counts keyed by the configured root each finding's skill lives under. */\n byRoot: Record<string, AuditStateCounts>;\n /** Names of skills the audit clustered as overlapping — a candidate matching one is worth flagging. */\n overlapClusters: AuditStateCluster[];\n /** True when the overlap scan was Pro-locked for that run: empty clusters mean \"not analyzed\", not \"none found\". */\n overlapLocked: boolean;\n evolveEligibleCount: number;\n /**\n * Number of findings this run matched to a stored acceptance (v0.12) — additive, defaults to `0`\n * on load so a pre-v0.12 state file stays valid. `findings`/`byRoot` above already count ACTIVE\n * findings only (see `summarizeAudit`'s doc comment) — this field is purely informational, not a\n * second source of the active/accepted split.\n */\n acceptedCount?: number;\n}\n\nexport function getAuditStatePath(): string {\n return join(getHomeDir(), 'audit-state.json');\n}\n\nfunction emptyCounts(): AuditStateCounts {\n return { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };\n}\n\nfunction bump(counts: AuditStateCounts, severity: Severity): void {\n counts[severity] = (counts[severity] ?? 0) + 1;\n}\n\n/** Findings serious enough to mention next to a promote decision. */\nfunction seriousCount(counts: AuditStateCounts): number {\n return (counts.CRITICAL ?? 0) + (counts.HIGH ?? 0) + (counts.MEDIUM ?? 0);\n}\n\n/**\n * Derives the persisted summary from a full `AuditReport`.\n *\n * Findings are attributed to a root by looking the finding's `target` path up in the inventory —\n * a finding whose target isn't a known skill path (an MCP target file, a root-level problem) is\n * counted in the whole-set totals but not attributed to any root, rather than being guessed into\n * one.\n */\nexport function summarizeAudit(report: AuditReport, reportPath?: string): AuditState {\n // Keyed on the skill DIRECTORY only — the parent walk below reaches SKILL.md and anything else\n // inside it, so a second entry per skill would be redundant.\n const rootOfSkillPath = new Map<string, string>();\n for (const skill of report.inventory.skills) {\n rootOfSkillPath.set(skill.path, skill.root);\n }\n\n const byRoot: Record<string, AuditStateCounts> = {};\n for (const finding of report.hygiene) {\n // Walk up the target's parent directories: a finding targets a file inside a skill dir as\n // often as the dir itself. Parent-walking IS the longest-prefix match (scanning the map and\n // taking the first `startsWith` hit would return whichever path happened to be inserted\n // first), and it costs path-depth lookups instead of one pass over every known skill.\n let root: string | undefined;\n for (let dir = finding.target; ; dir = dirname(dir)) {\n root = rootOfSkillPath.get(dir);\n if (root || dirname(dir) === dir) break;\n }\n if (!root) continue;\n byRoot[root] ??= emptyCounts();\n bump(byRoot[root], finding.severity);\n }\n\n return {\n schemaVersion: 1,\n lastRunAt: report.generatedAt,\n reportPath,\n skillCount: report.summary.skillCount,\n mcpTargetCount: report.summary.mcpTargetCount,\n // Taken from the report rather than re-accumulated: `audit.ts` already counted exactly this\n // array, and a second tally is how the terminal summary and the markdown report end up\n // disagreeing about the same run.\n findings: { ...report.summary.findingCounts },\n byRoot,\n overlapClusters: report.opportunities.overlapClusters.map((c) => ({\n // Cluster members are SKILL.md paths; the skill name is the parent directory name. Stripped\n // of control characters at the boundary where they are PERSISTED, because these names come\n // from directories on disk and are later printed into the gate report.\n members: c.members.map((m) => sanitizeForMarkdown(basename(m.replace(/\\/SKILL\\.md$/, '')))),\n topScore: c.topScore,\n suggestedVerb: c.suggestedVerb,\n })),\n overlapLocked: report.opportunities.overlapLocked === true,\n evolveEligibleCount: report.summary.evolveEligibleCount,\n acceptedCount: report.summary.acceptedCount,\n };\n}\n\n/** Persists the summary. Atomic + owner-only via the shared writer in util.ts. */\nexport function writeAuditState(state: AuditState): void {\n writeJsonAtomic(getAuditStatePath(), state);\n}\n\n/**\n * Reads the persisted summary, or undefined when there is none / it is unusable.\n *\n * Never throws: this is consulted from inside `add`'s gate path, and a corrupt advisory file must\n * degrade to \"no audit context\" rather than break an install. A `schemaVersion` this build does\n * not know is treated the same way — future-version state is ignored, not misread.\n */\nexport function readAuditState(): AuditState | undefined {\n const path = getAuditStatePath();\n if (!existsSync(path)) return undefined;\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf-8')) as Partial<AuditState>;\n if (parsed.schemaVersion !== 1 || typeof parsed.lastRunAt !== 'string') return undefined;\n return {\n schemaVersion: 1,\n lastRunAt: parsed.lastRunAt,\n reportPath: parsed.reportPath,\n skillCount: parsed.skillCount ?? 0,\n mcpTargetCount: parsed.mcpTargetCount ?? 0,\n findings: parsed.findings ?? emptyCounts(),\n byRoot: parsed.byRoot ?? {},\n overlapClusters: parsed.overlapClusters ?? [],\n overlapLocked: parsed.overlapLocked === true,\n evolveEligibleCount: parsed.evolveEligibleCount ?? 0,\n acceptedCount: parsed.acceptedCount ?? 0,\n };\n } catch {\n return undefined;\n }\n}\n\n/** Whole days since the audit ran; undefined when the timestamp is unparseable. */\nexport function auditAgeDays(state: AuditState, now: Date = new Date()): number | undefined {\n const then = Date.parse(state.lastRunAt);\n if (Number.isNaN(then)) return undefined;\n return Math.max(0, Math.floor((now.getTime() - then) / 86_400_000));\n}\n\n/** Audits older than this are called out as stale. Two weeks: long enough not to nag, short enough to matter. */\nexport const STALE_AUDIT_DAYS = 14;\n\n/** One-line freshness summary for `status`/`guide`/`routine`. */\nexport function formatAuditFreshness(state: AuditState | undefined, now: Date = new Date()): string {\n if (!state) return 'never run — `skill-forge audit`';\n const age = auditAgeDays(state, now);\n if (age === undefined) return `last run at an unreadable timestamp (${state.lastRunAt})`;\n const when = age === 0 ? 'today' : age === 1 ? '1 day ago' : `${age} days ago`;\n const stale = age >= STALE_AUDIT_DAYS ? ' — stale, consider re-running `skill-forge audit`' : '';\n return `${when}${stale}`;\n}\n\n/**\n * Advisory lines for the gate report — plain text, NO label or padding: `report/render.ts` owns\n * the label column (it knows `LABEL_WIDTH`), so this module cannot misalign against it.\n *\n * `candidateName` is matched against the names in each recorded overlap cluster: promoting a\n * skill into a set that ALREADY has an unresolved overlap cluster around that name is exactly the\n * moment to mention it. Never returns anything that looks like a verdict.\n */\nexport function formatAuditAdvisory(\n state: AuditState | undefined,\n options: { targetRoot?: string; candidateName?: string; now?: Date } = {}\n): string[] {\n if (!state) {\n return ['no audit on record — `skill-forge audit` reports what your installed set already looks like'];\n }\n const lines = [`last audit ${formatAuditFreshness(state, options.now)}`];\n\n if (options.targetRoot) {\n const counts = state.byRoot[options.targetRoot];\n if (counts && seriousCount(counts) > 0) {\n const parts = [\n counts.CRITICAL ? `${counts.CRITICAL} critical` : '',\n counts.HIGH ? `${counts.HIGH} high` : '',\n counts.MEDIUM ? `${counts.MEDIUM} medium` : '',\n ].filter(Boolean);\n lines.push(`target root has ${parts.join(' / ')} open finding(s) from that audit`);\n }\n }\n\n if (options.candidateName) {\n const needle = options.candidateName.toLowerCase();\n const hit = state.overlapClusters.find((c) => c.members.some((m) => m.toLowerCase() === needle));\n if (hit) {\n lines.push(\n `\"${options.candidateName}\" is already in a flagged overlap cluster (${hit.members.join(', ')}; suggested ${hit.suggestedVerb})`\n );\n }\n }\n\n return lines;\n}\n","import {\n existsSync,\n lstatSync,\n mkdtempSync,\n readFileSync,\n readdirSync,\n realpathSync,\n renameSync,\n rmSync,\n statSync,\n writeFileSync,\n type Dirent,\n} from 'node:fs';\nimport { basename, delimiter, extname, join, resolve as resolvePath } from 'node:path';\nimport type { BusinessProfile, Config, Strictness } from './config.js';\nimport { UserError } from './errors.js';\nimport { scanSafety } from './gate/safety.js';\nimport { scanMcpFindings } from './gate/mcpSafety.js';\nimport { scanOverlapPaths, suggestVerbCode, type OverlapPathPair, type SuggestedVerbCode } from './gate/overlap.js';\nimport {\n collectNpxPackageSpecs,\n formatSafetyFinding,\n isNpxCommand,\n verdictFor,\n type SafetyFinding,\n type Severity,\n type Verdict,\n} from './gate/scanShared.js';\nimport { fingerprintFinding, FINGERPRINT_DISPLAY_LENGTH, type AcceptedFinding } from './findings.js';\nimport { proFeatureStatus } from './license.js';\nimport { appendProvenance } from './provenance.js';\nimport { expandHome, isDir, stripQuotes } from './util.js';\n\n/**\n * `skill-forge audit` engine (v0.8, MIT, side-effect-free except\n * `scaffoldFoundationSkill`) — a doctor-style health check over the\n * EXISTING configured skill roots and MCP targets: inventory, static\n * hygiene/safety findings, and consolidation/refinement opportunities. The\n * decide/absorb judgment itself is handed to the user's coding agent via the\n * bundled `assets/curation-prompt.md` (see src/handoff.ts) — this module\n * only produces the static evidence, never a verdict on what to actually do.\n *\n * Nothing scanned by this module is ever executed. `auditConfiguredSet` only\n * reads; `scaffoldFoundationSkill` is the one function here that writes, and\n * only under an explicit caller opt-in (see src/commands/audit.ts).\n */\n\n/** Threshold above which a SKILL.md's estimated token count becomes an `oversized-skill` finding. */\nexport const OVERSIZED_SKILL_EST_TOKENS = 5_000;\n\nexport interface AuditOptions {\n /** Overrides gate/overlap.ts's default cross-root overlap threshold (0.45). */\n overlapThreshold?: number;\n /** Overrides OVERSIZED_SKILL_EST_TOKENS for the oversized-skill finding. */\n oversizedSkillTokens?: number;\n /** Injectable clock for deterministic `generatedAt` in tests. Defaults to `() => new Date()`. */\n now?: () => Date;\n /** Injectable PATH lookup for the tooling section (`skillspector` detection). Defaults to a real PATH scan. */\n which?: (cmd: string) => string | undefined;\n /** Injectable environment for the tooling section (VERCEL_OIDC_TOKEN / SKILLSPECTOR_PROVIDER / provider key presence). Defaults to `process.env`. */\n env?: NodeJS.ProcessEnv;\n /**\n * Accepted-findings records to partition active findings against (v0.12) — injectable like\n * `now`/`which`/`env` so the engine stays pure. Callers (`commands/audit.ts`,\n * `commands/routine.ts`, `commands/finding.ts`) load `~/.skill-forge/accepted-findings.json` via\n * `src/findings.ts`'s `readAcceptedFindings()` and pass its `records` here; any store-read\n * notices they got back are the caller's job to fold into the returned report, not this engine's.\n * Defaults to `[]` — nothing accepted, every finding reports active.\n */\n accepted?: AcceptedFinding[];\n}\n\nexport interface AuditedRoot {\n /** The root exactly as configured (may contain `~`). */\n root: string;\n /** Resolved realpath, or the configured root itself when it doesn't exist / can't be resolved. */\n realPath: string;\n exists: boolean;\n skillCount: number;\n}\n\nexport interface AuditedSkill {\n name: string;\n /** Canonical (first-seen, realpath-deduped) skill directory. */\n path: string;\n skillMdPath: string;\n /** The configured root this skill was discovered under. */\n root: string;\n /** Other configured-root paths (symlink aliases) that resolve to this same physical skill. */\n aliasPaths: string[];\n validFrontmatter: boolean;\n description?: string;\n sizeBytes: number;\n estTokens: number;\n}\n\nexport interface AuditedMcpServer {\n name: string;\n commandBasename?: string;\n packageSpec?: string;\n argCount: number;\n envCount: number;\n}\n\nexport type AuditedMcpTargetStatus = 'clean' | 'missing' | 'unreadable' | 'malformed';\n\nexport interface AuditedMcpTarget {\n path: string;\n format: 'json' | 'toml';\n status: AuditedMcpTargetStatus;\n /** Server enumeration — JSON targets only; always empty for TOML (structured enumeration unsupported) or a non-clean status. */\n servers: AuditedMcpServer[];\n}\n\nexport interface AuditFinding {\n severity: Severity;\n rule: string;\n target: string;\n detail: string;\n /** Content-derived fingerprinting input (v0.12) — see `src/findings.ts`'s module doc. Additive/optional. */\n context?: string;\n /**\n * sha256 identity computed by this engine for EVERY finding (active or accepted) — v0.12,\n * additive. Absent only on a pre-existing `AuditFinding` object built by hand rather than by\n * `auditConfiguredSet` (e.g. `test/auditState.test.ts`'s fixtures).\n */\n fingerprint?: string;\n}\n\n/** One record in `AuditReport.accepted` (v0.12) — an active finding this run matched to a stored acceptance, paired with why and when a human accepted it. */\nexport interface AcceptedFindingEntry {\n finding: AuditFinding;\n reason: string;\n acceptedAt: string;\n}\n\n/** Reshapes gate/scanShared.ts's `{severity,rule,file,detail,context?}` SafetyFinding into this module's `{severity,rule,target,detail,context?}` AuditFinding — shared by the per-skill safety scan and the MCP target scan. */\nfunction toAuditFindings(source: SafetyFinding[]): AuditFinding[] {\n return source.map((f) => ({ severity: f.severity, rule: f.rule, target: f.file, detail: f.detail, context: f.context }));\n}\n\n/** Reshapes AuditFinding[] back into SafetyFinding[] shape (target -> file) so `gate/scanShared.ts`'s `verdictFor` can be reused for the v0.12 evolve-eligible recompute below, without a second severity->verdict mapping. */\nfunction activeVerdictFor(findings: AuditFinding[], strictness: Strictness): Verdict {\n return verdictFor(\n findings.map((f) => ({ severity: f.severity, rule: f.rule, file: f.target, detail: f.detail })),\n strictness\n );\n}\n\nexport interface OverlapCluster {\n /** SKILL.md paths in this connected component, sorted for determinism — always at least 2 (a cluster only exists when at least one scored pair formed it). */\n members: [string, string, ...string[]];\n topScore: number;\n /** Bare verb code (`suggestVerbCode`'s typed union) — markdown renders it as-is; prose belongs to the agent-side curation prompt, not this report. */\n suggestedVerb: SuggestedVerbCode;\n}\n\nexport interface EvolveCandidate {\n name: string;\n path: string;\n}\n\n/** Discriminated on `available`: the `false` branch always carries the existing path (never optional-and-absent by accident). */\nexport type FoundationOpportunity =\n | { available: true; message: string }\n | { available: false; existingPath: string; message: string };\n\nexport interface AuditSummary {\n rootCount: number;\n skillCount: number;\n mcpTargetCount: number;\n /** Counts ACTIVE findings only (v0.12) — an accepted finding is excluded, same rationale as `hygiene` below. */\n findingCounts: Record<Severity, number>;\n overlapClusterCount: number;\n evolveEligibleCount: number;\n /** Number of active findings this run matched to a stored acceptance (v0.12). */\n acceptedCount: number;\n}\n\nexport interface AuditInventory {\n skillRoots: AuditedRoot[];\n skills: AuditedSkill[];\n mcpTargets: AuditedMcpTarget[];\n}\n\nexport interface AuditOpportunities {\n overlapClusters: OverlapCluster[];\n evolveEligible: EvolveCandidate[];\n foundation: FoundationOpportunity;\n /** True when the Pro overlap gate was locked for this run (see `notices` for the upgrade notice) — lets JSON consumers branch on this instead of string-matching `notices`. */\n overlapLocked: boolean;\n}\n\n/** `skillspector` CLI (deep local safety scan, github.com/NVIDIA/SkillSpector) readiness — see gate/safety.ts's own PATH-based `scanWithSkillSpector`, which this only reports on, never invokes. */\nexport interface AuditToolingSkillspector {\n installed: boolean;\n /** Setup guidance, present only when `installed` is false. */\n hint?: string;\n}\n\n/** `VERCEL_OIDC_TOKEN` presence — the auth anchor for skills.sh discovery/partner audits (`skill-forge find`). Presence only; the token value is never read into a report. */\nexport interface AuditToolingSkillsShToken {\n present: boolean;\n /** Setup guidance, present only when `present` is false. */\n hint?: string;\n}\n\n/** Optional SkillSpector LLM (semantic) stage readiness — static scanning works without it. Provider NAME only; the associated API key's value is never read into a report. */\nexport interface AuditToolingSkillspectorLlm {\n configured: boolean;\n provider?: string;\n}\n\nexport interface AuditTooling {\n skillspector: AuditToolingSkillspector;\n skillsShToken: AuditToolingSkillsShToken;\n skillspectorLlm: AuditToolingSkillspectorLlm;\n}\n\nexport interface AuditReport {\n schemaVersion: 1;\n generatedAt: string;\n inventory: AuditInventory;\n /** ACTIVE findings only (v0.12) — an accepted finding moves to `accepted` below, never disappears. */\n hygiene: AuditFinding[];\n /** Findings this run matched to a stored acceptance (v0.12) — permanently visible, excluded from `hygiene`/`summary.findingCounts`. */\n accepted: AcceptedFindingEntry[];\n opportunities: AuditOpportunities;\n /** Skill/MCP tooling readiness (skillspector CLI, skills.sh auth, optional SkillSpector LLM stage) — port of skill_doctor.py. Additive (v0.8.1); absent only on a pre-existing report object built by hand rather than by `auditConfiguredSet`. */\n tooling?: AuditTooling;\n notices: string[];\n summary: AuditSummary;\n}\n\n// ---------------------------------------------------------------------------\n// Inventory: symlink-aware skill discovery\n// ---------------------------------------------------------------------------\n\n/** Resolves a configured root (which may contain `~`) to an absolute filesystem path, without probing existence. */\nfunction resolveConfiguredRoot(root: string): string {\n return resolvePath(expandHome(root));\n}\n\n/** `statSync` that follows symlinks and swallows any error (missing/broken/permission) rather than throwing. */\nfunction safeStat(p: string) {\n try {\n return statSync(p);\n } catch {\n return undefined;\n }\n}\n\nfunction isDirFollowingSymlink(p: string): boolean {\n return safeStat(p)?.isDirectory() ?? false;\n}\n\n/**\n * Subdirectories of `dir`, including symlinked ones that resolve to a directory. Deliberately\n * does NOT rely on `Dirent#isDirectory()` alone — for a symlink entry, `readdir`'s dirent type\n * reflects the link itself (DT_LNK), not what it points at, so a symlinked skills root or skill\n * dir would otherwise be silently skipped (the same lesson noted on gate/overlap.ts's `find`-based\n * walker, which has the identical gap).\n */\nfunction listSubdirsFollowingSymlinks(dir: string): string[] {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const out: string[] = [];\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory() || (entry.isSymbolicLink() && isDirFollowingSymlink(full))) {\n out.push(full);\n }\n }\n return out;\n}\n\n/**\n * Same shape as `listSubdirsFollowingSymlinks`, but does NOT follow symlinked entries — only\n * real directories (`Dirent#isDirectory()`, no stat-follow). Used exclusively by\n * `findSkillDirsUnderRoot`'s third (nested `topDir/skills/*`) discovery loop, to close a\n * symlink-escape path: a malicious INSTALLED skill (itself a legitimate top-level entry under a\n * configured root) could otherwise plant its own `skills/<name>` symlink pointing outside the\n * root, and have whatever SKILL.md lives at that external destination silently absorbed into\n * this root's inventory — audited and disclosed into reports/handoffs as if it belonged to the\n * configured root. Top-level (`root/*`) and `root/skills/*` discovery deliberately keep\n * following symlinks — legitimately symlinked skill dirs at those levels are a primary use case.\n */\nfunction listSubdirsNoSymlinks(dir: string): string[] {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n return entries.filter((e) => e.isDirectory()).map((e) => join(dir, e.name));\n}\n\n/** Same three glob shapes as gate/overlap.ts's `findSkillMdFiles`, but symlink-aware (see `listSubdirsFollowingSymlinks`) and returning skill DIRECTORIES, not SKILL.md paths. */\nfunction findSkillDirsUnderRoot(root: string): string[] {\n const found = new Set<string>();\n const rootSubdirs = listSubdirsFollowingSymlinks(root);\n\n for (const dir of rootSubdirs) {\n if (existsSync(join(dir, 'SKILL.md'))) found.add(dir);\n }\n for (const dir of listSubdirsFollowingSymlinks(join(root, 'skills'))) {\n if (existsSync(join(dir, 'SKILL.md'))) found.add(dir);\n }\n for (const topDir of rootSubdirs) {\n // Nested loop: does NOT follow symlinked entries — see listSubdirsNoSymlinks's doc comment.\n for (const dir of listSubdirsNoSymlinks(join(topDir, 'skills'))) {\n if (existsSync(join(dir, 'SKILL.md'))) found.add(dir);\n }\n }\n\n return Array.from(found).filter((p) => !p.includes('/.git/'));\n}\n\ninterface SkillCandidate {\n /** Path as discovered by the shape-walk above — may traverse through a symlink. */\n configuredPath: string;\n /** The configured root (raw, as given in config.skillsRoots) this candidate was found under. */\n root: string;\n}\n\ninterface RootMeta {\n exists: boolean;\n realPath?: string;\n}\n\n/** Probes every configured root and collects every skill-dir candidate found under it (pre-dedupe). */\nfunction collectSkillCandidates(roots: readonly string[]): {\n candidates: SkillCandidate[];\n rootMeta: Map<string, RootMeta>;\n} {\n const candidates: SkillCandidate[] = [];\n const rootMeta = new Map<string, RootMeta>();\n\n for (const root of roots) {\n const resolvedRoot = resolveConfiguredRoot(root);\n const exists = isDir(resolvedRoot);\n let realPath: string | undefined;\n if (exists) {\n try {\n realPath = realpathSync(resolvedRoot);\n } catch {\n realPath = undefined;\n }\n }\n rootMeta.set(root, { exists, realPath });\n if (!exists) continue;\n\n for (const dir of findSkillDirsUnderRoot(resolvedRoot)) {\n candidates.push({ configuredPath: dir, root });\n }\n }\n\n return { candidates, rootMeta };\n}\n\ninterface ResolvedSkillGroup {\n realPath: string;\n /** First configured path seen for this physical skill — the canonical location reported. */\n canonicalPath: string;\n root: string;\n /** Other configured-root paths (symlink aliases) resolving to the same realPath. */\n aliasPaths: string[];\n}\n\n/**\n * Dedupes skill candidates by `realpathSync` so the same physical skill reached via two roots\n * (or a symlinked alias within one root) is reported once, with alias locations retained rather\n * than dropped. A candidate whose realpath can't be resolved (broken symlink, or an `ELOOP`\n * self-referential symlink cycle) becomes an `unreadable-skill` finding and is skipped, rather\n * than aborting the whole audit.\n */\nfunction dedupeSkillCandidates(candidates: SkillCandidate[], findings: AuditFinding[]): ResolvedSkillGroup[] {\n const byRealPath = new Map<string, ResolvedSkillGroup>();\n const order: string[] = [];\n\n for (const candidate of candidates) {\n let realPath: string;\n try {\n realPath = realpathSync(candidate.configuredPath);\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'unreadable-skill',\n target: candidate.configuredPath,\n detail: `could not resolve skill directory (broken symlink or cycle): ${(err as Error).message}`,\n });\n continue;\n }\n\n const existing = byRealPath.get(realPath);\n if (existing) {\n if (existing.canonicalPath !== candidate.configuredPath && !existing.aliasPaths.includes(candidate.configuredPath)) {\n existing.aliasPaths.push(candidate.configuredPath);\n }\n continue;\n }\n\n byRealPath.set(realPath, { realPath, canonicalPath: candidate.configuredPath, root: candidate.root, aliasPaths: [] });\n order.push(realPath);\n }\n\n return order.map((realPath) => byRealPath.get(realPath)!);\n}\n\n// ---------------------------------------------------------------------------\n// Strict frontmatter validation — deliberately NOT gate/overlap.ts's readFrontmatter,\n// whose dirname/heading fallbacks exist for scoring robustness, not validation.\n// ---------------------------------------------------------------------------\n\ninterface StrictFrontmatterResult {\n valid: boolean;\n name?: string;\n description?: string;\n errors: string[];\n}\n\n/** Strict frontmatter parse: a missing/unclosed `---` fence or a missing/empty `name`/`description` field is a finding, never silently backfilled. */\nfunction parseStrictFrontmatter(text: string): StrictFrontmatterResult {\n if (!text.startsWith('---\\n')) {\n return { valid: false, errors: ['missing frontmatter fence (SKILL.md must start with \"---\")'] };\n }\n\n const afterOpen = text.slice(4);\n const closeIdx = afterOpen.indexOf('\\n---');\n if (closeIdx === -1) {\n return { valid: false, errors: ['unclosed frontmatter fence (no closing \"---\" found)'] };\n }\n const block = afterOpen.slice(0, closeIdx);\n const errors: string[] = [];\n\n const nameMatch = block.match(/^name:\\s*(.+)$/m);\n const name = nameMatch ? stripQuotes(nameMatch[1].trim()) : undefined;\n if (!name) errors.push('missing required \"name\" field in frontmatter');\n\n const descMatch = block.match(/^description:\\s*(.*)$/m);\n let description: string | undefined;\n if (descMatch) {\n const first = descMatch[1].trim();\n if (['>', '>-', '|', '|-', ''].includes(first)) {\n const lines = block.split(/\\r\\n|\\r|\\n/);\n const idx = lines.findIndex((l) => /^description:/.test(l));\n const buf: string[] = [];\n if (idx !== -1) {\n for (const l of lines.slice(idx + 1)) {\n if (/^\\s+\\S/.test(l)) buf.push(l.trim());\n else if (l.trim() === '') continue;\n else break;\n }\n }\n description = buf.join(' ').trim() || undefined;\n } else {\n description = stripQuotes(first);\n }\n }\n if (!description) errors.push('missing required \"description\" field in frontmatter');\n\n return { valid: errors.length === 0, name, description, errors };\n}\n\n/** Ceil(byteLength/4) — a rough, always-labeled-\"est.\" token estimate. Shared by the hygiene oversized check and the report renderer. */\nexport function estimateTokens(text: string): number {\n return Math.ceil(Buffer.byteLength(text, 'utf-8') / 4);\n}\n\nfunction profileAuditedSkill(\n group: ResolvedSkillGroup,\n findings: AuditFinding[],\n unreadableSkillMdPaths: Set<string>\n): AuditedSkill {\n const skillMdPath = join(group.canonicalPath, 'SKILL.md');\n const base: Omit<AuditedSkill, 'name' | 'validFrontmatter' | 'description' | 'sizeBytes' | 'estTokens'> = {\n path: group.canonicalPath,\n skillMdPath,\n root: group.root,\n aliasPaths: group.aliasPaths,\n };\n\n let text: string;\n let sizeBytes: number;\n try {\n sizeBytes = statSync(skillMdPath).size;\n text = readFileSync(skillMdPath, 'utf-8');\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'unreadable-skill',\n target: skillMdPath,\n detail: `could not read SKILL.md: ${(err as Error).message}`,\n });\n // Excluded from the cross-root overlap scan below (see auditConfiguredSet) — that scanner's\n // readFrontmatter does an unguarded readFileSync, so feeding it a path we already know is\n // unreadable would abort the whole audit rather than degrade gracefully.\n unreadableSkillMdPaths.add(skillMdPath);\n return { ...base, name: basename(group.canonicalPath), validFrontmatter: false, sizeBytes: 0, estTokens: 0 };\n }\n\n const fm = parseStrictFrontmatter(text);\n if (!fm.valid) {\n for (const error of fm.errors) {\n findings.push({ severity: 'MEDIUM', rule: 'malformed-frontmatter', target: skillMdPath, detail: error });\n }\n }\n\n return {\n ...base,\n name: fm.name ?? basename(group.canonicalPath),\n validFrontmatter: fm.valid,\n description: fm.description,\n sizeBytes,\n estTokens: estimateTokens(text),\n };\n}\n\n// ---------------------------------------------------------------------------\n// MCP target inventory — JSON structured enumeration (names/basenames/specs/COUNTS\n// only, never env values or arbitrary arg values); TOML is textual-scan-only.\n// ---------------------------------------------------------------------------\n\ninterface RawMcpServerEntry {\n command?: unknown;\n args?: unknown;\n env?: unknown;\n}\n\n/** True when a parsed JSON document has an `mcpServers` or `servers` object — the shape `enumerateMcpServers` knows how to read. Shared with `auditMcpTarget`'s `unrecognized-mcp-target-shape` finding so the two can't drift on what counts as \"MCP-shaped\". */\nfunction hasRecognizedMcpShape(parsed: unknown): boolean {\n if (typeof parsed !== 'object' || parsed === null) return false;\n const obj = parsed as Record<string, unknown>;\n const serversRaw = obj.mcpServers ?? obj.servers;\n return typeof serversRaw === 'object' && serversRaw !== null;\n}\n\n/** Enumerates a parsed MCP config's `mcpServers`/`servers` map into name/command-basename/package-spec/COUNTS only — see AuditedMcpServer's doc comment on the report shape. */\nfunction enumerateMcpServers(parsed: unknown): AuditedMcpServer[] {\n if (!hasRecognizedMcpShape(parsed)) return [];\n const obj = parsed as Record<string, unknown>;\n const serversRaw = obj.mcpServers ?? obj.servers;\n\n const out: AuditedMcpServer[] = [];\n for (const [name, raw] of Object.entries(serversRaw as Record<string, unknown>)) {\n if (typeof raw !== 'object' || raw === null) continue;\n const entry = raw as RawMcpServerEntry;\n const command = typeof entry.command === 'string' ? entry.command : undefined;\n const args = Array.isArray(entry.args) ? entry.args.filter((a): a is string => typeof a === 'string') : [];\n const env = entry.env && typeof entry.env === 'object' && !Array.isArray(entry.env) ? (entry.env as Record<string, unknown>) : {};\n\n out.push({\n name,\n commandBasename: command ? basename(command) : undefined,\n packageSpec: command && isNpxCommand(command) ? collectNpxPackageSpecs(args).positionalSpec : undefined,\n argCount: args.length,\n envCount: Object.keys(env).length,\n });\n }\n return out;\n}\n\nfunction auditMcpTarget(targetPath: string, notices: string[]): { target: AuditedMcpTarget; findings: AuditFinding[] } {\n const findings: AuditFinding[] = [];\n const isToml = extname(targetPath).toLowerCase() === '.toml';\n const format: AuditedMcpTarget['format'] = isToml ? 'toml' : 'json';\n\n if (!existsSync(targetPath)) {\n findings.push({\n severity: 'LOW',\n rule: 'missing-mcp-target',\n target: targetPath,\n detail: 'configured MCP target file does not exist',\n });\n return { target: { path: targetPath, format, status: 'missing', servers: [] }, findings };\n }\n\n // Textual scan (unpinned npx, inline credentials, dangerous flags) runs for both\n // formats — it's the ONLY enumeration TOML gets; JSON also gets structured below.\n try {\n findings.push(...toAuditFindings(scanMcpFindings(targetPath)));\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'unreadable-mcp-target',\n target: targetPath,\n detail: `MCP safety scan failed: ${(err as Error).message}`,\n });\n }\n\n if (isToml) {\n // Explicit readability probe: scanMcpFindings above swallows read failures internally\n // (readTextFile catches and returns undefined -> no findings, no throw), so without this\n // check an unreadable TOML file would otherwise fall straight through to a hard-coded\n // 'clean' status below.\n try {\n readFileSync(targetPath, 'utf-8');\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'unreadable-mcp-target',\n target: targetPath,\n detail: `could not read: ${(err as Error).message}`,\n });\n return { target: { path: targetPath, format, status: 'unreadable', servers: [] }, findings };\n }\n\n notices.push(`MCP target ${targetPath}: TOML format — structured server enumeration unsupported (textual safety scan only)`);\n return { target: { path: targetPath, format, status: 'clean', servers: [] }, findings };\n }\n\n let raw: string;\n try {\n raw = readFileSync(targetPath, 'utf-8');\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'unreadable-mcp-target',\n target: targetPath,\n detail: `could not read: ${(err as Error).message}`,\n });\n return { target: { path: targetPath, format, status: 'unreadable', servers: [] }, findings };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'malformed-mcp-target',\n target: targetPath,\n detail: `invalid JSON: ${(err as Error).message}`,\n });\n return { target: { path: targetPath, format, status: 'malformed', servers: [] }, findings };\n }\n\n if (!hasRecognizedMcpShape(parsed)) {\n findings.push({\n severity: 'LOW',\n rule: 'unrecognized-mcp-target-shape',\n target: targetPath,\n detail: 'valid JSON but no \"mcpServers\" or \"servers\" object was found — reporting 0 servers',\n });\n }\n\n return { target: { path: targetPath, format, status: 'clean', servers: enumerateMcpServers(parsed) }, findings };\n}\n\n// ---------------------------------------------------------------------------\n// Opportunities: cross-root overlap clustering (Pro), evolve eligibility, foundation\n// ---------------------------------------------------------------------------\n\n/** Union-find connected-component clustering over overlap pairs already filtered to >= threshold by scanOverlapPaths. */\nfunction clusterOverlapPairs(pairs: OverlapPathPair[]): OverlapCluster[] {\n const parent = new Map<string, string>();\n\n function find(x: string): string {\n if (!parent.has(x)) parent.set(x, x);\n let root = x;\n while (parent.get(root) !== root) root = parent.get(root)!;\n let cur = x;\n while (parent.get(cur) !== root) {\n const next = parent.get(cur)!;\n parent.set(cur, root);\n cur = next;\n }\n return root;\n }\n\n function union(a: string, b: string): void {\n const ra = find(a);\n const rb = find(b);\n if (ra !== rb) parent.set(ra, rb);\n }\n\n for (const pair of pairs) union(pair.aPath, pair.bPath);\n\n const groups = new Map<string, { members: Set<string>; topScore: number }>();\n for (const pair of pairs) {\n const root = find(pair.aPath);\n const group = groups.get(root) ?? { members: new Set<string>(), topScore: 0 };\n group.members.add(pair.aPath);\n group.members.add(pair.bPath);\n group.topScore = Math.max(group.topScore, pair.score);\n groups.set(root, group);\n }\n\n return Array.from(groups.values())\n .map((g) => ({ members: toClusterMembers(g.members), topScore: g.topScore, suggestedVerb: suggestVerbCode(g.topScore) }))\n .sort((a, b) => b.topScore - a.topScore || a.members[0].localeCompare(b.members[0]));\n}\n\n/** Sorts and type-asserts a cluster's member-path set into the `[string, string, ...string[]]` tuple floor — safe because a cluster is only ever built from >= 1 scored pair (see `clusterOverlapPairs`), so it always has at least 2 distinct member paths. */\nfunction toClusterMembers(members: Set<string>): [string, string, ...string[]] {\n const sorted = Array.from(members).sort();\n if (sorted.length < 2) {\n throw new Error(`internal error: overlap cluster has fewer than 2 members (${sorted.length})`);\n }\n return sorted as [string, string, ...string[]];\n}\n\n// ---------------------------------------------------------------------------\n// Tooling readiness — port of skill_doctor.py (rhize-plugins). Read-only:\n// PATH lookup + environment-variable PRESENCE checks only. Never reads a\n// secret's value into the report or --json output — provider names and\n// booleans only.\n// ---------------------------------------------------------------------------\n\n/** Locate an executable on PATH without shelling out — mirrors gate/safety.ts's own (unexported) `which`, duplicated here rather than imported so audit.ts's tooling check stays a pure PATH/env read with no dependency on gate/safety.ts's internals. */\nfunction defaultWhich(cmd: string): string | undefined {\n const pathEnv = process.env.PATH ?? '';\n const dirs = pathEnv.split(delimiter).filter(Boolean);\n const exts = process.platform === 'win32' ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT').split(';') : [''];\n for (const dir of dirs) {\n for (const ext of exts) {\n const candidate = join(dir, cmd + ext);\n try {\n if (statSync(candidate).isFile()) return candidate;\n } catch {\n // not found in this dir, keep looking\n }\n }\n }\n return undefined;\n}\n\n/** Maps a `SKILLSPECTOR_PROVIDER` value to the env var holding its API key — mirrors skill_doctor.py's `keymap`. */\nconst SKILLSPECTOR_PROVIDER_KEYMAP: Record<string, string> = {\n openai: 'OPENAI_API_KEY',\n anthropic: 'ANTHROPIC_API_KEY',\n nv_build: 'NVIDIA_INFERENCE_KEY',\n};\n\n/**\n * Port of skill_doctor.py's readiness checks: `skillspector` on PATH (deep local safety scan),\n * `VERCEL_OIDC_TOKEN` presence (skills.sh discovery/partner audits — presence only, deliberately\n * narrower than skill_doctor.py's additional `.env`/`.env.local` file grep, since this is a\n * side-effect-free audit engine that otherwise never reads arbitrary project files), and the\n * optional SkillSpector LLM stage (provider name + whether its key env var is set — never the\n * key's value).\n */\nfunction checkTooling(which: (cmd: string) => string | undefined, env: NodeJS.ProcessEnv): AuditTooling {\n const skillspectorPath = which('skillspector');\n const skillspector: AuditToolingSkillspector = skillspectorPath\n ? { installed: true }\n : {\n installed: false,\n hint: 'NOT installed — github.com/NVIDIA/skillspector (git clone + make install; Python 3.12+)',\n };\n\n const tokenPresent = Boolean(env.VERCEL_OIDC_TOKEN);\n const skillsShToken: AuditToolingSkillsShToken = tokenPresent\n ? { present: true }\n : {\n present: false,\n hint: 'missing — enable OIDC Federation on your Vercel project, then `vercel link && vercel env pull`',\n };\n\n const provider = env.SKILLSPECTOR_PROVIDER;\n const providerKeyEnv = provider ? SKILLSPECTOR_PROVIDER_KEYMAP[provider] : undefined;\n const skillspectorLlm: AuditToolingSkillspectorLlm = {\n configured: Boolean(provider && providerKeyEnv && env[providerKeyEnv]),\n provider: provider || undefined,\n };\n\n return { skillspector, skillsShToken, skillspectorLlm };\n}\n\n// ---------------------------------------------------------------------------\n// Top-level engine\n// ---------------------------------------------------------------------------\n\n/**\n * Side-effect-free audit of every configured skills root and MCP target: inventory, structural +\n * safety hygiene findings, and consolidation/refinement opportunities. Never executes anything it\n * scans. Per-item failures (unreadable skill, missing MCP target, malformed JSON, ...) become\n * findings and never abort the run.\n */\nexport async function auditConfiguredSet(config: Config, options: AuditOptions = {}): Promise<AuditReport> {\n const now = options.now ?? (() => new Date());\n const oversizedThreshold = options.oversizedSkillTokens ?? OVERSIZED_SKILL_EST_TOKENS;\n const findings: AuditFinding[] = [];\n const notices: string[] = [];\n\n // --- Inventory: skill roots + skills ---\n const { candidates, rootMeta } = collectSkillCandidates(config.skillsRoots);\n const groups = dedupeSkillCandidates(candidates, findings);\n const unreadableSkillMdPaths = new Set<string>();\n const skills = groups.map((g) => profileAuditedSkill(g, findings, unreadableSkillMdPaths));\n\n const skillRoots: AuditedRoot[] = config.skillsRoots.map((root) => {\n const meta = rootMeta.get(root) ?? { exists: false };\n if (!meta.exists) {\n findings.push({ severity: 'MEDIUM', rule: 'missing-skills-root', target: root, detail: 'configured skills root does not exist on disk' });\n } else if (!meta.realPath) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'unreadable-skills-root',\n target: root,\n detail: 'could not resolve skills root to a real path (broken symlink or cycle)',\n });\n }\n return {\n root,\n realPath: meta.realPath ?? resolveConfiguredRoot(root),\n exists: meta.exists,\n skillCount: skills.filter((s) => s.root === root).length,\n };\n });\n\n // --- Hygiene: duplicate names across roots, oversized skills ---\n const byName = new Map<string, AuditedSkill[]>();\n for (const skill of skills) {\n const arr = byName.get(skill.name) ?? [];\n arr.push(skill);\n byName.set(skill.name, arr);\n }\n for (const [name, group] of byName) {\n if (group.length > 1) {\n // One finding per duplicate path (not one comma-joined finding) — keeps AuditFinding.target\n // single-path everywhere, matching every other finding this module emits.\n for (const skill of group) {\n const otherPaths = group.filter((g) => g.path !== skill.path).map((g) => g.path);\n findings.push({\n severity: 'MEDIUM',\n rule: 'duplicate-skill-name',\n target: skill.path,\n detail: `name \"${name}\" is duplicated — also found at ${otherPaths.join(', ')}`,\n });\n }\n }\n }\n for (const skill of skills) {\n if (skill.estTokens > oversizedThreshold) {\n findings.push({\n severity: 'LOW',\n rule: 'oversized-skill',\n target: skill.skillMdPath,\n detail: `est. ${skill.estTokens} tokens exceeds the ${oversizedThreshold}-token threshold — consider trimming or splitting`,\n // Deliberate '' (v0.12, see docs/accepted-findings-schema.md's context table): the detail\n // embeds a token count that changes on every edit, so \"this skill may exceed the\n // threshold\" is genuinely a rule+path identity here. An accepted oversized-skill finding\n // therefore stays suppressed as the skill keeps growing — a documented tradeoff, not a bug.\n context: '',\n });\n }\n }\n\n // --- Hygiene: per-skill safety scan (never aborts the run on a single failure) ---\n // perSkillFindings/skillScanFailed feed the evolve-eligible recompute below (v0.12): eligibility\n // is judged against ACTIVE findings only, per skill, so a skill whose only findings are accepted\n // becomes eligible. The AuditFinding objects stored here are the SAME objects pushed into\n // `findings` below (not copies) — the fingerprint attached during the global partition later is\n // therefore visible through this map too.\n const perSkillFindings = new Map<string, AuditFinding[]>();\n const skillScanFailed = new Set<string>();\n for (const skill of skills) {\n try {\n const result = await scanSafety(skill.path, config.strictness);\n const skillFindings = toAuditFindings(result.findings);\n findings.push(...skillFindings);\n perSkillFindings.set(skill.path, skillFindings);\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'unreadable-skill',\n target: skill.path,\n detail: `safety scan failed: ${(err as Error).message}`,\n });\n skillScanFailed.add(skill.path);\n }\n }\n\n // --- Inventory: MCP targets ---\n const mcpTargetPaths = config.mcpTargets ?? [];\n const mcpTargets: AuditedMcpTarget[] = [];\n for (const targetPath of mcpTargetPaths) {\n const { target, findings: targetFindings } = auditMcpTarget(targetPath, notices);\n mcpTargets.push(target);\n findings.push(...targetFindings);\n }\n\n // --- Opportunities: cross-root overlap clusters (Pro) ---\n const overlapStatus = proFeatureStatus('installed-set overlap analysis');\n let overlapClusters: OverlapCluster[] = [];\n if (overlapStatus.enabled) {\n if (overlapStatus.notice) notices.push(overlapStatus.notice);\n try {\n // Excludes any SKILL.md path already known unreadable (see profileAuditedSkill) —\n // scanOverlapPaths's readFrontmatter does an unguarded readFileSync, so feeding it an\n // unreadable path would otherwise throw and abort the whole audit with no report.\n const readableSkillMdPaths = skills.map((s) => s.skillMdPath).filter((p) => !unreadableSkillMdPaths.has(p));\n const pairs = scanOverlapPaths(readableSkillMdPaths, options.overlapThreshold);\n overlapClusters = clusterOverlapPairs(pairs);\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'overlap-scan-failed',\n target: 'installed-set overlap analysis',\n detail: `cross-root overlap scan failed: ${(err as Error).message}`,\n });\n notices.push(`cross-root overlap scan failed — overlap clusters unavailable this run: ${(err as Error).message}`);\n overlapClusters = [];\n }\n } else {\n notices.push(overlapStatus.notice ?? 'Pro feature (installed-set overlap analysis) locked.');\n }\n\n // --- Accepted-findings partition (v0.12) ---\n // Computed over EVERY finding collected so far (skills + MCP targets + overlap-scan-failed) —\n // fingerprints every finding, then splits into active (report.hygiene) vs accepted\n // (report.accepted). Mutates each AuditFinding in place to attach `.fingerprint` — the same\n // objects referenced by `perSkillFindings` above, so evolve-eligibility below sees it too.\n // Nothing here may influence a gate verdict (see src/findings.ts's module doc) — this is\n // classification for the audit REPORT only.\n const acceptedRecords = options.accepted ?? [];\n const acceptedByFingerprint = new Map(acceptedRecords.map((r) => [r.fingerprint, r]));\n const matchedFingerprints = new Set<string>();\n const activeFindings: AuditFinding[] = [];\n const acceptedEntries: AcceptedFindingEntry[] = [];\n\n for (const f of findings) {\n const context = f.context ?? f.detail;\n const fingerprint = fingerprintFinding({ severity: f.severity, rule: f.rule, target: f.target, context });\n f.fingerprint = fingerprint;\n const record = acceptedByFingerprint.get(fingerprint);\n if (record) {\n matchedFingerprints.add(fingerprint);\n acceptedEntries.push({ finding: f, reason: record.reason, acceptedAt: record.acceptedAt });\n } else {\n activeFindings.push(f);\n }\n }\n\n const unmatchedAcceptedCount = acceptedRecords.filter((r) => !matchedFingerprints.has(r.fingerprint)).length;\n if (unmatchedAcceptedCount > 0) {\n notices.push(\n `${unmatchedAcceptedCount} accepted finding(s) did not match anything this run (resolved, or content changed) — see \\`skill-forge finding list --stale\\``\n );\n }\n\n const activeFindingSet = new Set(activeFindings);\n\n // --- Opportunities: evolve-eligible (structurally valid + a clean ACTIVE-findings verdict) ---\n // v0.12 semantic change: judged against ACTIVE findings only (not scanSafety's raw verdict), so a\n // skill whose only findings are accepted becomes eligible. A scan failure (skillScanFailed) is\n // never eligible regardless of strictness — that guard predates and is unrelated to acceptance.\n const evolveEligible: EvolveCandidate[] = skills\n .filter((s) => {\n if (!s.validFrontmatter || skillScanFailed.has(s.path)) return false;\n const skillFindings = perSkillFindings.get(s.path) ?? [];\n const activeSkillFindings = skillFindings.filter((f) => activeFindingSet.has(f));\n return activeVerdictFor(activeSkillFindings, config.strictness) === 'pass';\n })\n .map((s) => ({ name: s.name, path: s.path }));\n\n // --- Opportunities: foundation skill ---\n const foundation: FoundationOpportunity = config.foundationSkillPath\n ? {\n available: false,\n existingPath: config.foundationSkillPath,\n message: `a business-foundation skill already exists at ${config.foundationSkillPath}`,\n }\n : {\n available: true,\n message: 'no business-foundation skill yet — `skill-forge audit --foundation` can scaffold one to ground future skills in your business profile',\n };\n\n // --- Summary ---\n // Active-only (v0.12) — an accepted finding is excluded from these counts (and therefore from\n // routine --fail-on and the add/scan advisory), but never disappears: it's always in `accepted`.\n const findingCounts: Record<Severity, number> = { LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0 };\n for (const f of activeFindings) findingCounts[f.severity]++;\n\n const summary: AuditSummary = {\n rootCount: skillRoots.length,\n skillCount: skills.length,\n mcpTargetCount: mcpTargets.length,\n findingCounts,\n overlapClusterCount: overlapClusters.length,\n evolveEligibleCount: evolveEligible.length,\n acceptedCount: acceptedEntries.length,\n };\n\n // --- Tooling readiness (skill_doctor.py port) ---\n const tooling = checkTooling(options.which ?? defaultWhich, options.env ?? process.env);\n\n return {\n schemaVersion: 1,\n generatedAt: now().toISOString(),\n inventory: { skillRoots, skills, mcpTargets },\n hygiene: activeFindings,\n accepted: acceptedEntries,\n opportunities: { overlapClusters, evolveEligible, foundation, overlapLocked: !overlapStatus.enabled },\n tooling,\n notices,\n summary,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Markdown report rendering\n// ---------------------------------------------------------------------------\n\nfunction bulletList(items: string[], emptyPlaceholder: string): string[] {\n return items.length > 0 ? items.map((i) => `- ${i}`) : [`- ${emptyPlaceholder}`];\n}\n\n/**\n * Strips C0 (0x00-0x1F) and C1 (0x7F-0x9F) control characters from an untrusted string before\n * it's interpolated into markdown report output — these are single-line interpolations, so\n * nothing needs to replace what's stripped. Guards against e.g. an installed skill's name or an\n * MCP server's name/command/package-spec carrying an embedded ANSI escape sequence that would\n * otherwise render raw into a terminal later `cat`ing the report. JSON output (`--json`) is\n * untouched — `JSON.stringify` already escapes control characters. Exported for reuse by every\n * command that renders untrusted inventory/ledger/API strings into human-readable output\n * (organize, find, watch, ingest).\n */\nexport function sanitizeForMarkdown(s: string): string {\n // eslint-disable-next-line no-control-regex -- intentional: stripping C0/C1 control chars\n return s.replace(/[\\x00-\\x1F\\x7F-\\x9F]/g, '');\n}\n\n/** Comma-joins the defined/truthy parts of a list of optional strings — shared by the skills and MCP-targets sections' inline detail summaries. */\nfunction joinDefined(parts: (string | undefined)[]): string {\n return parts.filter((v): v is string => Boolean(v)).join(', ');\n}\n\nfunction renderRootsSection(roots: AuditedRoot[]): string[] {\n return [\n '### Skill roots',\n '',\n ...bulletList(\n roots.map((r) => `${r.root}${r.exists ? '' : ' (MISSING)'} — ${r.skillCount} skill${r.skillCount === 1 ? '' : 's'}`),\n '(none configured)'\n ),\n ];\n}\n\nfunction renderSkillsSection(skills: AuditedSkill[]): string[] {\n return [\n '### Skills',\n '',\n ...bulletList(\n skills.map((s) => {\n const flags = joinDefined([\n s.validFrontmatter ? undefined : 'INVALID FRONTMATTER',\n s.aliasPaths.length > 0 ? `${s.aliasPaths.length} alias path(s)` : undefined,\n ]);\n return `${sanitizeForMarkdown(s.name)} — ${s.path} (est. ${s.estTokens} tokens)${flags ? ` [${flags}]` : ''}`;\n }),\n '(none found)'\n ),\n ];\n}\n\nfunction renderMcpTargetsSection(targets: AuditedMcpTarget[]): string[] {\n const lines = ['### MCP targets', ''];\n if (targets.length === 0) {\n lines.push('- (none configured)');\n return lines;\n }\n for (const t of targets) {\n lines.push(`- ${t.path} — ${t.format}, ${t.status}`);\n for (const server of t.servers) {\n const detail = joinDefined([\n server.commandBasename ? sanitizeForMarkdown(server.commandBasename) : undefined,\n server.packageSpec ? sanitizeForMarkdown(server.packageSpec) : undefined,\n `${server.argCount} arg${server.argCount === 1 ? '' : 's'}`,\n `${server.envCount} env var${server.envCount === 1 ? '' : 's'}`,\n ]);\n lines.push(` - ${sanitizeForMarkdown(server.name)} (${detail})`);\n }\n }\n return lines;\n}\n\n/** First `FINGERPRINT_DISPLAY_LENGTH` hex chars of a finding's fingerprint, or `'unknown'` for a hand-built `AuditFinding` from before v0.12 (fingerprint is optional). */\nfunction displayFingerprint(f: AuditFinding): string {\n return f.fingerprint ? f.fingerprint.slice(0, FINGERPRINT_DISPLAY_LENGTH) : 'unknown';\n}\n\n/** ACTIVE findings only (v0.12) — each suffixed with its fingerprint-12 so it can be passed to `skill-forge finding accept <fp-prefix>`. */\nfunction renderFindingsSection(findings: AuditFinding[]): string[] {\n return [\n '## Hygiene findings',\n '',\n ...bulletList(\n findings.map(\n (f) => `[${f.severity}] ${f.rule} — ${sanitizeForMarkdown(f.target)}: ${sanitizeForMarkdown(f.detail)} (fp ${displayFingerprint(f)})`\n ),\n '(none — clean)'\n ),\n ];\n}\n\n/** Permanent record of every finding a human has accepted (v0.12) — reason + date, never dropped from the report even once it stops matching a live finding (see the unmatched-ack notice for that case). */\nfunction renderAcceptedFindingsSection(entries: AcceptedFindingEntry[]): string[] {\n return [\n '## Accepted findings',\n '',\n ...bulletList(\n entries.map(\n (e) =>\n `[${e.finding.severity}] ${e.finding.rule} — ${sanitizeForMarkdown(e.finding.target)}: ${sanitizeForMarkdown(e.reason)} (accepted ${sanitizeForMarkdown(e.acceptedAt.slice(0, 10))})`\n ),\n '(none)'\n ),\n ];\n}\n\nfunction renderOpportunitiesSection(report: AuditReport): string[] {\n const { overlapClusters, evolveEligible, foundation } = report.opportunities;\n return [\n '## Opportunities',\n '',\n '### Overlap clusters',\n '',\n ...bulletList(\n overlapClusters.map((c) => `top score ${c.topScore} — ${c.suggestedVerb} — ${c.members.join(', ')}`),\n '(none — either no cross-root overlap found, or the Pro feature is locked, see Notices)'\n ),\n '',\n '### Evolve-eligible',\n '',\n ...bulletList(\n evolveEligible.map((c) => `${sanitizeForMarkdown(c.name)} — ${c.path}`),\n '(none)'\n ),\n '',\n '### Foundation skill',\n '',\n `- ${foundation.message}`,\n ];\n}\n\nfunction renderToolingSection(tooling: AuditTooling | undefined): string[] {\n const lines = ['## Tooling', ''];\n if (!tooling) {\n lines.push('- (not available in this report)');\n return lines;\n }\n\n lines.push(`- SkillSpector CLI: ${tooling.skillspector.installed ? 'installed' : 'NOT installed'}`);\n if (tooling.skillspector.hint) lines.push(` - ${tooling.skillspector.hint}`);\n\n lines.push(`- skills.sh auth (VERCEL_OIDC_TOKEN): ${tooling.skillsShToken.present ? 'present' : 'missing'}`);\n if (tooling.skillsShToken.hint) lines.push(` - ${tooling.skillsShToken.hint}`);\n\n const llm = tooling.skillspectorLlm;\n if (llm.configured) {\n lines.push(`- SkillSpector LLM stage (optional): configured (provider: ${sanitizeForMarkdown(llm.provider ?? '')})`);\n } else if (llm.provider) {\n lines.push(`- SkillSpector LLM stage (optional): not configured — SKILLSPECTOR_PROVIDER is \"${sanitizeForMarkdown(llm.provider)}\" but its API key env var is not set`);\n } else {\n lines.push(\n '- SkillSpector LLM stage (optional): not set — static scan works without it (use --no-llm); ' +\n 'set SKILLSPECTOR_PROVIDER + its API key for semantic analysis'\n );\n }\n\n return lines;\n}\n\n/** Renders an `AuditReport` as a standalone markdown document — the file `skill-forge audit` writes under `~/.skill-forge/reports/`. */\nexport function renderAuditMarkdown(report: AuditReport): string {\n const { summary } = report;\n const lines: string[] = [\n `# skill-forge audit — ${report.generatedAt}`,\n '',\n '## Summary',\n '',\n `- Skill roots: ${summary.rootCount}`,\n `- Skills: ${summary.skillCount}`,\n `- MCP targets: ${summary.mcpTargetCount}`,\n `- Findings: ${summary.findingCounts.CRITICAL} CRITICAL, ${summary.findingCounts.HIGH} HIGH, ${summary.findingCounts.MEDIUM} MEDIUM, ${summary.findingCounts.LOW} LOW`,\n `- Accepted: ${summary.acceptedCount} (suppressed from the counts above; see the Accepted findings section)`,\n `- Overlap clusters: ${summary.overlapClusterCount}`,\n `- Evolve-eligible skills: ${summary.evolveEligibleCount}`,\n '',\n '## Inventory',\n '',\n ...renderRootsSection(report.inventory.skillRoots),\n '',\n ...renderSkillsSection(report.inventory.skills),\n '',\n ...renderMcpTargetsSection(report.inventory.mcpTargets),\n '',\n ...renderFindingsSection(report.hygiene),\n '',\n ...renderAcceptedFindingsSection(report.accepted),\n '',\n ...renderOpportunitiesSection(report),\n '',\n ...renderToolingSection(report.tooling),\n '',\n '## Notices',\n '',\n ...bulletList(report.notices, '(none)'),\n ];\n return lines.join('\\n') + '\\n';\n}\n\n// ---------------------------------------------------------------------------\n// Foundation skill scaffold — the one write path in this module\n// ---------------------------------------------------------------------------\n\nfunction renderFoundationSkillMd(profile: BusinessProfile): string {\n const description = profile.businessName\n ? `Business-foundation context for ${profile.businessName} — grounds new and existing skills in its audiences, workflows, and constraints. Generated by skill-forge audit.`\n : 'Business-foundation context for this business — grounds new and existing skills in its audiences, workflows, and constraints. Generated by skill-forge audit.';\n\n const lines: (string | undefined)[] = [\n '---',\n 'name: business-foundation',\n `description: ${description}`,\n '---',\n '',\n `# Business Foundation${profile.businessName ? `: ${profile.businessName}` : ''}`,\n '',\n profile.industry ? `**Industry:** ${profile.industry}` : undefined,\n '',\n '## Audiences',\n '',\n ...bulletList(profile.audiences, '(none captured yet)'),\n '',\n '## Workflows',\n '',\n ...bulletList(profile.workflows, '(none captured yet)'),\n '',\n '## Constraints',\n '',\n ...bulletList(profile.constraints, '(none captured yet)'),\n '',\n '## Integration guidance',\n '',\n 'New and existing skills in this set should align with the business context above:',\n '- Match the audiences, terminology, and tone described here.',\n '- Respect the workflows and constraints listed above when proposing changes.',\n '- When curating or evolving skills (`skill-forge evolve`, `skill-forge add`), treat this file as ground ' +\n 'truth for what the business actually needs — update it by re-running `skill-forge audit` rather than ' +\n 'editing it out of band, so it stays a single source of truth.',\n ];\n\n return lines.filter((l): l is string => l !== undefined).join('\\n') + '\\n';\n}\n\n/**\n * Scaffolds `<targetRoot>/business-foundation/SKILL.md` from a captured business profile.\n * `targetRoot` must be one of `config.skillsRoots` (containment check) — this is the one write\n * path in this module, gated entirely behind the caller's explicit opt-in (see\n * src/commands/audit.ts's `--foundation` flag / interactive confirm). Refuses an existing\n * dir/file or a symlinked destination; writes atomically (builds in a sibling temp dir, then\n * renames the whole dir into place, so a failure never leaves a partial `business-foundation/`\n * visible); runs `scanSafety` on the generated content before the rename and refuses to place it\n * on a block verdict. Returns the canonical path — the caller is responsible for persisting it to\n * `config.foundationSkillPath`. Deliberately async (unlike the plan's originally sketched sync\n * signature) because `scanSafety`, which this function must run before placement, is itself\n * async — there is no synchronous way to honor \"scan before write\" here.\n */\nexport async function scaffoldFoundationSkill(config: Config, profile: BusinessProfile, targetRoot: string): Promise<string> {\n const resolvedRoot = resolveConfiguredRoot(targetRoot);\n const configuredRoots = config.skillsRoots.map(resolveConfiguredRoot);\n if (!configuredRoots.includes(resolvedRoot)) {\n throw new UserError(`refusing to scaffold a foundation skill under ${resolvedRoot}: not one of the configured skillsRoots`);\n }\n if (!isDir(resolvedRoot)) {\n throw new UserError(`skills root does not exist: ${resolvedRoot}`);\n }\n\n const destDir = join(resolvedRoot, 'business-foundation');\n\n let destLstat;\n try {\n destLstat = lstatSync(destDir);\n } catch {\n destLstat = undefined;\n }\n if (destLstat?.isSymbolicLink()) {\n throw new UserError(`refusing to scaffold a foundation skill at ${destDir}: destination is a symlink`);\n }\n if (destLstat) {\n throw new UserError(`refusing to scaffold a foundation skill at ${destDir}: already exists`);\n }\n\n const content = renderFoundationSkillMd(profile);\n const tempDir = mkdtempSync(join(resolvedRoot, '.business-foundation-'));\n try {\n writeFileSync(join(tempDir, 'SKILL.md'), content, { flag: 'wx', mode: 0o600 });\n\n const safety = await scanSafety(tempDir, config.strictness);\n if (safety.verdict === 'block') {\n throw new UserError(\n `refusing to scaffold a foundation skill: the generated SKILL.md was flagged by the safety scan (${safety.findings\n .map(formatSafetyFinding)\n .join('; ')})`\n );\n }\n\n renameSync(tempDir, destDir);\n } catch (err) {\n rmSync(tempDir, { recursive: true, force: true });\n throw err;\n }\n\n // The skill is already on disk at this point (renameSync above succeeded) — a provenance\n // failure here must not surface as a raw, unattributed error, and must not silently leave the\n // caller believing nothing happened (mirrors the v0.7 pattern in src/evolve.ts's adopt path).\n try {\n appendProvenance(\n {\n name: 'business-foundation',\n source: 'skill-forge audit (business profile)',\n verb: 'DEFER',\n target: destDir,\n took: 'scaffolded a new business-foundation skill from the captured business profile',\n notes: 'scaffolded by skill-forge audit',\n },\n resolvedRoot\n );\n } catch (err) {\n throw new UserError(\n `the business-foundation skill was created at ${destDir}, but recording the provenance entry failed: ${(err as Error).message}`\n );\n }\n\n return destDir;\n}\n","import { statSync } from 'node:fs';\nimport { execFile } from 'node:child_process';\nimport { delimiter, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { DEFAULT_STRICTNESS, type Strictness } from '../config.js';\nimport { readTextFile, verdictFor, walk } from './scanShared.js';\nimport type { SafetyFinding, SafetyResult, Severity } from './scanShared.js';\n\nexport type { SafetyFinding, SafetyResult, Severity, Verdict } from './scanShared.js';\n\nconst execFileAsync = promisify(execFile);\n\ninterface DenyRule {\n rule: string;\n severity: Severity;\n pattern: RegExp;\n message: string;\n /**\n * Optional second-stage judgment on a line the pattern already matched. A regex can say \"this\n * line contains `rm -rf`\"; only argument analysis can say whether that is `rm -rf dist` or\n * `rm -rf /`. Return `null` to suppress the finding entirely, or an overriding severity/message.\n *\n * Exists because pattern-only rules have poor precision on real content, and a noisy HIGH costs\n * more than it earns — people stop reading the list, including the one line that mattered.\n * `destructive-rm-rf` is the first user; add others as the same problem shows up.\n */\n refine?: (line: string) => { severity: Severity; message: string } | null;\n}\n\n/**\n * Target of an `rm -rf`, if one can be parsed off the line: the first non-flag argument, quoted\n * or bare. `[^\\s;|&)\"'`]+` stops at shell metacharacters so `rm -rf dist && echo ok` yields `dist`.\n */\nconst RM_RF_TARGET_RE = /\\brm\\s+(?:-[a-z]+\\s+)+(\"[^\"]*\"|'[^']*'|[^\\s;|&)\"'`]+)/i;\n\n/**\n * Judges an `rm -rf` by its target. Measured against a real installed skill set (2026-08-09),\n * every match was `rm -rf dist` or `rm -rf .next` in a package.json script — a 100% false-positive\n * rate for a HIGH finding.\n *\n * Suppressed: a relative path with no traversal and no shell expansion — the normal build-artifact\n * clean step. Still HIGH: root/home targets, bare globs, `..` traversal, and any `$` expansion\n * (an unset variable turns `rm -rf $DIR/` into something far worse). MEDIUM: a deep absolute path,\n * which is outside any project dir and worth a look without being alarming.\n */\nfunction refineRmRf(line: string): { severity: Severity; message: string } | null {\n const match = line.match(RM_RF_TARGET_RE);\n if (!match) {\n return { severity: 'HIGH', message: 'recursive force-delete (rm -rf) with an unparseable target — verify the path is scoped' };\n }\n const target = match[1].replace(/^[\"']|[\"']$/g, '').trim();\n\n if (target.includes('$') || target.includes('`')) {\n return {\n severity: 'HIGH',\n message: `recursive force-delete of \"${target}\" — the target is a shell expansion; if it is empty or unset this can delete far more than intended`,\n };\n }\n if (target === '/' || target === '/*' || target === '*' || target === '.' || target === '..' || target === '~' || target === '~/') {\n return { severity: 'HIGH', message: `recursive force-delete of \"${target}\" — this targets the filesystem root, the home directory, or everything in the working directory` };\n }\n if (target.startsWith('~')) {\n return { severity: 'HIGH', message: `recursive force-delete of \"${target}\" — targets a path inside the home directory, outside any project` };\n }\n if (target.split('/').includes('..')) {\n return { severity: 'HIGH', message: `recursive force-delete of \"${target}\" — the target escapes the current directory via \"..\"` };\n }\n if (target.startsWith('/')) {\n const segments = target.split('/').filter(Boolean);\n if (segments.length < 3) {\n return { severity: 'HIGH', message: `recursive force-delete of \"${target}\" — a shallow absolute path like this is a system directory` };\n }\n return { severity: 'MEDIUM', message: `recursive force-delete of \"${target}\" — an absolute path outside any project directory` };\n }\n // Relative, no traversal, no expansion: `rm -rf dist` / `rm -rf .next` / `rm -rf build/cache`.\n return null;\n}\n\nconst DENY_RULES: DenyRule[] = [\n {\n rule: 'curl-pipe-shell',\n severity: 'CRITICAL',\n pattern: /\\b(curl|wget)\\b[^\\n|]*\\|\\s*(sudo\\s+)?(bash|sh|zsh)\\b/i,\n message: 'downloads a remote script and pipes it directly into a shell',\n },\n {\n rule: 'base64-obfuscated-exec',\n severity: 'CRITICAL',\n pattern:\n /(base64\\s+(-d|--decode)\\b[^\\n|]*\\|\\s*(bash|sh|zsh|python3?)\\b)|(\\bexec\\s*\\(\\s*(base64\\.)?b64decode\\()|(\\beval\\s*\\(\\s*Buffer\\.from\\([^)]*['\"]base64['\"])/i,\n message: 'decodes a base64 payload and executes it (obfuscated execution)',\n },\n {\n rule: 'reverse-shell',\n severity: 'CRITICAL',\n pattern: /(\\/dev\\/tcp\\/\\S+\\/\\d+)|(\\bnc\\s+-e\\s+\\/bin\\/(ba)?sh\\b)|(bash\\s+-i\\s*>&\\s*\\/dev\\/tcp)/i,\n message: 'opens a reverse shell back to a remote host',\n },\n {\n rule: 'destructive-rm-rf',\n severity: 'HIGH',\n pattern: /\\brm\\s+-[a-z]*r[a-z]*f[a-z]*\\b|\\brm\\s+-[a-z]*f[a-z]*r[a-z]*\\b/i,\n message: 'recursive force-delete (rm -rf) — verify the target path is scoped',\n refine: refineRmRf,\n },\n {\n rule: 'credential-file-access',\n severity: 'HIGH',\n pattern:\n /(~|\\$HOME)?\\/\\.(ssh\\/id_(rsa|ed25519|ecdsa)|aws\\/credentials|npmrc|netrc|gnupg|docker\\/config\\.json)\\b|\\bid_rsa\\b|\\.env\\b.*\\bcat\\b|\\bcat\\b.*\\.env\\b/i,\n message: 'reads a credential/secrets file (SSH keys, cloud credentials, .env)',\n },\n {\n rule: 'credential-exfil',\n severity: 'HIGH',\n pattern: /\\b(curl|wget|fetch)\\b[^\\n]*(--data|--data-binary|-F|-d)\\b[^\\n]*(\\.ssh|\\.aws|\\.env|credentials|id_rsa)/i,\n message: 'uploads credential/secret file contents to a remote endpoint',\n },\n {\n rule: 'dynamic-eval',\n severity: 'MEDIUM',\n pattern: /\\beval\\s*\\(|\\bexec\\s*\\(\\s*compile\\s*\\(|\\bnew\\s+Function\\s*\\(/,\n message: 'evaluates dynamically constructed code',\n },\n {\n rule: 'shell-true-subprocess',\n severity: 'MEDIUM',\n pattern: /subprocess\\.(Popen|run|call)\\([^)]*shell\\s*=\\s*True/,\n message: 'runs a subprocess with shell=True (shell-injection risk)',\n },\n {\n rule: 'persistence-cron-rc',\n severity: 'MEDIUM',\n pattern: />>\\s*~?\\/?\\.(bashrc|zshrc|bash_profile|profile)\\b|\\bcrontab\\s+-/i,\n message: 'modifies shell startup files or crontab (persistence)',\n },\n {\n rule: 'sudo-usage',\n severity: 'LOW',\n pattern: /\\bsudo\\s+/,\n message: 'invokes sudo — review for unnecessary privilege escalation',\n },\n];\n\nfunction scanStaticRules(targetPath: string): SafetyFinding[] {\n const findings: SafetyFinding[] = [];\n let root;\n try {\n root = statSync(targetPath);\n } catch {\n return findings;\n }\n const files = root.isDirectory() ? Array.from(walk(targetPath)) : [targetPath];\n\n for (const file of files) {\n const content = readTextFile(file);\n if (content === undefined) continue;\n const lines = content.split('\\n');\n for (const rule of DENY_RULES) {\n for (let i = 0; i < lines.length; i++) {\n if (rule.pattern.test(lines[i])) {\n const refined = rule.refine ? rule.refine(lines[i]) : { severity: rule.severity, message: rule.message };\n if (!refined) continue; // second stage judged this match benign\n findings.push({\n severity: refined.severity,\n rule: rule.rule,\n file,\n detail: `line ${i + 1}: ${refined.message}`,\n context: lines[i].trim(),\n });\n }\n }\n }\n }\n return findings;\n}\n\n/** Locate an executable on PATH without shelling out (no `which`/`command -v` dependency). */\nfunction which(cmd: string): string | undefined {\n const pathEnv = process.env.PATH ?? '';\n const dirs = pathEnv.split(delimiter).filter(Boolean);\n const exts = process.platform === 'win32' ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT').split(';') : [''];\n for (const dir of dirs) {\n for (const ext of exts) {\n const candidate = join(dir, cmd + ext);\n try {\n if (statSync(candidate).isFile()) return candidate;\n } catch {\n // not found in this dir, keep looking\n }\n }\n }\n return undefined;\n}\n\ninterface SkillSpectorFinding {\n severity?: string;\n rule_id?: string;\n message?: string;\n}\n\ninterface SkillSpectorOutput {\n filtered_findings?: SkillSpectorFinding[];\n findings?: SkillSpectorFinding[];\n}\n\nfunction normalizeSeverity(sev: string | undefined): Severity {\n const s = (sev ?? '').toUpperCase();\n if (s === 'LOW' || s === 'MEDIUM' || s === 'HIGH' || s === 'CRITICAL') return s;\n return 'MEDIUM'; // unrecognized external severity: treat cautiously, don't silently trust as safe\n}\n\n/**\n * Mirrors skill_safety.py's `skillspector scan <target> --format json --no-llm`\n * invocation. Skipped cleanly (no findings, no error) when SkillSpector isn't\n * installed or its output can't be parsed — the built-in ruleset above is the\n * FREE-tier baseline; SkillSpector is additive when present.\n */\nasync function scanWithSkillSpector(targetPath: string): Promise<SafetyFinding[]> {\n const bin = which('skillspector');\n if (!bin) return [];\n\n try {\n const { stdout } = await execFileAsync(bin, ['scan', targetPath, '--format', 'json', '--no-llm'], {\n timeout: 60_000,\n maxBuffer: 10 * 1024 * 1024,\n });\n const parsed = JSON.parse(stdout) as SkillSpectorOutput;\n const raw = parsed.filtered_findings ?? parsed.findings ?? [];\n return raw.map((f) => ({\n severity: normalizeSeverity(f.severity),\n rule: f.rule_id ?? 'skillspector',\n file: targetPath,\n detail: f.message ?? '',\n }));\n } catch {\n return [];\n }\n}\n\n/**\n * Scans a quarantined skill (file or directory) for unsafe patterns: the\n * built-in deny-pattern ruleset always runs, and an installed SkillSpector\n * (https://github.com/NVIDIA/SkillSpector) is shelled out to and merged in\n * when present on PATH. `strictness` (default `DEFAULT_STRICTNESS` from\n * `config.ts`) selects which severities produce a `block` verdict —\n * see `BLOCK_SEVERITIES_BY_STRICTNESS` in `./scanShared.ts`.\n */\nexport async function scanSafety(targetPath: string, strictness: Strictness = DEFAULT_STRICTNESS): Promise<SafetyResult> {\n const findings = [...scanStaticRules(targetPath), ...(await scanWithSkillSpector(targetPath))];\n return { findings, verdict: verdictFor(findings, strictness) };\n}\n","import { statSync } from 'node:fs';\nimport { basename, extname } from 'node:path';\nimport { DEFAULT_STRICTNESS, type Strictness } from '../config.js';\nimport { scanSafety } from './safety.js';\nimport {\n collectNpxPackageSpecs,\n isNpxCommand,\n isPinnedPackageSpec,\n packageSpecBase,\n readTextFile,\n verdictFor,\n walk,\n looksHighEntropy,\n looksStructuredNonSecret,\n} from './scanShared.js';\nimport type { SafetyFinding, SafetyResult } from './scanShared.js';\n\n/**\n * MCP-specific findings, composed on top of the base deny-ruleset scan\n * (`scanSafety`). curl|bash is deliberately NOT re-implemented here — the\n * base scan's `curl-pipe-shell` rule already covers it; `mcpScan` merges its\n * findings in.\n *\n * Detects:\n * - mcp-inline-credential-value (HIGH): a *_KEY/*_TOKEN/*_SECRET-shaped env\n * var (or any high-entropy value) assigned a real-looking value instead of\n * an empty string / ${VAR} placeholder, in an MCP server's `env` block or\n * a config/example text file. Values that are structurally non-secret — ISO\n * timestamps, credential-free URLs, filesystem paths — are exempt; see\n * `looksStructuredNonSecret` in gate/scanShared.ts for why.\n * - mcp-credential-file-path (LOW): a credential-SHAPED key whose value is a\n * filesystem path rather than an inline secret (e.g.\n * GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json). That is the documented,\n * correct way to configure those variables, so it is informational: the value\n * leaks nothing, but the file it points at is worth protecting.\n * - mcp-unpinned-npx (MEDIUM with -y, LOW without — v0.12): an unpinned npx\n * launch spec (structured `{command,args}` or shell text). `npx -y <pkg>` /\n * `npx --yes <pkg>` without a pinned `@<version>` is MEDIUM (silent\n * install); `npx <pkg>` without `-y` and without a pin is LOW — npx\n * prompts before the FIRST install, but once cached the tag still floats\n * to whatever the registry currently points at, so it's still worth\n * pinning. A pinned spec is never a finding either way.\n * - mcp-dangerous-flag (HIGH): `--dangerously-*` or `--no-sandbox` launch args.\n * - mcp-filesystem-root-arg (MEDIUM): a launch arg that is exactly \"/\" or \"~\".\n */\n\nfunction isConfigOrExampleFile(file: string): boolean {\n const base = basename(file).toLowerCase();\n if (base.startsWith('.env')) return true;\n if (/\\.(json|ya?ml)$/.test(base)) return true;\n return /(example|sample|config)/.test(base);\n}\n\nconst CREDENTIAL_KEY_RE = /(API[_-]?KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL)/i;\nfunction looksCredentialKey(name: string): boolean {\n return CREDENTIAL_KEY_RE.test(name);\n}\n\nconst PLACEHOLDER_VALUE_RE =\n /^(\\$\\{.*\\}|\\$[A-Z0-9_]+|your[_-]?.*|<.*>|xxx+|changeme|replace[_-]?me|example|placeholder|todo|fill[_-]?me|none|null|redacted|\\.\\.\\.)$/i;\nfunction isPlaceholderValue(value: string): boolean {\n const v = value.trim();\n if (v === '') return true;\n if (v.startsWith('process.env')) return true;\n return PLACEHOLDER_VALUE_RE.test(v);\n}\n\n/** Length/charset heuristic for \"looks like a real secret\" — no whitespace, mixed charset, low repetition. */\n\ninterface ServerLikeNode {\n command?: unknown;\n args?: unknown;\n env?: unknown;\n}\n\nfunction collectServerNodes(node: unknown, out: ServerLikeNode[]): void {\n if (Array.isArray(node)) {\n for (const item of node) collectServerNodes(item, out);\n return;\n }\n if (node && typeof node === 'object') {\n const obj = node as Record<string, unknown>;\n if (typeof obj.command === 'string' || Array.isArray(obj.args) || (obj.env && typeof obj.env === 'object')) {\n out.push(obj);\n }\n for (const value of Object.values(obj)) collectServerNodes(value, out);\n }\n}\n\nfunction tryParseJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/** Structural scan of a parsed MCP-config-shaped JSON document (mcpServers/servers entries, nested or not). */\nfunction scanJsonConfig(file: string, parsed: unknown): SafetyFinding[] {\n const findings: SafetyFinding[] = [];\n const nodes: ServerLikeNode[] = [];\n collectServerNodes(parsed, nodes);\n\n for (const node of nodes) {\n const command = typeof node.command === 'string' ? node.command : undefined;\n const args = Array.isArray(node.args) ? node.args.filter((a): a is string => typeof a === 'string') : [];\n\n if (command && isNpxCommand(command)) {\n // v0.12: -y/--yes presence no longer gates whether the rule runs at all — it now only\n // selects severity/message (see the module doc comment). collectNpxPackageSpecs() parses\n // the way npx itself does, including every repeatable -p/--package value, not just the\n // first non-flag token (see its own doc comment in scanShared.ts), and now runs\n // unconditionally.\n const hasYes = args.includes('-y') || args.includes('--yes');\n const { packageFlagSpecs, positionalSpec } = collectNpxPackageSpecs(args);\n const allSpecs = positionalSpec ? [...packageFlagSpecs, positionalSpec] : packageFlagSpecs;\n for (const pkgSpec of allSpecs) {\n if (isPinnedPackageSpec(pkgSpec)) continue;\n if (hasYes) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'mcp-unpinned-npx',\n file,\n detail: `npx -y ${pkgSpec} is unpinned — pin an exact version (${packageSpecBase(pkgSpec)}@<version>) so the resolved server code can't change silently`,\n context: pkgSpec,\n });\n } else {\n findings.push({\n severity: 'LOW',\n rule: 'mcp-unpinned-npx',\n file,\n detail: `npx ${pkgSpec} is unpinned — without -y npx prompts before first install, but once cached the tag still floats to whatever the registry points at; pin ${packageSpecBase(pkgSpec)}@<version>`,\n context: pkgSpec,\n });\n }\n }\n }\n\n for (const a of args) {\n if (/^--dangerously-/i.test(a) || a === '--no-sandbox') {\n findings.push({\n severity: 'HIGH',\n rule: 'mcp-dangerous-flag',\n file,\n detail: `launch arg ${a} disables a safety guard`,\n context: a,\n });\n }\n if (a === '/' || a === '~') {\n findings.push({\n severity: 'MEDIUM',\n rule: 'mcp-filesystem-root-arg',\n file,\n detail: `launch arg \"${a}\" grants filesystem access at the root/home directory — scope this to a specific directory`,\n context: a,\n });\n }\n }\n\n if (node.env && typeof node.env === 'object' && !Array.isArray(node.env)) {\n for (const [key, value] of Object.entries(node.env as Record<string, unknown>)) {\n if (typeof value !== 'string') continue;\n const kind = classifyCredentialAssignment(key, value);\n if (kind === 'inline-secret') {\n findings.push({\n severity: 'HIGH',\n rule: 'mcp-inline-credential-value',\n file,\n detail: `env.${key} has an inline credential value — emit an empty string and document that the user must fill it in`,\n context: `env.${key}`,\n });\n } else if (kind === 'file-path') {\n findings.push({\n severity: 'LOW',\n rule: 'mcp-credential-file-path',\n file,\n detail: `env.${key} points at a credential file rather than an inline secret — the value leaks nothing, but keep that file out of version control`,\n context: `env.${key}`,\n });\n }\n }\n }\n }\n\n return findings;\n}\n\n// `(?:\\.cmd|\\.exe)?` and the `i` flag mirror isNpxCommand's basename\n// normalization so the text path honors the same \"recognized by basename\"\n// guarantee as the JSON path. v0.12: the `-y`/`--yes` group is now OPTIONAL\n// (group 1, presence selects severity) rather than mandatory — previously\n// its mandatory presence shielded the pattern from ever matching a flag as\n// the \"spec\"; making it optional means the spec group's first character MUST\n// be `@` or alphanumeric, so a `-`-leading token (`--version`, `--help`,\n// `-p`) can never be mistaken for a package spec. Documented limitation: a\n// line like `npx -p foo@1.2.3 bar` is still parsed imperfectly (line-regex,\n// not argv-grade) — the structural path (`scanJsonConfig`) is authoritative\n// for real configs; see docs/gate-policy.md.\nconst NPX_SHELL_RE = /\\bnpx(?:\\.cmd|\\.exe)?\\s+(?:(-y|--yes)\\s+)?((?:@[\\w.-]+\\/)?[A-Za-z0-9][\\w.-]*)(@[\\w.-]+)?/gi;\nconst DANGEROUS_FLAG_RE = /--dangerously-[\\w-]+|--no-sandbox\\b/g;\n/** Quoted assignments: `NAME=\"value\"` / `NAME: 'value'`. */\nconst CREDENTIAL_ASSIGNMENT_RE = /[\"']?([A-Za-z][A-Za-z0-9_]*)[\"']?\\s*[:=]\\s*[\"']([^\"']{6,})[\"']/g;\n/**\n * Unquoted assignments: `NAME=value` / `NAME: value` — no quotes around the\n * value — including the common prefixed forms `export NAME=value` (shell-sourced\n * .env) and `- NAME=value` (YAML list-style env, e.g. docker-compose).\n */\nconst CREDENTIAL_ASSIGNMENT_UNQUOTED_RE = /^\\s*(?:export\\s+|-\\s+)?([A-Za-z][A-Za-z0-9_]*)\\s*[:=]\\s*(\\S+)/;\n/** A value starting with a quote is the quoted regex's job — guards against double-reporting one line. */\nconst LEADING_QUOTE_RE = /^[\"']/;\n\n/**\n * Classifies one `key = value` pair. Split out so the structured (`env` block) and text-line\n * paths cannot drift — before this, only one of them knew about the path case.\n */\nfunction classifyCredentialAssignment(key: string, value: string): 'inline-secret' | 'file-path' | 'none' {\n if (isPlaceholderValue(value)) return 'none';\n const credentialKey = looksCredentialKey(key);\n // A credential-named var pointing at a FILE is correct usage, not a leak.\n if (credentialKey && looksStructuredNonSecret(value) && !/^[a-z]+:\\/\\//i.test(value.trim())) return 'file-path';\n if (credentialKey || looksHighEntropy(value)) return 'inline-secret';\n return 'none';\n}\n\nfunction pushCredentialFinding(\n findings: SafetyFinding[],\n file: string,\n lineNo: number,\n varName: string,\n value: string,\n): void {\n const kind = classifyCredentialAssignment(varName, value);\n if (kind === 'inline-secret') {\n findings.push({\n severity: 'HIGH',\n rule: 'mcp-inline-credential-value',\n file,\n detail: `line ${lineNo}: inline credential value assigned to ${varName}`,\n context: varName,\n });\n } else if (kind === 'file-path') {\n findings.push({\n severity: 'LOW',\n rule: 'mcp-credential-file-path',\n file,\n detail: `line ${lineNo}: ${varName} points at a credential file rather than an inline secret — the value leaks nothing, but keep that file out of version control`,\n context: varName,\n });\n }\n}\n\n/** Fallback line-based scan for non-JSON (or unparseable) text files: shell examples, README, .env. */\nfunction scanTextFile(file: string, content: string, allowCredentialCheck: boolean): SafetyFinding[] {\n const findings: SafetyFinding[] = [];\n const lines = content.split('\\n');\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const lineNo = i + 1;\n\n NPX_SHELL_RE.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = NPX_SHELL_RE.exec(line)) !== null) {\n const hasYes = Boolean(m[1]);\n const pkgSpec = m[2];\n const versionSuffix = m[3] ?? '';\n const fullSpec = pkgSpec + versionSuffix;\n if (!isPinnedPackageSpec(fullSpec)) {\n if (hasYes) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'mcp-unpinned-npx',\n file,\n detail: `line ${lineNo}: npx -y ${pkgSpec} is unpinned — pin an exact version (${pkgSpec}@<version>)`,\n context: fullSpec,\n });\n } else {\n findings.push({\n severity: 'LOW',\n rule: 'mcp-unpinned-npx',\n file,\n detail: `line ${lineNo}: npx ${pkgSpec} is unpinned — without -y npx prompts before first install, but once cached the tag still floats to whatever the registry points at; pin ${pkgSpec}@<version>`,\n context: fullSpec,\n });\n }\n }\n }\n\n DANGEROUS_FLAG_RE.lastIndex = 0;\n while ((m = DANGEROUS_FLAG_RE.exec(line)) !== null) {\n findings.push({\n severity: 'HIGH',\n rule: 'mcp-dangerous-flag',\n file,\n detail: `line ${lineNo}: launch arg ${m[0]} disables a safety guard`,\n });\n }\n\n if (allowCredentialCheck) {\n CREDENTIAL_ASSIGNMENT_RE.lastIndex = 0;\n while ((m = CREDENTIAL_ASSIGNMENT_RE.exec(line)) !== null) {\n pushCredentialFinding(findings, file, lineNo, m[1], m[2]);\n }\n\n const unquoted = CREDENTIAL_ASSIGNMENT_UNQUOTED_RE.exec(line);\n if (unquoted && !LEADING_QUOTE_RE.test(unquoted[2])) {\n pushCredentialFinding(findings, file, lineNo, unquoted[1], unquoted[2]);\n }\n }\n }\n\n return findings;\n}\n\nfunction scanFileForMcpFindings(file: string): SafetyFinding[] {\n const content = readTextFile(file);\n if (content === undefined) return [];\n\n if (extname(file).toLowerCase() === '.json') {\n const parsed = tryParseJson(content);\n if (parsed !== undefined) return scanJsonConfig(file, parsed);\n // malformed JSON — fall through to the text scan below\n }\n\n return scanTextFile(file, content, isConfigOrExampleFile(file));\n}\n\n/** Walks `targetPath` and returns MCP-specific findings only (not merged with the base ruleset). */\nexport function scanMcpFindings(targetPath: string): SafetyFinding[] {\n let root;\n try {\n root = statSync(targetPath);\n } catch {\n return [];\n }\n const files = root.isDirectory() ? Array.from(walk(targetPath)) : [targetPath];\n\n const findings: SafetyFinding[] = [];\n for (const file of files) {\n findings.push(...scanFileForMcpFindings(file));\n }\n return findings;\n}\n\n/**\n * MCP gate safety scan: the base deny-ruleset (`scanSafety`, includes curl|bash,\n * credential-file-access, etc.) plus the MCP-specific findings above, merged\n * into a single verdict via the same strictness rules as `scanSafety`.\n */\nexport async function mcpScan(targetPath: string, strictness: Strictness = DEFAULT_STRICTNESS): Promise<SafetyResult> {\n const base = await scanSafety(targetPath, strictness);\n const mcpFindings = scanMcpFindings(targetPath);\n const findings = [...base.findings, ...mcpFindings];\n return { findings, verdict: verdictFor(findings, strictness) };\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { existsSync, readFileSync, readdirSync, type Dirent } from 'node:fs';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { expandHome, isDir, stripQuotes } from '../util.js';\n\n/**\n * Overlap gate — ranks a candidate skill against an existing skill set.\n *\n * Heuristic, dependency-free. Blends Jaccard token overlap with keyword containment of the\n * candidate's distinctive phrases.\n */\n\nconst STOPWORDS = new Set(\n `\na an the and or of to for in on with use used using when whether this that these those is are be\nyour you it its as at by from into via per also any all not no than then so if up out via about\nskill skills user claude code based comprehensive enhanced practical including include includes\n`\n .split(/\\s+/)\n .filter(Boolean),\n);\n\nconst DEFAULT_SET_THRESHOLD = 0.45;\n\nexport interface SkillMeta {\n name: string;\n description: string;\n}\n\nexport interface SkillOverlapScore {\n skill: string;\n path: string;\n score: number;\n}\n\nexport interface OverlapResult {\n candidate: string;\n nearest: string;\n nearestScore: number;\n suggestedVerb: string;\n ranking: SkillOverlapScore[];\n}\n\nexport interface OverlapPair {\n a: string;\n b: string;\n score: number;\n}\n\n/** `OverlapPair` plus the absolute SKILL.md paths behind `a`/`b` — paths are the stable identity across roots (names can collide). */\nexport interface OverlapPathPair extends OverlapPair {\n aPath: string;\n bPath: string;\n}\n\n/** Mirrors Python's `str.split(sep, maxSplit)` — returns up to maxSplit+1 parts. */\nfunction splitLimited(text: string, sep: string, maxSplit: number): string[] {\n const parts: string[] = [];\n let rest = text;\n for (let i = 0; i < maxSplit; i++) {\n const idx = rest.indexOf(sep);\n if (idx === -1) break;\n parts.push(rest.slice(0, idx));\n rest = rest.slice(idx + sep.length);\n }\n parts.push(rest);\n return parts;\n}\n\n/** Reads a SKILL.md's `name`/`description` frontmatter, falling back to heading + body lead-in. */\nexport function readFrontmatter(skillMdPath: string): SkillMeta {\n const text = readFileSync(skillMdPath, 'utf-8');\n const meta: SkillMeta = { name: basename(dirname(skillMdPath)), description: '' };\n\n if (text.startsWith('---\\n')) {\n const block = splitLimited(text, '---', 2)[1] ?? '';\n\n const nameMatch = block.match(/^name:\\s*(.+)$/m);\n if (nameMatch) {\n meta.name = stripQuotes(nameMatch[1].trim());\n }\n\n const descMatch = block.match(/^description:\\s*(.*)$/m);\n if (descMatch) {\n const first = descMatch[1].trim();\n if (['>', '>-', '|', '|-', ''].includes(first)) {\n const lines = block.split(/\\r\\n|\\r|\\n/);\n const idx = lines.findIndex((l) => /^description:/.test(l));\n const buf: string[] = [];\n if (idx !== -1) {\n for (const l of lines.slice(idx + 1)) {\n if (/^\\s+\\S/.test(l)) {\n buf.push(l.trim());\n } else if (l.trim() === '') {\n continue;\n } else {\n break;\n }\n }\n }\n meta.description = buf.join(' ');\n } else {\n meta.description = stripQuotes(first);\n }\n }\n } else {\n const heads = Array.from(text.matchAll(/^#\\s+(.+)$/gm)).map((m) => m[1]);\n meta.description = `${heads[0] ?? ''} ${text.slice(0, 400)}`;\n }\n\n return meta;\n}\n\nfunction tokenize(text: string): Set<string> {\n const matches = text.toLowerCase().match(/[a-z][a-z0-9+.-]{2,}/g) ?? [];\n return new Set(matches.filter((t) => !STOPWORDS.has(t)));\n}\n\n/**\n * Distinctive quoted trigger phrases and salient words from a description.\n * Ported for parity with the source script; not used by score() (which extracts phrases inline).\n */\nexport function keyphrases(text: string): Set<string> {\n const quoted = new Set<string>();\n for (const m of text.matchAll(/\"([^\"]{2,40})\"/g)) quoted.add(m[1].toLowerCase().trim());\n for (const m of text.matchAll(/'([^']{2,40})'/g)) quoted.add(m[1].toLowerCase().trim());\n return quoted.size > 0 ? quoted : tokenize(text);\n}\n\nfunction intersectSize(a: Set<string>, b: Set<string>): number {\n let n = 0;\n for (const x of a) if (b.has(x)) n += 1;\n return n;\n}\n\nfunction unionSize(a: Set<string>, b: Set<string>): number {\n const u = new Set(a);\n for (const x of b) u.add(x);\n return u.size;\n}\n\nfunction isSubset(sub: Set<string>, sup: Set<string>): boolean {\n for (const x of sub) if (!sup.has(x)) return false;\n return true;\n}\n\nfunction round3(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n\nfunction score(cand: SkillMeta, other: SkillMeta): number {\n const a = tokenize(`${cand.name} ${cand.description}`);\n const b = tokenize(`${other.name} ${other.description}`);\n if (a.size === 0) return 0.0;\n\n const inter = intersectSize(a, b);\n const union = unionSize(a, b);\n const jaccard = union ? inter / union : 0.0;\n const containment = inter / a.size;\n\n const phrases = new Set<string>();\n for (const m of cand.description.matchAll(/\"([^\"]{2,40})\"/g)) phrases.add(m[1]);\n for (const m of cand.description.matchAll(/'([^']{2,40})'/g)) phrases.add(m[1]);\n\n let pscore = 0.0;\n if (phrases.size > 0) {\n let hits = 0;\n for (const p of phrases) {\n const pw = tokenize(p);\n if (pw.size > 0 && isSubset(pw, b)) hits += 1;\n }\n pscore = hits / phrases.size;\n }\n\n return round3(0.3 * jaccard + 0.5 * containment + 0.2 * pscore);\n}\n\n/** Score tiers behind suggestVerb/suggestVerbCode — one table so prose and bare verb can't drift. */\nconst VERB_TIERS = [\n { min: 0.45, verb: 'ABSORB', prose: 'ABSORB (or REJECT if ours is already better)' },\n { min: 0.2, verb: 'FORK', prose: 'FORK (re-skin), or ABSORB one piece' },\n { min: -Infinity, verb: 'DEFER', prose: 'DEFER / new FORK (little overlap)' },\n] as const;\n\nfunction verbTier(topScore: number): (typeof VERB_TIERS)[number] {\n return VERB_TIERS.find((t) => topScore >= t.min) ?? VERB_TIERS[VERB_TIERS.length - 1];\n}\n\nexport function suggestVerb(topScore: number): string {\n return verbTier(topScore).prose;\n}\n\n/** Bare verb of the tier suggestVerb would pick — for callers that store an enum, not prose (e.g. the pending queue). */\nexport function suggestVerbCode(topScore: number): (typeof VERB_TIERS)[number]['verb'] {\n return verbTier(topScore).verb;\n}\n\n/** The typed union `suggestVerbCode` returns — shared by callers (e.g. `audit.ts`'s `OverlapCluster.suggestedVerb`) that want the bare enum's type without re-deriving it. */\nexport type SuggestedVerbCode = ReturnType<typeof suggestVerbCode>;\n\nfunction listSubdirs(dir: string): string[] {\n if (!existsSync(dir)) return [];\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n return entries.filter((e) => e.isDirectory()).map((e) => join(dir, e.name));\n}\n\n/**\n * Finds SKILL.md files matching the same three glob shapes as the Python script:\n * \"<dir>/SKILL.md\", \"skills/<dir>/SKILL.md\", \"<dir>/skills/<dir>/SKILL.md\" (one wildcard\n * <dir> level each) — deduped, .git excluded, sorted.\n */\nfunction findSkillMdFiles(root: string): string[] {\n const found = new Set<string>();\n\n for (const dir of listSubdirs(root)) {\n const candidate = join(dir, 'SKILL.md');\n if (existsSync(candidate)) found.add(resolve(candidate));\n }\n\n for (const dir of listSubdirs(join(root, 'skills'))) {\n const candidate = join(dir, 'SKILL.md');\n if (existsSync(candidate)) found.add(resolve(candidate));\n }\n\n for (const topDir of listSubdirs(root)) {\n for (const dir of listSubdirs(join(topDir, 'skills'))) {\n const candidate = join(dir, 'SKILL.md');\n if (existsSync(candidate)) found.add(resolve(candidate));\n }\n }\n\n return Array.from(found)\n .filter((p) => !p.includes('/.git/'))\n .sort();\n}\n\n/**\n * Ranks a single candidate skill against every skill found under `skillsRoot`.\n * Throws if `skillsRoot` isn't a directory, the candidate's SKILL.md can't be found,\n * or no existing skills are found under the root (mirrors the Python script's `fail()` exits).\n */\nexport function scanOverlap(candidatePath: string, skillsRoot: string): OverlapResult {\n const root = resolve(expandHome(skillsRoot));\n if (!isDir(root)) {\n throw new Error(`skills-root not a directory: ${root}`);\n }\n\n let cpath = resolve(expandHome(candidatePath));\n if (isDir(cpath)) {\n cpath = join(cpath, 'SKILL.md');\n }\n if (!existsSync(cpath)) {\n throw new Error(`candidate SKILL.md not found: ${cpath}`);\n }\n\n const cand = readFrontmatter(cpath);\n\n const existing = findSkillMdFiles(root).filter((p) => p !== resolve(cpath));\n if (existing.length === 0) {\n throw new Error(`no existing skills found under ${root}`);\n }\n\n const ranking: SkillOverlapScore[] = existing.map((p) => {\n const other = readFrontmatter(p);\n return { skill: other.name, path: p, score: score(cand, other) };\n });\n ranking.sort((a, b) => b.score - a.score);\n\n const top = ranking[0];\n return {\n candidate: cand.name,\n nearest: top.skill,\n nearestScore: top.score,\n suggestedVerb: suggestVerb(top.score),\n ranking,\n };\n}\n\n/**\n * N-way: pairwise overlap across an arbitrary set of SKILL.md paths; reports pairs scoring >=\n * threshold. Symmetric — the pair score is max(score(a,b), score(b,a)) since score() is\n * candidate-centric. Paths (not names) are the stable identity: names can collide across roots,\n * which is exactly why `skill-forge audit`'s cross-root clustering (src/audit.ts) needs the path\n * alongside each name rather than `scanOverlapSet`'s name-only `OverlapPair`.\n */\nexport function scanOverlapPaths(\n skillMdPaths: readonly string[],\n threshold: number = DEFAULT_SET_THRESHOLD,\n): OverlapPathPair[] {\n const metas = skillMdPaths.map(readFrontmatter);\n\n const pairs: OverlapPathPair[] = [];\n for (let i = 0; i < metas.length; i++) {\n for (let j = i + 1; j < metas.length; j++) {\n const sc = Math.max(score(metas[i], metas[j]), score(metas[j], metas[i]));\n if (sc >= threshold) {\n pairs.push({\n a: metas[i].name,\n b: metas[j].name,\n aPath: skillMdPaths[i],\n bPath: skillMdPaths[j],\n score: round3(sc),\n });\n }\n }\n }\n pairs.sort((a, b) => b.score - a.score);\n return pairs;\n}\n\n/**\n * N-way: pairwise overlap across the whole installed set; reports pairs scoring >= threshold.\n * Delegates to `scanOverlapPaths` (path-based identity) and strips paths back off, so its public\n * behavior — exact scoring, ordering, and the name-only `OverlapPair` shape — is unchanged.\n */\nexport function scanOverlapSet(\n skillsRoot: string,\n threshold: number = DEFAULT_SET_THRESHOLD,\n): OverlapPair[] {\n const root = resolve(expandHome(skillsRoot));\n if (!isDir(root)) {\n throw new Error(`skills-root not a directory: ${root}`);\n }\n\n const paths = findSkillMdFiles(root);\n return scanOverlapPaths(paths, threshold).map(({ a, b, score: s }) => ({ a, b, score: s }));\n}\n","import { createHash } from 'node:crypto';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { AuditFinding } from './audit.js';\nimport { getHomeDir } from './config.js';\nimport { UserError } from './errors.js';\nimport type { Severity } from './gate/scanShared.js';\nimport { MAX_PREFERENCE_NOTE_LENGTH, validateProvenanceText } from './preferences.js';\nimport { writeJsonAtomic } from './util.js';\n\n/**\n * Accepted audit findings (v0.12) — a per-machine store at `~/.skill-forge/accepted-findings.json`\n * (honors `SKILL_FORGE_HOME`) holding human-reviewed acknowledgements of `skill-forge audit`\n * findings. See `docs/accepted-findings-schema.md` for the full identity contract, the per-rule\n * `context` table, and the fail-open semantics summarized below.\n *\n * ## Identity: the fingerprint\n *\n * Each record is keyed by `fingerprint = sha256(severity \\0 rule \\0 target \\0 context)` —\n * {@link fingerprintFinding}. `context` is the offending line's text (or a rule-specific stable\n * token; see `docs/accepted-findings-schema.md`'s per-rule table) — the finding's LINE NUMBER is\n * deliberately excluded (it drifts on unrelated edits above/below), and SEVERITY is deliberately\n * included (so re-tuning a rule to a higher severity — e.g. the §9 `mcp-unpinned-npx` widening —\n * can never inherit an ack minted against the lower one). Any change to the offending content\n * produces a new fingerprint, so an edited or new-but-matching-pattern line is NOT suppressed by an\n * old ack — this is the deliberate \"fail open to re-reporting\" design, not a bug: the alternative\n * (keying on rule+path only) is the classic baseline trap, where accepting one benign line silently\n * suppresses every future line the same rule matches in that file.\n *\n * ## Hard boundary: advisory only, never a gate input\n *\n * Nothing in this store may influence a **gate verdict** (`add`/`scan`/`promote <id>`/`evolve`\n * re-gate). The gate judges a *candidate*; this store describes decisions about the *installed\n * set*. `runGate`/`gatePipeline.ts` never read this module — only `src/audit.ts` (the audit\n * report's active/accepted partition) and its consumers (`commands/audit.ts`, `commands/routine.ts`,\n * `commands/finding.ts`) do. This mirrors `src/auditState.ts`'s own advisory-only invariant.\n *\n * ## Fail-open contract\n *\n * {@link readAcceptedFindings} NEVER throws: a missing store, corrupt JSON, an unrecognized\n * `schemaVersion`, or an individual malformed record all degrade toward MORE reporting, never\n * less — every failure mode returns `{ records: [], notices: [...] }` or drops just the bad\n * record(s), and always says so via a notice. This is consulted from inside `audit`'s report-build\n * path (and, downstream, `add`/`scan`'s advisory), so a corrupt store must never break a run.\n */\n\nexport interface AcceptedFinding {\n /** 64 lowercase hex chars — sha256 of the identity components. See {@link fingerprintFinding}. */\n fingerprint: string;\n /** Snapshot at accept time — also a hash input (see the module doc's identity section). */\n severity: Severity;\n rule: string;\n target: string;\n /** '' is allowed only for the specific rules `docs/accepted-findings-schema.md`'s table permits. */\n context: string;\n /** Display snapshot of the finding's detail text at accept time — NOT a hash input. */\n detail: string;\n /** Mandatory human-supplied note on why this finding is accepted. 1-200 chars, no C0/C1 controls. */\n reason: string;\n /** ISO-8601 timestamp of when this record was written. */\n acceptedAt: string;\n /** Enum-of-one today — the human-door invariant, machine-checkable. See `docs/gate-policy.md`. */\n source: 'user';\n}\n\nexport interface AcceptedFindingsStore {\n schemaVersion: 1;\n records: AcceptedFinding[];\n}\n\n/** Rendered/matched fingerprint display length (first N hex chars) — `docs/accepted-findings-schema.md` §1.1. */\nexport const FINGERPRINT_DISPLAY_LENGTH = 12;\n\n/** Minimum prefix length `finding accept`/`finding revoke` will attempt to resolve — shorter is refused as too likely to collide. */\nexport const MIN_FINGERPRINT_PREFIX_LENGTH = 8;\n\nconst FINGERPRINT_HEX_RE = /^[0-9a-f]{64}$/;\nconst VALID_SEVERITIES: readonly Severity[] = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];\n// eslint-disable-next-line no-control-regex -- intentional: rejecting C0/C1 control characters\nconst CONTROL_CHAR_RE = /[\\u0000-\\u001F\\u007F-\\u009F]/;\n\nfunction isValidSeverity(value: unknown): value is Severity {\n return typeof value === 'string' && (VALID_SEVERITIES as readonly string[]).includes(value);\n}\n\n/** sha256 hex over the four identity components, NUL-separated. See the module doc's identity section for why line numbers are excluded and severity is included. */\nexport function fingerprintFinding(f: { severity: Severity; rule: string; target: string; context: string }): string {\n return createHash('sha256').update(`${f.severity}\\0${f.rule}\\0${f.target}\\0${f.context}`).digest('hex');\n}\n\nexport function getAcceptedFindingsPath(): string {\n return join(getHomeDir(), 'accepted-findings.json');\n}\n\n/**\n * Per-record repair check, mirroring `preferences.ts`'s `normalizePreferences` philosophy: a\n * record that fails ANY of these checks is dropped rather than trusted, because every one of\n * these fields is later rendered into a terminal/markdown report (control characters would smuggle\n * escape sequences) or used as an equality/matching key (a malformed fingerprint or severity must\n * never silently participate in matching). Returns a short reason string, or `undefined` when the\n * record is valid.\n */\nfunction invalidRecordReason(record: unknown): string | undefined {\n if (typeof record !== 'object' || record === null) return 'not an object';\n const r = record as Record<string, unknown>;\n if (typeof r.fingerprint !== 'string' || !FINGERPRINT_HEX_RE.test(r.fingerprint)) return 'invalid fingerprint';\n if (!isValidSeverity(r.severity)) return 'invalid severity';\n if (typeof r.rule !== 'string' || r.rule.length === 0 || CONTROL_CHAR_RE.test(r.rule)) return 'invalid rule';\n if (typeof r.target !== 'string' || r.target.length === 0 || CONTROL_CHAR_RE.test(r.target)) return 'invalid target';\n if (typeof r.context !== 'string' || CONTROL_CHAR_RE.test(r.context)) return 'invalid context';\n if (typeof r.detail !== 'string' || CONTROL_CHAR_RE.test(r.detail)) return 'invalid detail';\n if (\n typeof r.reason !== 'string' ||\n r.reason.length === 0 ||\n r.reason.length > MAX_PREFERENCE_NOTE_LENGTH ||\n CONTROL_CHAR_RE.test(r.reason)\n ) {\n return 'invalid reason';\n }\n if (typeof r.acceptedAt !== 'string' || r.acceptedAt.length === 0) return 'invalid acceptedAt';\n if (r.source !== 'user') return 'invalid source';\n return undefined;\n}\n\n/**\n * Reads the accepted-findings store. NEVER throws — see the module doc's fail-open contract.\n * Every failure mode (missing file, corrupt JSON, unrecognized envelope shape/schemaVersion, or an\n * individual malformed record) returns fewer or zero records, plus a notice explaining why —\n * failure always moves toward MORE reporting, never less.\n */\nexport function readAcceptedFindings(): { records: AcceptedFinding[]; notices: string[] } {\n const path = getAcceptedFindingsPath();\n if (!existsSync(path)) return { records: [], notices: [] };\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(path, 'utf-8'));\n } catch {\n return { records: [], notices: ['accepted-findings store unreadable (corrupt JSON) — nothing suppressed this run'] };\n }\n\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n (parsed as { schemaVersion?: unknown }).schemaVersion !== 1 ||\n !Array.isArray((parsed as { records?: unknown }).records)\n ) {\n return { records: [], notices: ['accepted-findings store unreadable (unrecognized shape or schema version) — nothing suppressed this run'] };\n }\n\n const rawRecords = (parsed as { records: unknown[] }).records;\n const records: AcceptedFinding[] = [];\n let dropped = 0;\n for (const raw of rawRecords) {\n if (invalidRecordReason(raw) === undefined) {\n records.push(raw as AcceptedFinding);\n } else {\n dropped++;\n }\n }\n\n const notices: string[] = [];\n if (dropped > 0) {\n notices.push(\n `${dropped} accepted-finding record(s) in the store failed validation and were dropped — whatever they described is reported as active again`\n );\n }\n\n return { records, notices };\n}\n\n/** Overwrites the store. Atomic + owner-only via the shared writer in util.ts. */\nexport function writeAcceptedFindings(records: AcceptedFinding[]): void {\n const store: AcceptedFindingsStore = { schemaVersion: 1, records };\n writeJsonAtomic(getAcceptedFindingsPath(), store);\n}\n\n/**\n * Validates `finding accept`'s mandatory `--reason`. Reuses `preferences.ts`'s\n * control-char/length validator (`validateProvenanceText`, capped at `MAX_PREFERENCE_NOTE_LENGTH`)\n * rather than a second copy — the field renders at the same kind of human-review moment\n * (`finding list`, the report's Accepted findings section) that `origin`/`note` do.\n */\nexport function validateAcceptReason(reason: string): void {\n if (reason.length === 0) {\n throw new UserError('--reason is required — a short note on why this finding is accepted (e.g. \"funnel genuinely needs root\")');\n }\n validateProvenanceText('reason', reason);\n}\n\n/** Every active `AuditFinding` (must carry a `fingerprint`) whose fingerprint starts with `prefix` (case-insensitive). */\nexport function resolveAmongFindings(prefix: string, findings: readonly AuditFinding[]): AuditFinding[] {\n const lower = prefix.toLowerCase();\n return findings.filter((f) => f.fingerprint !== undefined && f.fingerprint.startsWith(lower));\n}\n\n/** Every stored `AcceptedFinding` whose fingerprint starts with `prefix` (case-insensitive). */\nexport function resolveAmongRecords(prefix: string, records: readonly AcceptedFinding[]): AcceptedFinding[] {\n const lower = prefix.toLowerCase();\n return records.filter((r) => r.fingerprint.startsWith(lower));\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { verify as cryptoVerify } from 'node:crypto';\nimport { loadConfig } from './config.js';\n\n/**\n * Offline license-key verification for skill-forge Pro. No network calls —\n * the CLI must gate Pro features without phoning home.\n *\n * Key format: `skf1.<base64url payload>.<base64url signature>`\n * - `skf1` is a fixed prefix identifying the key scheme/version.\n * - payload is base64url-encoded JSON: `{ name, email, seats, exp }`, where\n * `exp` is an ISO date string (`YYYY-MM-DD`) the key is valid through.\n * - signature is an Ed25519 signature (via node:crypto) over the raw\n * decoded payload bytes, verified against the embedded PUBLIC_KEY_PEM.\n *\n * Keys are minted offline with `scripts/keygen.mjs sign` using the matching\n * private key, which is never committed to this repo (see keys/, gitignored).\n *\n * Beta pricing (0.x): the commercial license still governs Pro modules, but\n * the runtime gate SLEEPS while BETA_FREE is true — every Pro feature runs\n * for everyone, with a one-line notice unless a valid key is present. Flip\n * BETA_FREE to false at 1.0 to re-arm the lock; nothing else needs to change.\n * `proFeatureStatus()` is the shape callers should gate on: it folds\n * \"do we have a valid key\" and \"does the beta make that not matter\" into a\n * single `{ enabled, notice? }` result. `hasProLicense()` / `upgradeNotice()`\n * remain as-is for existing call sites until they're migrated to it.\n */\n\nconst LICENSE_PREFIX = 'skf1';\n\n/** Single source of truth for beta pricing. Flip to false at v1.0. */\nexport const BETA_FREE = true;\n\n// Ed25519 public key (SPKI/PEM), generated once via `scripts/keygen.mjs keypair`.\n// The matching private key lives outside this repo — see scripts/keygen.mjs.\nconst PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAWSVbcX9u70IsN1O5zS5z+AYtAUVz7ExT7h+1gT9x+wQ=\n-----END PUBLIC KEY-----\n`;\n\nexport interface LicensePayload {\n name: string;\n email: string;\n seats: number;\n exp: string;\n}\n\nexport type LicenseInvalidReason = 'missing' | 'malformed' | 'signature' | 'expired';\n\nexport type LicenseStatus =\n | { valid: true; payload: LicensePayload }\n | { valid: false; reason: LicenseInvalidReason };\n\n/** Reads the raw license key string. SKILL_FORGE_LICENSE (env) takes precedence over config.licenseKey. */\nexport function readLicenseKey(): string | undefined {\n const fromEnv = process.env.SKILL_FORGE_LICENSE?.trim();\n if (fromEnv) return fromEnv;\n return loadConfig().licenseKey?.trim() || undefined;\n}\n\nfunction isLicensePayload(value: unknown): value is LicensePayload {\n if (typeof value !== 'object' || value === null) return false;\n const p = value as Record<string, unknown>;\n return typeof p.name === 'string' && typeof p.email === 'string' && typeof p.seats === 'number' && typeof p.exp === 'string';\n}\n\n/**\n * Verifies a license key string. Pass no argument to check the default\n * sources (SKILL_FORGE_LICENSE env, then config.licenseKey). Verifies the\n * `skf1.<payload>.<signature>` shape, the Ed25519 signature against\n * PUBLIC_KEY_PEM, and that `exp` has not passed. Any tampering (payload or\n * signature) or expiry makes the key invalid.\n */\nexport function checkLicense(rawKey: string | undefined = readLicenseKey()): LicenseStatus {\n if (!rawKey) {\n return { valid: false, reason: 'missing' };\n }\n\n const parts = rawKey.trim().split('.');\n if (parts.length !== 3 || parts[0] !== LICENSE_PREFIX) {\n return { valid: false, reason: 'malformed' };\n }\n const [, payloadPart, signaturePart] = parts;\n\n const payloadBuffer = Buffer.from(payloadPart, 'base64url');\n const signature = Buffer.from(signaturePart, 'base64url');\n\n let payload: unknown;\n try {\n payload = JSON.parse(payloadBuffer.toString('utf-8'));\n } catch {\n return { valid: false, reason: 'malformed' };\n }\n if (!isLicensePayload(payload)) {\n return { valid: false, reason: 'malformed' };\n }\n\n // Ed25519 signs/verifies over raw bytes directly — algorithm must be null.\n let signatureValid: boolean;\n try {\n signatureValid = cryptoVerify(null, payloadBuffer, PUBLIC_KEY_PEM, signature);\n } catch {\n return { valid: false, reason: 'malformed' };\n }\n if (!signatureValid) {\n return { valid: false, reason: 'signature' };\n }\n\n const expMs = new Date(payload.exp).getTime();\n if (Number.isNaN(expMs) || expMs < Date.now()) {\n return { valid: false, reason: 'expired' };\n }\n\n return { valid: true, payload };\n}\n\n/** True if the default license sources resolve to a currently-valid license. */\nexport function hasProLicense(): boolean {\n return checkLicense().valid;\n}\n\n/** Short line replacing a locked Pro output — printed in place of the feature's normal output. */\nexport function upgradeNotice(feature: string): string {\n return `[skill-forge] Pro feature (${feature}) — set SKILL_FORGE_LICENSE or config.licenseKey to a valid license to unlock.`;\n}\n\nfunction invalidLicenseReasonText(reason: LicenseInvalidReason): string {\n switch (reason) {\n case 'malformed':\n return 'is malformed';\n case 'signature':\n return 'has an invalid signature';\n case 'expired':\n return 'has expired';\n default:\n return 'is invalid';\n }\n}\n\n/** Result shape for {@link proFeatureStatus}: whether a Pro feature should run, and an optional line to print alongside it. */\nexport interface ProFeatureStatus {\n enabled: boolean;\n notice?: string;\n}\n\n/**\n * Resolves whether a Pro feature should run right now, folding in the 0.x\n * beta-free policy. This is the shape call sites should consume going\n * forward — see the module docstring for the resolution table.\n *\n * `betaFree` defaults to {@link BETA_FREE} and is only a parameter so tests\n * can exercise the post-1.0 (locked) branch without touching the constant.\n */\nexport function proFeatureStatus(feature: string, betaFree: boolean = BETA_FREE): ProFeatureStatus {\n const status = checkLicense();\n if (status.valid) {\n return { enabled: true };\n }\n\n if (!betaFree) {\n return { enabled: false, notice: upgradeNotice(feature) };\n }\n\n const betaNotice = `Pro feature (${feature}) — free during the 0.x beta; will require a license at 1.0.`;\n if (status.reason === 'missing') {\n return { enabled: true, notice: betaNotice };\n }\n\n return {\n enabled: true,\n notice: `${betaNotice} (Note: your license key ${invalidLicenseReasonText(status.reason)} — ignored while Pro is free.)`,\n };\n}\n","import { execFileSync } from 'node:child_process';\nimport {\n closeSync,\n existsSync,\n mkdtempSync,\n openSync,\n readFileSync,\n readdirSync,\n readSync,\n rmSync,\n statSync,\n} from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { basename, dirname, extname, join, resolve } from 'node:path';\nimport { expandHome, isDir, stripQuotes } from '../util.js';\nimport { extractMcpCapabilities, type McpCapabilities } from './mcpCapabilities.js';\n\n/**\n * Stdlib-only structured profile of an external skill candidate: a skill dir, a single\n * SKILL.md, a plugin dir (.claude-plugin/plugin.json), an MCP config dir (.mcp.json /\n * mcp.json), or a .skill (zip) bundle. Fails loudly (throws) rather than guessing.\n */\n\nconst SPDX_HINTS: Record<string, string> = {\n mit: 'MIT (permissive)',\n apache: 'Apache-2.0 (permissive)',\n bsd: 'BSD (permissive)',\n isc: 'ISC (permissive)',\n unlicense: 'Unlicense (public domain)',\n cc0: 'CC0 (public domain)',\n 'mozilla public license': 'MPL (weak copyleft)',\n 'gnu general public': 'GPL (copyleft)',\n 'gnu affero': 'AGPL (copyleft)',\n 'creative commons attribution-sharealike': 'CC-BY-SA (copyleft)',\n 'creative commons attribution': 'CC-BY (attribution)',\n 'all rights reserved': 'All rights reserved (restrictive)',\n};\n\nconst RESOURCE_DIRS = ['scripts', 'references', 'reference', 'commands', 'hooks', 'assets', 'templates', 'sub-skills'];\n\nconst PYTHON_STDLIB = new Set([\n 'os', 'sys', 're', 'json', 'argparse', 'pathlib', 'tempfile', 'zipfile',\n 'subprocess', 'collections', 'datetime', 'math', 'itertools', 'typing',\n 'shutil', 'glob', 'hashlib', 'urllib', 'csv', 'io', 'functools', '__future__',\n]);\n\n/**\n * v0.5: the artifact this candidate should be gated/promoted as — 'mcp' when\n * an MCP config file or an MCP-server-shaped package.json is detected,\n * 'skill' otherwise (default). Orthogonal to `kind`, which selects which\n * profiler shape ran (skill dir / plugin / mcp config dir).\n */\nexport type ArtifactType = 'skill' | 'mcp';\n\nexport interface SkillProfile {\n kind: 'skill';\n inputPath: string;\n skillMd: string;\n name?: string;\n version?: string;\n validFrontmatter: boolean;\n description?: string;\n license: string;\n bodyLines: number;\n headerCount: number;\n topHeaders: string[];\n resources: Record<string, number>;\n mcpDependencies: string[];\n externalPythonDeps: string[];\n artifactType: ArtifactType;\n}\n\nexport interface PluginProfile {\n kind: 'plugin';\n inputPath: string;\n name?: string;\n version?: string;\n description?: string;\n skills: string[];\n skillCount: number;\n commands: number;\n mcpServers: string[];\n artifactType: ArtifactType;\n}\n\nexport interface McpProfile {\n kind: 'mcp';\n inputPath: string;\n config: string;\n servers: string[];\n serverCount: number;\n artifactType: ArtifactType;\n /** v0.6: static, best-effort declared capabilities (tools/resources/prompts) — see mcpCapabilities.ts. */\n capabilities: McpCapabilities;\n}\n\nexport type Profile = SkillProfile | PluginProfile | McpProfile;\n\n/** Profiles a skill dir, SKILL.md, plugin dir, MCP config dir, or .skill archive. */\nexport function profileSkill(inputPath: string): Profile {\n const p = resolve(expandHome(inputPath));\n if (!existsSync(p)) {\n throw new Error(`path does not exist: ${p}`);\n }\n\n const stat = statSync(p);\n const kind = stat.isDirectory() ? classifyInput(p) : 'skill';\n\n if (kind === 'plugin') {\n return profilePlugin(p);\n }\n if (kind === 'mcp') {\n return profileMcp(p);\n }\n return profileSkillDir(p);\n}\n\n/**\n * Narrows an already-resolved `Profile` to a `SkillProfile`, throwing a\n * descriptive error otherwise. For a caller that has already profiled a\n * path (e.g. via `profileSkill()`) and needs the skill-specific shape to\n * proceed. `context` is appended to the error message (e.g. `at ${path}`,\n * `for the evolved proposal`). Shared by `commands/gatePipeline.ts`'s\n * `asProfile()` (its skill branch) and `evolve.ts`'s re-gate step.\n */\nexport function asSkillProfile(profile: Profile, context: string): SkillProfile {\n if (profile.kind !== 'skill') {\n throw new Error(`expected a skill candidate ${context}, found a ${profile.kind}`);\n }\n return profile;\n}\n\n/**\n * Profiles `candidatePath` and asserts the result matches `artifact`\n * ('mcp' expects kind 'mcp'; 'skill'/absent expects kind 'skill'),\n * narrowing the return type accordingly. Throws a descriptive error\n * otherwise, rather than guessing. Moved here (v0.7 simplify pass) from\n * `commands/gatePipeline.ts`, which still owns calling it as part of the\n * gate pipeline; `evolve.ts` also profiles a skill through this same\n * skill-branch logic via `asSkillProfile()` above.\n */\nexport function asProfile(candidatePath: string, artifact: ArtifactType): Profile {\n const profile = profileSkill(candidatePath);\n if (artifact === 'mcp') {\n if (profile.kind !== 'mcp') {\n throw new Error(`expected an MCP candidate at ${candidatePath} (--artifact mcp), found a ${profile.kind}`);\n }\n return profile;\n }\n return asSkillProfile(profile, `at ${candidatePath}`);\n}\n\n/** skill | plugin | mcp — by manifest/config presence (directories only). */\nfunction classifyInput(p: string): 'skill' | 'plugin' | 'mcp' {\n if (existsSync(join(p, '.claude-plugin', 'plugin.json'))) return 'plugin';\n if (detectArtifactType(p) === 'mcp') return 'mcp';\n return 'skill';\n}\n\nconst MCP_SDK_DEP = '@modelcontextprotocol/sdk';\nconst MCP_KEYWORD = 'mcp-server';\nconst MCP_BIN_PREFIX = /^mcp-/i;\n\n/** Reads package.json for detection purposes only — malformed/missing is not a signal either way. */\nfunction readPackageJsonSafe(root: string): Record<string, unknown> | undefined {\n const pkgPath = join(root, 'package.json');\n if (!existsSync(pkgPath)) return undefined;\n try {\n return JSON.parse(readFileSync(pkgPath, 'utf-8')) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n\n/** deps containing @modelcontextprotocol/sdk, keywords containing \"mcp-server\", or bin names like mcp-*. */\nfunction hasMcpPackageMarkers(pkg: Record<string, unknown>): boolean {\n for (const section of ['dependencies', 'devDependencies', 'peerDependencies'] as const) {\n const deps = pkg[section];\n if (deps && typeof deps === 'object' && MCP_SDK_DEP in (deps as Record<string, unknown>)) return true;\n }\n\n const keywords = pkg.keywords;\n if (Array.isArray(keywords) && keywords.some((k) => k === MCP_KEYWORD)) return true;\n\n const bin = pkg.bin;\n if (typeof bin === 'string') {\n if (MCP_BIN_PREFIX.test(basename(bin))) return true;\n } else if (bin && typeof bin === 'object') {\n if (Object.keys(bin as Record<string, unknown>).some((k) => MCP_BIN_PREFIX.test(k))) return true;\n }\n\n return false;\n}\n\n/** v0.5 artifact classification: an mcp config file, or an MCP-server-shaped package.json, at `root`. */\nfunction detectArtifactType(root: string): ArtifactType {\n if (existsSync(join(root, '.mcp.json')) || existsSync(join(root, 'mcp.json'))) return 'mcp';\n const pkg = readPackageJsonSafe(root);\n if (pkg && hasMcpPackageMarkers(pkg)) return 'mcp';\n return 'skill';\n}\n\ninterface ResolvedSkillMd {\n skillMd: string;\n root: string;\n tmpDir?: string;\n}\n\nfunction resolveSkillMd(inputPath: string): ResolvedSkillMd {\n let currentPath = inputPath;\n let tmpDir: string | undefined;\n\n const initialStat = statSync(currentPath);\n if (extname(currentPath) === '.skill' || (initialStat.isFile() && isZipFile(currentPath))) {\n tmpDir = mkdtempSync(join(tmpdir(), 'forge_'));\n extractZip(currentPath, tmpDir);\n currentPath = tmpDir;\n }\n\n const stat = statSync(currentPath);\n if (stat.isFile() && basename(currentPath) === 'SKILL.md') {\n return { skillMd: currentPath, root: dirname(currentPath), tmpDir };\n }\n if (stat.isDirectory()) {\n const direct = join(currentPath, 'SKILL.md');\n if (existsSync(direct)) {\n return { skillMd: direct, root: currentPath, tmpDir };\n }\n const matches = findSkillMdOneLevelDown(currentPath);\n if (matches.length === 1) {\n return { skillMd: matches[0], root: dirname(matches[0]), tmpDir };\n }\n if (matches.length > 1) {\n cleanupTmp(tmpDir);\n throw new Error(\n `multiple SKILL.md found under ${currentPath}; point at a single skill dir:\\n ${matches.join('\\n ')}`\n );\n }\n }\n cleanupTmp(tmpDir);\n throw new Error(`no SKILL.md found at ${currentPath}`);\n}\n\nfunction cleanupTmp(tmpDir?: string): void {\n if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });\n}\n\n/** Mirrors path.glob(\"<dir>/SKILL.md\") + path.glob(\"skills/<dir>/SKILL.md\") (plugin layout support). */\nfunction findSkillMdOneLevelDown(dir: string): string[] {\n const matches = new Set<string>();\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.isDirectory()) {\n const candidate = join(dir, entry.name, 'SKILL.md');\n if (existsSync(candidate)) matches.add(candidate);\n }\n }\n const skillsDir = join(dir, 'skills');\n if (isDir(skillsDir)) {\n for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {\n if (entry.isDirectory()) {\n const candidate = join(skillsDir, entry.name, 'SKILL.md');\n if (existsSync(candidate)) matches.add(candidate);\n }\n }\n }\n return Array.from(matches);\n}\n\nfunction isZipFile(p: string): boolean {\n try {\n const fd = openSync(p, 'r');\n const buf = Buffer.alloc(4);\n const bytesRead = readSync(fd, buf, 0, 4, 0);\n closeSync(fd);\n if (bytesRead < 4) return false;\n // ZIP local file header (PK\\x03\\x04), empty archive (PK\\x05\\x06), or spanned (PK\\x07\\x08)\n return buf[0] === 0x50 && buf[1] === 0x4b && (buf[2] === 0x03 || buf[2] === 0x05 || buf[2] === 0x07);\n } catch {\n return false;\n }\n}\n\nfunction extractZip(zipPath: string, destDir: string): void {\n try {\n execFileSync('unzip', ['-q', '-o', zipPath, '-d', destDir], { stdio: ['ignore', 'ignore', 'pipe'] });\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === 'ENOENT') {\n throw new Error(\n 'unsupported without unzip: .skill archives require the system `unzip` binary, which was not found on ' +\n 'PATH. Install unzip, or extract the archive yourself and point profileSkill at the resulting directory.'\n );\n }\n throw new Error(`failed to extract ${zipPath}: ${e.message ?? String(err)}`);\n }\n}\n\ninterface Frontmatter {\n validFrontmatter: boolean;\n name?: string;\n version?: string;\n license?: string;\n description?: string;\n}\n\n/** Mirrors text.split(\"---\", 2)[1] — the block between the first two \"---\" delimiters. */\nfunction frontmatterBlock(text: string): string {\n const first = text.indexOf('---');\n const rest = text.slice(first + 3);\n const second = rest.indexOf('---');\n return second === -1 ? rest : rest.slice(0, second);\n}\n\nfunction extractSimpleField(block: string, key: string): string | undefined {\n const re = new RegExp(`^${key}:\\\\s*(.+)$`, 'm');\n const m = block.match(re);\n if (!m) return undefined;\n return stripQuotes(m[1].trim());\n}\n\nfunction extractDescription(block: string): string | undefined {\n const m = block.match(/^description:\\s*(.*)$/m);\n if (!m) return undefined;\n const first = m[1].trim();\n if (['>', '>-', '|', '|-', ''].includes(first)) {\n const lines = block.split('\\n');\n const idx = lines.findIndex((l) => /^description:/.test(l));\n if (idx === -1) return undefined;\n const buf: string[] = [];\n for (let i = idx + 1; i < lines.length; i++) {\n const l = lines[i];\n if (/^\\s+\\S/.test(l)) {\n buf.push(l.trim());\n } else if (l.trim() === '') {\n continue;\n } else {\n break;\n }\n }\n return buf.join(' ').trim();\n }\n return stripQuotes(first);\n}\n\nfunction parseFrontmatter(text: string): Frontmatter {\n if (!text.startsWith('---\\n')) {\n return { validFrontmatter: false };\n }\n const block = frontmatterBlock(text);\n return {\n validFrontmatter: true,\n name: extractSimpleField(block, 'name'),\n version: extractSimpleField(block, 'version'),\n license: extractSimpleField(block, 'license'),\n description: extractDescription(block),\n };\n}\n\nfunction detectLicense(root: string, fm: Frontmatter): string {\n if (fm.license) return fm.license;\n const names = ['LICENSE', 'LICENSE.md', 'LICENSE.txt', 'COPYING', 'license'];\n for (const name of names) {\n const p = join(root, name);\n if (existsSync(p) && statSync(p).isFile()) {\n const head = readFileSync(p, 'utf-8').slice(0, 2000).toLowerCase();\n for (const [hint, label] of Object.entries(SPDX_HINTS)) {\n if (head.includes(hint)) return label;\n }\n return 'present but unrecognized';\n }\n }\n return 'NONE STATED';\n}\n\nfunction walkFiles(dir: string): string[] {\n const out: string[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n out.push(...walkFiles(full));\n } else if (entry.isFile()) {\n out.push(full);\n }\n }\n return out;\n}\n\nfunction countResources(root: string): Record<string, number> {\n const resources: Record<string, number> = {};\n for (const d of RESOURCE_DIRS) {\n const dp = join(root, d);\n if (isDir(dp)) {\n resources[d] = walkFiles(dp).length;\n }\n }\n return resources;\n}\n\nfunction scanMcpAndImports(text: string, root: string): { mcpDeps: string[]; externalDeps: string[] } {\n const mcpRefRe = /mcp__[a-zA-Z0-9_]+/g;\n const mcpRefs = new Set(text.match(mcpRefRe) ?? []);\n const imports = new Set<string>();\n\n const scriptsDir = join(root, 'scripts');\n if (isDir(scriptsDir)) {\n for (const f of walkFiles(scriptsDir)) {\n if (!f.endsWith('.py')) continue;\n const ft = readFileSync(f, 'utf-8');\n for (const m of ft.match(mcpRefRe) ?? []) mcpRefs.add(m);\n const importRe = /^\\s*(?:import|from)\\s+([a-zA-Z0-9_]+)/gm;\n let m: RegExpExecArray | null;\n while ((m = importRe.exec(ft)) !== null) {\n imports.add(m[1]);\n }\n }\n }\n\n return {\n mcpDeps: Array.from(mcpRefs).sort(),\n externalDeps: Array.from(imports)\n .filter((i) => !PYTHON_STDLIB.has(i))\n .sort(),\n };\n}\n\nfunction profileSkillDir(inputPath: string): SkillProfile {\n const { skillMd, root, tmpDir } = resolveSkillMd(inputPath);\n try {\n const text = readFileSync(skillMd, 'utf-8');\n const fm = parseFrontmatter(text);\n const bodyLines = (text.match(/\\n/g)?.length ?? 0) + 1;\n const headers = Array.from(text.matchAll(/^#{1,3}\\s+(.+)$/gm)).map((m) => m[1]);\n const resources = countResources(root);\n const { mcpDeps, externalDeps } = scanMcpAndImports(text, root);\n\n return {\n kind: 'skill',\n inputPath,\n skillMd,\n name: fm.name,\n version: fm.version,\n validFrontmatter: fm.validFrontmatter,\n description: fm.description,\n license: detectLicense(root, fm),\n bodyLines,\n headerCount: headers.length,\n topHeaders: headers.slice(0, 12),\n resources,\n mcpDependencies: mcpDeps,\n externalPythonDeps: externalDeps,\n artifactType: detectArtifactType(root),\n };\n } finally {\n cleanupTmp(tmpDir);\n }\n}\n\nfunction profilePlugin(p: string): PluginProfile {\n const manifestPath = join(p, '.claude-plugin', 'plugin.json');\n let manifest: Record<string, unknown>;\n try {\n manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));\n } catch (err) {\n throw new Error(`could not parse plugin.json under ${p}: ${(err as Error).message}`);\n }\n\n const skillsDir = join(p, 'skills');\n let skills: string[] = [];\n if (isDir(skillsDir)) {\n skills = readdirSync(skillsDir, { withFileTypes: true })\n .filter((d) => d.isDirectory() && existsSync(join(skillsDir, d.name, 'SKILL.md')))\n .map((d) => d.name)\n .sort();\n }\n\n const commandsDir = join(p, 'commands');\n const commands = isDir(commandsDir)\n ? readdirSync(commandsDir).filter((f) => f.endsWith('.md')).length\n : 0;\n\n const mc = (manifest.mcpServers ?? manifest.mcp_servers ?? {}) as Record<string, unknown>;\n const mcpServers = typeof mc === 'object' && mc !== null ? Object.keys(mc).sort() : [];\n\n return {\n kind: 'plugin',\n inputPath: p,\n name: manifest.name as string | undefined,\n version: manifest.version as string | undefined,\n description: manifest.description as string | undefined,\n skills,\n skillCount: skills.length,\n commands,\n mcpServers,\n artifactType: detectArtifactType(p),\n };\n}\n\nfunction profileMcp(p: string): McpProfile {\n const explicitConfig = existsSync(join(p, '.mcp.json'))\n ? join(p, '.mcp.json')\n : existsSync(join(p, 'mcp.json'))\n ? join(p, 'mcp.json')\n : undefined;\n\n if (explicitConfig) {\n let cfg: Record<string, unknown>;\n try {\n cfg = JSON.parse(readFileSync(explicitConfig, 'utf-8'));\n } catch (err) {\n throw new Error(`could not parse MCP config ${explicitConfig}: ${(err as Error).message}`);\n }\n\n const servers = (cfg.mcpServers ?? cfg.servers ?? {}) as Record<string, unknown>;\n const names = typeof servers === 'object' && servers !== null ? Object.keys(servers).sort() : [];\n\n return {\n kind: 'mcp',\n inputPath: p,\n config: explicitConfig,\n servers: names,\n serverCount: names.length,\n artifactType: 'mcp',\n capabilities: extractMcpCapabilities(p),\n };\n }\n\n // No .mcp.json/mcp.json — this is an MCP server package identified by its own\n // package.json markers (see hasMcpPackageMarkers / detectArtifactType).\n const pkgPath = join(p, 'package.json');\n let pkg: Record<string, unknown>;\n try {\n pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n } catch (err) {\n throw new Error(`could not parse package.json ${pkgPath}: ${(err as Error).message}`);\n }\n const name = typeof pkg.name === 'string' && pkg.name.length > 0 ? pkg.name : basename(p);\n\n return {\n kind: 'mcp',\n inputPath: p,\n config: pkgPath,\n servers: [name],\n serverCount: 1,\n artifactType: 'mcp',\n capabilities: extractMcpCapabilities(p),\n };\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { extname, join } from 'node:path';\nimport { readTextFile, walk } from './scanShared.js';\n\n/**\n * v0.6: static, best-effort MCP server capability profiling.\n *\n * STATIC ONLY — this module reads files as plain text and JSON. It never\n * dynamically imports, requires, spawns, or otherwise executes anything from\n * the profiled package to discover its tools/resources/prompts. Anything\n * that can't be determined from source text is left out of the result\n * rather than guessed at by running the server — see `declaredConfidence`.\n *\n * Signals parsed, in order of strength:\n * - source text of .ts/.tsx/.js/.jsx/.mjs/.cjs files (node_modules/.git/dist\n * skipped): MCP SDK registration call patterns —\n * server.tool(...)/server.registerTool(...)/server.resource(...)/server.prompt(...)\n * — plus a best-effort scan of setRequestHandler(List*RequestSchema, ...)\n * handler bodies for name/uri literals. Presence of either -> 'high'.\n * - a shipped .mcp.json / mcp.json manifest, if its declared server entries\n * carry an explicit tools/resources/prompts list (uncommon, but checked).\n * - package.json: bin names, keywords, an `@modelcontextprotocol/sdk`\n * dependency, or an `mcp` field — presence-only signal (no capability\n * names), used for 'partial' confidence when nothing more specific was\n * found in source text.\n */\n\nexport type McpTransport = 'stdio' | 'http' | 'unknown';\nexport type DeclaredConfidence = 'high' | 'partial' | 'none';\n\nexport interface McpCapabilities {\n tools: string[];\n resources: string[];\n prompts: string[];\n transport?: McpTransport;\n declaredConfidence: DeclaredConfidence;\n}\n\nconst SOURCE_EXT = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);\nconst MAX_ITEMS_PER_CATEGORY = 200;\n\n/** The three capability categories, always collected together as one set of buckets. */\ntype Category = 'tools' | 'resources' | 'prompts';\ntype Buckets = Record<Category, Set<string>>;\n\nfunction readJsonSafe(file: string): unknown {\n try {\n return JSON.parse(readFileSync(file, 'utf-8'));\n } catch {\n return undefined;\n }\n}\n\n// ---- package.json markers (presence-only signal; never yields capability names) ----\n\nconst MCP_SDK_DEP = '@modelcontextprotocol/sdk';\nconst MCP_KEYWORD = 'mcp-server';\nconst MCP_BIN_PREFIX = /^mcp-/i;\n\nfunction hasPackageJsonMcpMarkers(pkg: Record<string, unknown>): boolean {\n for (const section of ['dependencies', 'devDependencies', 'peerDependencies'] as const) {\n const deps = pkg[section];\n if (deps && typeof deps === 'object' && MCP_SDK_DEP in (deps as Record<string, unknown>)) return true;\n }\n\n const keywords = pkg.keywords;\n if (Array.isArray(keywords) && keywords.some((k) => k === MCP_KEYWORD)) return true;\n\n const bin = pkg.bin;\n if (typeof bin === 'string') {\n if (MCP_BIN_PREFIX.test(bin.split('/').pop() ?? bin)) return true;\n } else if (bin && typeof bin === 'object') {\n if (Object.keys(bin as Record<string, unknown>).some((k) => MCP_BIN_PREFIX.test(k))) return true;\n }\n\n if (pkg.mcp && typeof pkg.mcp === 'object') return true;\n\n return false;\n}\n\n// ---- .mcp.json / mcp.json manifest: declared tools/resources/prompts arrays, if present ----\n\nfunction collectDeclaredFromManifest(node: unknown, buckets: Buckets): void {\n if (Array.isArray(node)) {\n for (const item of node) collectDeclaredFromManifest(item, buckets);\n return;\n }\n if (!node || typeof node !== 'object') return;\n\n const obj = node as Record<string, unknown>;\n for (const [key, value] of Object.entries(obj)) {\n if ((key === 'tools' || key === 'resources' || key === 'prompts') && Array.isArray(value)) {\n const target = buckets[key];\n for (const item of value) {\n if (typeof item === 'string') {\n target.add(item);\n } else if (item && typeof item === 'object') {\n const name = (item as Record<string, unknown>).name ?? (item as Record<string, unknown>).uri;\n if (typeof name === 'string') target.add(name);\n }\n }\n } else {\n collectDeclaredFromManifest(value, buckets);\n }\n }\n}\n\n// ---- MCP SDK registration call patterns ----\n// Single pass over server.tool()/registerTool()/resource()/prompt(): the method name\n// (group 1) routes the first string arg (group 2) to its category — tool and registerTool\n// both feed `tools`.\n\nconst REGISTRATION_CALL_RE = /\\bserver\\.(tool|registerTool|resource|prompt)\\(\\s*['\"]([^'\"]+)['\"]/g;\nconst REGISTRATION_METHOD_CATEGORY: Record<string, Category> = {\n tool: 'tools',\n registerTool: 'tools',\n resource: 'resources',\n prompt: 'prompts',\n};\n\nfunction captureRegistrations(text: string, buckets: Buckets): void {\n REGISTRATION_CALL_RE.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = REGISTRATION_CALL_RE.exec(text)) !== null) {\n buckets[REGISTRATION_METHOD_CATEGORY[m[1]]].add(m[2]);\n }\n}\n\n// ---- best-effort setRequestHandler(List*RequestSchema, ...) body scan ----\n\nconst LIST_HANDLER_SCHEMAS: Record<string, Category> = {\n ListToolsRequestSchema: 'tools',\n ListResourcesRequestSchema: 'resources',\n ListPromptsRequestSchema: 'prompts',\n};\nconst SET_REQUEST_HANDLER_RE =\n /setRequestHandler\\(\\s*(ListToolsRequestSchema|ListResourcesRequestSchema|ListPromptsRequestSchema)\\s*,/g;\nconst NAME_OR_URI_FIELD_RE = /\\b(?:name|uri)\\s*:\\s*['\"]([^'\"]+)['\"]/g;\n\n/** Returns the text of the balanced `(...)` call starting at `openParenIndex`, or a capped slice if unbalanced. */\nfunction extractBalancedCall(text: string, openParenIndex: number): string {\n let depth = 0;\n for (let i = openParenIndex; i < text.length; i++) {\n const ch = text[i];\n if (ch === '(') depth++;\n else if (ch === ')') {\n depth--;\n if (depth === 0) return text.slice(openParenIndex, i + 1);\n }\n }\n return text.slice(openParenIndex, Math.min(text.length, openParenIndex + 20000));\n}\n\nfunction extractFromListHandlers(text: string, buckets: Buckets): void {\n SET_REQUEST_HANDLER_RE.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = SET_REQUEST_HANDLER_RE.exec(text)) !== null) {\n const openParenIdx = text.indexOf('(', m.index);\n if (openParenIdx === -1) continue;\n const body = extractBalancedCall(text, openParenIdx);\n const target = buckets[LIST_HANDLER_SCHEMAS[m[1]]];\n\n NAME_OR_URI_FIELD_RE.lastIndex = 0;\n let fieldMatch: RegExpExecArray | null;\n while ((fieldMatch = NAME_OR_URI_FIELD_RE.exec(body)) !== null) {\n target.add(fieldMatch[1]);\n }\n }\n}\n\n// ---- transport hints ----\n\nconst STDIO_TRANSPORT_RE = /StdioServerTransport/;\nconst HTTP_TRANSPORT_RE = /StreamableHTTPServerTransport|SSEServerTransport|WebSocketServerTransport/;\nconst MCP_SDK_IMPORT_RE = /@modelcontextprotocol\\/sdk/;\n\nexport function extractMcpCapabilities(packageDir: string): McpCapabilities {\n const buckets: Buckets = { tools: new Set(), resources: new Set(), prompts: new Set() };\n\n let hasPackageJsonMarker = false;\n const pkgPath = join(packageDir, 'package.json');\n if (existsSync(pkgPath)) {\n const pkg = readJsonSafe(pkgPath);\n if (pkg && typeof pkg === 'object' && !Array.isArray(pkg)) {\n hasPackageJsonMarker = hasPackageJsonMcpMarkers(pkg as Record<string, unknown>);\n }\n }\n\n for (const manifestName of ['.mcp.json', 'mcp.json']) {\n const manifestPath = join(packageDir, manifestName);\n if (!existsSync(manifestPath)) continue;\n const parsed = readJsonSafe(manifestPath);\n if (parsed !== undefined) {\n collectDeclaredFromManifest(parsed, buckets);\n }\n }\n\n let sawStdioTransport = false;\n let sawHttpTransport = false;\n let sawMcpSdkImport = false;\n\n for (const file of walk(packageDir)) {\n if (!SOURCE_EXT.has(extname(file))) continue;\n const text = readTextFile(file);\n if (text === undefined) continue;\n\n captureRegistrations(text, buckets);\n extractFromListHandlers(text, buckets);\n\n if (STDIO_TRANSPORT_RE.test(text)) sawStdioTransport = true;\n if (HTTP_TRANSPORT_RE.test(text)) sawHttpTransport = true;\n if (MCP_SDK_IMPORT_RE.test(text)) sawMcpSdkImport = true;\n }\n\n const transport: McpTransport | undefined = sawStdioTransport\n ? 'stdio'\n : sawHttpTransport\n ? 'http'\n : sawMcpSdkImport\n ? 'unknown'\n : undefined;\n\n const sortCap = (set: Set<string>): string[] => Array.from(set).sort().slice(0, MAX_ITEMS_PER_CATEGORY);\n const tools = sortCap(buckets.tools);\n const resources = sortCap(buckets.resources);\n const prompts = sortCap(buckets.prompts);\n\n const hasExplicitSignal = tools.length > 0 || resources.length > 0 || prompts.length > 0;\n const declaredConfidence: DeclaredConfidence = hasExplicitSignal\n ? 'high'\n : hasPackageJsonMarker\n ? 'partial'\n : 'none';\n\n return {\n tools,\n resources,\n prompts,\n transport,\n declaredConfidence,\n };\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { expandHome } from '../util.js';\nimport { suggestVerb, type OverlapResult, type SkillOverlapScore } from './overlap.js';\n\n/**\n * MCP overlap gate (v0.5) — ranks a candidate MCP server against the server\n * entries already present in the user's detected mcp config files, by name\n * similarity + same package/command. No canonical Python source exists for\n * this one (unlike gate/overlap.ts's skill overlap); it's new for v0.5.\n *\n * Returns the same `OverlapResult` shape gate/overlap.ts's scanOverlap()\n * returns (and reuses its score-tier suggestVerb()) so `report/render.ts`\n * renders an MCP candidate's overlap without any changes: `ranking[].skill`\n * holds the existing server's name, `ranking[].path` the config file it came\n * from.\n */\n\n/**\n * Minimal shape scanMcpOverlap needs to describe the candidate being added.\n * Deliberately decoupled from src/gate/profile.ts's McpProfile — callers\n * adapt whatever candidate profile they built (npm package.json, a local\n * dir's mcp.json, etc.) into this shape.\n */\nexport interface McpOverlapCandidate {\n /** Candidate server name (the config key it would be promoted under). */\n name: string;\n /** Launch command (e.g. \"npx\", \"node\", a binary path) — bin/entry hint. */\n command?: string;\n /** Launch args — used for command/package similarity (e.g. [\"-y\", \"@scope/pkg\"]). */\n args?: string[];\n /** npm package name, when the candidate is an npm-sourced MCP server. */\n packageName?: string;\n}\n\ninterface McpTargetServer {\n name: string;\n command?: string;\n args?: string[];\n}\n\n/** Reads one mcp config file's `mcpServers` (falling back to `servers`) map. Missing/malformed files yield no entries. */\nfunction readTargetServers(targetPath: string): McpTargetServer[] {\n const path = resolve(expandHome(targetPath));\n if (!existsSync(path)) return [];\n\n let cfg: Record<string, unknown>;\n try {\n cfg = JSON.parse(readFileSync(path, 'utf-8'));\n } catch {\n return [];\n }\n\n const servers = (cfg.mcpServers ?? cfg.servers ?? {}) as Record<string, unknown>;\n if (typeof servers !== 'object' || servers === null) return [];\n\n return Object.entries(servers).map(([name, raw]) => {\n const v = (raw ?? {}) as Record<string, unknown>;\n return {\n name,\n command: typeof v.command === 'string' ? v.command : undefined,\n args: Array.isArray(v.args) ? v.args.filter((a): a is string => typeof a === 'string') : undefined,\n };\n });\n}\n\nfunction tokenizeName(name: string): Set<string> {\n return new Set(name.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean));\n}\n\n/** Exact match = 1; else Jaccard token overlap on the name split by non-alphanumerics. */\nfunction nameSimilarity(a: string, b: string): number {\n if (a.toLowerCase() === b.toLowerCase()) return 1;\n const ta = tokenizeName(a);\n const tb = tokenizeName(b);\n if (ta.size === 0 || tb.size === 0) return 0;\n let inter = 0;\n for (const t of ta) if (tb.has(t)) inter += 1;\n const union = new Set([...ta, ...tb]).size;\n return union ? inter / union : 0;\n}\n\n/** Strips a trailing \"@version\" from a package spec without breaking a leading \"@scope/name\". */\nfunction packageBaseName(spec: string): string {\n const searchFrom = spec.startsWith('@') ? 1 : 0;\n const atIdx = spec.indexOf('@', searchFrom);\n return atIdx === -1 ? spec : spec.slice(0, atIdx);\n}\n\n/** Identifies the package/binary a launch command targets: the first non-flag arg for a package-runner command, else the command's basename (so a bare binary name matches a full path to the same binary). */\nfunction extractPackageSpec(command?: string, args: string[] = []): string | undefined {\n if (!command) return undefined;\n const base = command.split('/').pop() ?? command;\n if (['npx', 'npm', 'pnpm', 'yarn', 'uvx'].includes(base)) {\n return args.find((a) => !a.startsWith('-'));\n }\n return base;\n}\n\n/** 1 if candidate and target resolve to the same package/binary identity, else 0. */\nfunction commandSimilarity(\n candidate: McpOverlapCandidate,\n target: Pick<McpTargetServer, 'command' | 'args'>,\n): number {\n const candSpec = candidate.packageName ?? extractPackageSpec(candidate.command, candidate.args ?? []);\n const targetSpec = extractPackageSpec(target.command, target.args ?? []);\n if (!candSpec || !targetSpec) return 0;\n return packageBaseName(candSpec).toLowerCase() === packageBaseName(targetSpec).toLowerCase() ? 1 : 0;\n}\n\nfunction round3(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n\nfunction score(candidate: McpOverlapCandidate, target: McpTargetServer): number {\n return round3(0.6 * nameSimilarity(candidate.name, target.name) + 0.4 * commandSimilarity(candidate, target));\n}\n\n/**\n * Ranks `candidate` against every server entry found across `mcpTargetPaths`\n * (each file's `mcpServers` map). Unlike scanOverlap(), a target file that's\n * missing or empty isn't an error — a brand-new mcp config with zero servers\n * is the normal case for a first MCP install — so this returns an\n * empty-ranking result (`nearest: ''`, `nearestScore: 0`) instead of\n * throwing.\n */\nexport function scanMcpOverlap(candidate: McpOverlapCandidate, mcpTargetPaths: string[]): OverlapResult {\n const ranking: SkillOverlapScore[] = [];\n\n for (const targetPath of mcpTargetPaths) {\n const resolvedPath = resolve(expandHome(targetPath));\n for (const target of readTargetServers(targetPath)) {\n ranking.push({ skill: target.name, path: resolvedPath, score: score(candidate, target) });\n }\n }\n ranking.sort((a, b) => b.score - a.score);\n\n const top = ranking[0];\n return {\n candidate: candidate.name,\n nearest: top?.skill ?? '',\n nearestScore: top?.score ?? 0,\n suggestedVerb: suggestVerb(top?.score ?? 0),\n ranking,\n };\n}\n","import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { expandHome } from '../util.js';\n\n/**\n * Shared loader + types for the generated skill-map artifact produced by the\n * `rhize-plugins` repo's `scripts/build_skill_map.py` (schema:\n * `schemas/skill-map.schema.json` there — see that repo's\n * `.claude/plans/skill-map-graph-substrate.md`, Phase 4). skill-forge never\n * generates or writes this file; it only ever READS it as data.\n *\n * The map is deliberately treated as an *external, untrusted-shape* JSON\n * document here: at the time this loader was written the schema was still\n * being finalized in a parallel workstream, and skills roots are exactly the\n * kind of place a hostile or malformed file could land. Every field access\n * downstream (`skillMapDrift.ts`, `gate/mapOverlap.ts`) treats node/edge\n * fields as plain data — nothing from a loaded map is ever executed,\n * interpreted as a command, or passed to a shell. See those modules' own doc\n * comments for the specific never-execute invariant each enforces.\n */\n\nexport interface SkillMapNode {\n id: string;\n type: string;\n name?: string;\n description?: string;\n path?: string;\n contentHash?: string;\n /** Upstream location for `external` nodes referenced by a `fork-of` edge — a URL or a local path (fixtures/tests). Never executed. */\n url?: string;\n tags?: string[];\n [key: string]: unknown;\n}\n\nexport interface SkillMapEdge {\n from: string;\n to: string;\n type: string;\n source?: string;\n /**\n * Fork-of edges may carry drift-check metadata recorded at ingest time (mirrors\n * `provenance.ts`'s `driftCheck` field on a SOURCES.md entry). This is DATA ONLY —\n * no field of it is ever read as a command to run. skill-forge derives its own\n * drift-check behavior (contentHash compare) rather than trusting anything stored here.\n */\n driftCheck?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface SkillMap {\n schemaVersion?: string;\n nodes: SkillMapNode[];\n edges: SkillMapEdge[];\n}\n\n/** Reads and minimally validates a skill-map JSON file. Throws with a descriptive message on any failure — callers decide whether that's fatal or a skippable warning. */\nexport function loadSkillMap(path: string): SkillMap {\n const resolved = resolve(expandHome(path));\n let text: string;\n try {\n text = readFileSync(resolved, 'utf-8');\n } catch (err) {\n throw new Error(`could not read skill map at ${resolved}: ${(err as Error).message}`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (err) {\n throw new Error(`skill map at ${resolved} is not valid JSON: ${(err as Error).message}`);\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n throw new Error(`skill map at ${resolved}: expected a JSON object`);\n }\n const obj = parsed as Record<string, unknown>;\n if (!Array.isArray(obj.nodes) || !Array.isArray(obj.edges)) {\n throw new Error(`skill map at ${resolved}: expected \"nodes\" and \"edges\" arrays`);\n }\n\n return {\n schemaVersion: typeof obj.schemaVersion === 'string' ? obj.schemaVersion : undefined,\n nodes: obj.nodes as SkillMapNode[],\n edges: obj.edges as SkillMapEdge[],\n };\n}\n\nexport function nodeById(map: SkillMap, id: string): SkillMapNode | undefined {\n return map.nodes.find((n) => n.id === id);\n}\n\nexport function edgesOfType(map: SkillMap, type: string): SkillMapEdge[] {\n return map.edges.filter((e) => e.type === type);\n}\n\n/**\n * Tags for a skill node: its own denormalized `tags` field (if the compiler emits one),\n * plus the names of any `tag` nodes reached via `topic-tag`/`stack-tag` edges FROM this\n * node — tolerant of either representation since the schema was still settling when this\n * was written.\n */\nexport function tagsForNode(map: SkillMap, node: SkillMapNode): string[] {\n const tags = new Set<string>(node.tags ?? []);\n for (const edge of map.edges) {\n if (edge.from !== node.id) continue;\n if (edge.type !== 'topic-tag' && edge.type !== 'stack-tag') continue;\n const tagNode = nodeById(map, edge.to);\n const label = tagNode?.name ?? tagNode?.id;\n if (label) tags.add(label);\n }\n return Array.from(tags);\n}\n","import type { Severity, SafetyFinding } from './scanShared.js';\nimport { tagsForNode, type SkillMap, type SkillMapNode } from './skillMap.js';\n\n/**\n * Ingest-gate overlap check against the generated `rhize-plugins` skill map (Phase 4 of\n * `.claude/plans/skill-map-graph-substrate.md`) — implements that marketplace's own\n * CLAUDE.md curation rule at the point of ingest: \"a new skill must close a gap, never\n * duplicate.\" This is deliberately a SEPARATE, lexical-only check from `gate/overlap.ts`'s\n * local skillsRoot overlap: `overlap.ts` ranks a candidate against skills already installed\n * on disk; this module ranks it against every `skill` node the map knows about across the\n * whole marketplace (including skills not installed in this particular skillsRoot).\n *\n * Scoring intentionally mirrors `gate/overlap.ts`'s Jaccard + containment formula and its\n * ABSORB/FORK/DEFER score tiers (0.45 / 0.2) for consistency — this module does not invent\n * a second scoring scale. No embeddings; pure token overlap.\n */\n\nconst STOPWORDS = new Set(\n `\na an the and or of to for in on with use used using when whether this that these those is are be\nyour you it its as at by from into via per also any all not no than then so if up out about\nskill skills user claude code based comprehensive enhanced practical including include includes\n`\n .split(/\\s+/)\n .filter(Boolean),\n);\n\n/** Same tiers as `gate/overlap.ts`'s VERB_TIERS — kept in sync manually since the two live in different license tiers (this module is MIT; overlap.ts is Pro). */\nconst STRONG_THRESHOLD = 0.45;\nconst REPORT_THRESHOLD = 0.2;\n\nexport interface MapOverlapCandidate {\n name?: string;\n description?: string;\n}\n\nexport interface MapOverlapMatch {\n skillId: string;\n name: string;\n score: number;\n}\n\nexport interface MapOverlapResult {\n /** All skill-node matches at or above REPORT_THRESHOLD, sorted by score descending. Empty when nothing meets the threshold. */\n matches: MapOverlapMatch[];\n}\n\nfunction tokenize(text: string): Set<string> {\n const matches = text.toLowerCase().match(/[a-z][a-z0-9+.-]{2,}/g) ?? [];\n return new Set(matches.filter((t) => !STOPWORDS.has(t)));\n}\n\nfunction intersectSize(a: Set<string>, b: Set<string>): number {\n let n = 0;\n for (const x of a) if (b.has(x)) n += 1;\n return n;\n}\n\nfunction unionSize(a: Set<string>, b: Set<string>): number {\n const u = new Set(a);\n for (const x of b) u.add(x);\n return u.size;\n}\n\nfunction round3(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n\nfunction score(candidateTokens: Set<string>, otherTokens: Set<string>): number {\n if (candidateTokens.size === 0) return 0;\n const inter = intersectSize(candidateTokens, otherTokens);\n const union = unionSize(candidateTokens, otherTokens);\n const jaccard = union ? inter / union : 0;\n const containment = inter / candidateTokens.size;\n return round3(0.3 * jaccard + 0.7 * containment);\n}\n\nfunction nodeText(map: SkillMap, node: SkillMapNode): string {\n const tags = tagsForNode(map, node);\n return [node.name ?? '', node.description ?? '', ...tags].join(' ');\n}\n\n/**\n * Ranks `candidate` (the ingest gate's profiled name/description) against every `skill`\n * node in `map`. Returns every match at or above REPORT_THRESHOLD — not just the top one —\n * since a candidate can meaningfully overlap more than one existing skill (e.g. two\n * clusters of an already-fragmented capability).\n */\nexport function scanMapOverlap(candidate: MapOverlapCandidate, map: SkillMap): MapOverlapResult {\n const candidateTokens = tokenize(`${candidate.name ?? ''} ${candidate.description ?? ''}`);\n if (candidateTokens.size === 0) return { matches: [] };\n\n const matches: MapOverlapMatch[] = [];\n for (const node of map.nodes) {\n if (node.type !== 'skill') continue;\n const otherTokens = tokenize(nodeText(map, node));\n const s = score(candidateTokens, otherTokens);\n if (s >= REPORT_THRESHOLD) {\n matches.push({ skillId: node.id, name: node.name ?? node.id, score: s });\n }\n }\n matches.sort((a, b) => b.score - a.score);\n return { matches };\n}\n\n/**\n * Turns the top map-overlap match (if any) into a single gate finding, using the same\n * severity vocabulary `gate/safety.ts`/`gate/scanShared.ts` already use — so it flows\n * through the existing `verdictFor()` severity->verdict mapping instead of inventing a new\n * one. A match at/above STRONG_THRESHOLD (mirrors overlap.ts's ABSORB tier — \"this already\n * exists, don't re-ship it\") is severity HIGH, which `verdictFor` escalates to `block` under\n * the default `block-high` strictness: the candidate is held, not silently promoted. A\n * lesser match (mirrors the FORK tier) is MEDIUM, which surfaces as `warn` — hold is the\n * interactive default, but doesn't block outright. Below REPORT_THRESHOLD, no finding is\n * produced (the report already carries the full `matches` list for informational display\n * regardless of this).\n */\nexport function mapOverlapFinding(result: MapOverlapResult): SafetyFinding | undefined {\n const top = result.matches[0];\n if (!top) return undefined;\n\n const severity: Severity = top.score >= STRONG_THRESHOLD ? 'HIGH' : 'MEDIUM';\n return {\n severity,\n rule: 'skill-map-overlap',\n file: 'skill-map',\n detail: `candidate overlaps existing marketplace skill \"${top.name}\" (score ${top.score}) — see CLAUDE.md's curation rule: close the gap, don't duplicate`,\n context: top.skillId,\n };\n}\n","import type { Profile, McpProfile } from '../gate/profile.js';\nimport type { OverlapResult } from '../gate/overlap.js';\nimport type { SafetyFinding as GateSafetyFinding, Verdict as GateVerdict } from '../gate/safety.js';\nimport { upgradeNotice } from '../license.js';\n\n/**\n * Report renderer — Step 3 of the gate pipeline (profile -> safety scan ->\n * overlap analysis -> report -> promote/reject). Renders the human-readable\n * decision box and a `--json` equivalent.\n */\n\nexport type SafetyVerdict = 'ALLOW' | 'CAUTION' | 'BLOCK' | 'UNKNOWN';\n\nexport interface SafetyFinding {\n severity?: string;\n rule?: string;\n message?: string;\n}\n\n/** Mirrors the verdict shape emitted by skill_safety.py (SkillSpector wrapper). */\nexport interface SafetyResult {\n verdict: SafetyVerdict;\n severity?: string;\n score?: number;\n recommendation?: string;\n findings?: SafetyFinding[];\n}\n\n/**\n * Aggregate result of the quarantine gate pipeline for a single skill\n * candidate — the input to renderReport / renderJson. `worthTaking` and\n * `leaveBehind` are placeholders: they're only populated once a human (or a\n * later ABSORB step) has actually decided what to extract, so they render as\n * pending until then.\n */\nexport interface GateResult {\n profile: Profile;\n safety: SafetyResult;\n overlap?: OverlapResult;\n /** True when overlap analysis was skipped for lack of a Pro license (as opposed to a missing skills root). */\n overlapLocked?: boolean;\n worthTaking?: string[];\n leaveBehind?: string[];\n /** Pro-feature beta/upgrade notices (see src/license.ts's proFeatureStatus) — printed above the terminal report, or carried as a JSON field for --json. */\n notices?: string[];\n /**\n * Audit-state context lines (src/auditState.ts), gathered once by `runGate` so every gate flow\n * gets them without each call site remembering to. Rendered BELOW the verdict and deliberately\n * omitted from `renderJson` — advisory text must never be mistakable for a gate result.\n */\n advisory?: string[];\n}\n\n/** Maps gate/safety.ts's pass|warn|block verdict to this module's ALLOW|CAUTION|BLOCK — shared by commands/gatePipeline.ts's runGate() and evolve.ts's re-gate step. */\nexport const SAFETY_VERDICT_MAP: Record<GateVerdict, SafetyVerdict> = {\n pass: 'ALLOW',\n warn: 'CAUTION',\n block: 'BLOCK',\n};\n\n/** Maps gate/safety.ts's {severity, rule, file, detail} findings into this module's {severity, rule, message} shape — shared by the same two call sites as SAFETY_VERDICT_MAP. */\nexport function mapSafetyFindings(findings: GateSafetyFinding[]): SafetyFinding[] {\n return findings.map((f) => ({ severity: f.severity, rule: f.rule, message: `${f.file}: ${f.detail}` }));\n}\n\nconst BORDER = '━'.repeat(55);\nconst LABEL_WIDTH = 20;\n\nfunction padLabel(text: string): string {\n return text.padEnd(LABEL_WIDTH);\n}\n\nfunction renderListField(labelText: string, items: string[] | undefined, placeholder: string): string {\n const head = `${padLabel(labelText)}: `;\n if (!items || items.length === 0) {\n return `${head}${placeholder}`;\n }\n const bullets = items.map((item) => ` - ${item}`).join('\\n');\n return `${head.trimEnd()}\\n${bullets}`;\n}\n\nfunction renderSafetyLine(safety: SafetyResult): string {\n const detail = [safety.severity, safety.score !== undefined ? `score ${safety.score}` : undefined]\n .filter((v): v is string => Boolean(v))\n .join(', ');\n const withDetail = detail ? `${safety.verdict} (${detail})` : safety.verdict;\n return safety.recommendation ? `${withDetail} — ${safety.recommendation}` : withDetail;\n}\n\nfunction renderFindingLine(finding: SafetyFinding): string {\n const severity = finding.severity ?? 'UNKNOWN';\n const rule = finding.rule ?? 'unknown-rule';\n return ` - [${severity}] ${rule}${finding.message ? `: ${finding.message}` : ''}`;\n}\n\n/**\n * Renders the safety findings list — severity, rule, and a one-line detail\n * per finding — so a blocked/cautioned skill shows *why* without needing\n * `--json`. Omitted entirely when there are no findings.\n */\nfunction renderFindingsLines(findings: SafetyFinding[] | undefined): string[] {\n if (!findings?.length) return [];\n return [`${padLabel('Findings')}:`, ...findings.map(renderFindingLine)];\n}\n\n/**\n * Header fields for the \"🔨 Forge: ...\" line — a skill profile has\n * name/version/license; an MCP profile (v0.5) has no such fields (it may\n * describe more than one server entry), so it renders the server name(s)\n * instead and 'n/a' for version/license. Plugin profiles have name/version\n * but no license (not currently reachable via add/scan's CLI routing).\n */\nfunction profileHeaderFields(profile: Profile): { name: string; version: string; license: string } {\n if (profile.kind === 'mcp') {\n const name = profile.serverCount === 1 ? profile.servers[0] : `${profile.serverCount} servers`;\n return { name, version: 'n/a', license: 'n/a' };\n }\n return {\n name: profile.name ?? 'unknown',\n version: profile.version ?? 'unknown',\n license: profile.kind === 'skill' ? profile.license : 'UNKNOWN',\n };\n}\n\nfunction pluralize(count: number, noun: string): string {\n return `${count} ${noun}${count === 1 ? '' : 's'}`;\n}\n\n/**\n * v0.6: one-line static capability summary for an MCP profile — tool/resource/prompt\n * counts plus `declaredConfidence`, so a candidate's declared surface is visible without\n * `--json`. Free tier (it's profiling, not the Pro overlap/decide pass) — always rendered\n * for `profile.kind === 'mcp'`, never gated on `overlapLocked`/`proEnabled`.\n */\nfunction renderCapabilitiesLine(profile: McpProfile): string {\n const { tools, resources, prompts, declaredConfidence } = profile.capabilities;\n const counts = [pluralize(tools.length, 'tool'), pluralize(resources.length, 'resource'), pluralize(prompts.length, 'prompt')].join(\n ', '\n );\n return `${padLabel('Capabilities')}: ${counts} (declaredConfidence: ${declaredConfidence})`;\n}\n\n/** Renders the Step-3 terminal decision box for a single gate result. */\nexport function renderReport(result: GateResult): string {\n const { profile, overlap, overlapLocked, safety, worthTaking, leaveBehind } = result;\n const { name, version, license } = profileHeaderFields(profile);\n\n // Locked (no Pro license): the two overlap lines collapse into one short\n // upgrade notice instead of \"n/a\" placeholders for data we never computed.\n const overlapLines = overlapLocked\n ? [`${padLabel('Overlap analysis')}: ${upgradeNotice('overlap analysis')}`]\n : overlap\n ? [\n `${padLabel('Nearest existing skill')}: ${overlap.nearest} (overlap ${overlap.nearestScore})`,\n `${padLabel('Recommended verb')}: ${overlap.suggestedVerb}`,\n ]\n : [`${padLabel('Nearest existing skill')}: n/a — no skills root configured`, `${padLabel('Recommended verb')}: n/a`];\n\n const lines: string[] = [\n BORDER,\n `🔨 Forge: ${name} (v${version}, ${license})`,\n BORDER,\n `${padLabel('Artifact type')}: ${profile.artifactType}`,\n ...(profile.kind === 'mcp' ? [renderCapabilitiesLine(profile)] : []),\n ...overlapLines,\n renderListField(\"What's worth taking\", worthTaking, '(pending — not yet determined)'),\n renderListField('What to leave behind', leaveBehind, '(pending — not yet determined)'),\n `${padLabel('Safety verdict')}: ${renderSafetyLine(safety)}`,\n ...renderFindingsLines(safety.findings),\n BORDER,\n ];\n\n return lines.join('\\n');\n}\n\n/**\n * Renders a gate result as JSON for `--json` output.\n *\n * `advisory` is stripped deliberately: it is audit CONTEXT about the set the candidate is landing\n * in, not part of the gate's answer about the candidate, and leaving it in the payload would let\n * consumers start branching on it as though it were a result. It goes to stderr instead (see\n * printGateOutput).\n */\nexport function renderJson(result: GateResult): string {\n const { advisory: _advisory, ...payload } = result;\n return JSON.stringify(payload, null, 2);\n}\n\n/**\n * Prints a gate result the same way for `add`/`scan`/`evolve`: any Pro-feature\n * notices then the terminal report box normally, or a single parseable JSON\n * payload with `--json` (notices ride along as the payload's `notices` field\n * instead of being printed above it). Moved here (v0.7 simplify pass) from\n * `commands/gatePipeline.ts`, which re-exports it so `add.ts`/`scan.ts`'s\n * existing import path keeps working.\n */\nexport function printGateOutput(gateResult: GateResult, json?: boolean): void {\n if (!json) {\n for (const notice of gateResult.notices ?? []) console.log(`[skill-forge] ${notice}`);\n }\n console.log(json ? renderJson(gateResult) : renderReport(gateResult));\n\n // v0.11 audit advisory (src/auditState.ts): context about the set the candidate is landing in,\n // printed BELOW the verdict so it can never be read as part of it. In --json mode it goes to\n // stderr, keeping stdout exactly one parseable document — and it is deliberately NOT a field of\n // the JSON payload, so no consumer can start branching on advisory text as though it were a\n // gate result.\n const advisory = gateResult.advisory ?? [];\n if (advisory.length === 0) return;\n if (json) {\n for (const line of advisory) console.error(`[skill-forge] ${line}`);\n return;\n }\n // First line takes the label column; the rest align under it.\n console.log(`${padLabel('Audit context')}: ${advisory[0]}`);\n for (const line of advisory.slice(1)) console.log(`${' '.repeat(LABEL_WIDTH + 2)}${line}`);\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { randomUUID } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { getHomeDir } from './config.js';\nimport type { Verdict as SafetyVerdict } from './gate/safety.js';\nimport type { SourceType } from './install/resolve.js';\nimport type { ForgeVerb } from './provenance.js';\n\n/**\n * Pending-ingestion queue — persists every skill install decided by `add`\n * (promote or hold) to `<SKILL_FORGE_HOME>/queue.json`, so the `--ingest`\n * Claude handoff (or a future `queue` command) can pick the entry back up.\n * The entry shape here, `schemas/queue-entry.schema.json`, and\n * `docs/queue-schema.md` are the single source of truth for the pending-queue\n * entry spec — keep all three in sync if the shape ever changes.\n */\n\n/** Suggested forge verb on a queue entry — the five-verb matrix defined by provenance.ts's FORGE_VERBS. */\nexport type SuggestedVerb = ForgeVerb;\nexport type { SafetyVerdict, SourceType };\n\n/** What kind of candidate this queue entry is for. Absent on an entry means 'skill' (pre-v0.5 entries stay valid). */\nexport type ArtifactType = 'skill' | 'mcp';\n\n/** How this entry was produced, when it isn't a normal `add`-sourced external install. Absent means a normal entry. 'evolve' (v0.7): self-evolved via `skill-forge evolve` (SkillOpt-Sleep) and already re-gated at adopt time. */\nexport type QueueEntryOrigin = 'evolve';\n\nexport type QueueStatus = 'pending' | 'ingested' | 'dismissed';\n\n/**\n * Statically-extracted MCP server capability profile (v0.6) — tool/resource/prompt names\n * and transport hints parsed from a quarantined MCP candidate's package.json, manifest, and\n * source text (NEVER by executing the server). Only present on `artifactType: 'mcp'` entries;\n * absent means the entry predates v0.6 or profiling couldn't determine anything.\n */\nexport interface McpCapabilities {\n tools: string[];\n resources: string[];\n prompts: string[];\n /** Best-effort transport hint (e.g. 'stdio', 'http'), when determinable statically. */\n transport?: string;\n /** How confidently `tools`/`resources`/`prompts` were determined from static analysis alone. */\n declaredConfidence: 'high' | 'partial' | 'none';\n}\n\nexport interface OverlapTopEntry {\n skill: string;\n score: number;\n}\n\nexport interface QueueEntryGate {\n license: string | null;\n safetyVerdict: SafetyVerdict;\n safetyFindings: string[];\n /** Top overlap matches against the configured skill set; empty on the free tier. */\n overlapTop: OverlapTopEntry[];\n suggestedVerb: SuggestedVerb | null;\n}\n\nexport interface QueueEntry {\n id: string;\n source: string;\n sourceType: SourceType;\n /** Final path after promote; null while the candidate is only held in quarantine. */\n installedPath: string | null;\n quarantinePath: string;\n gate: QueueEntryGate;\n status: QueueStatus;\n createdAt: string;\n /** 'skill' | 'mcp'; optional — absent means 'skill' (pre-v0.5 entries don't have this field). */\n artifactType?: ArtifactType;\n /** Statically-extracted MCP capability profile; optional — absent means pre-v0.6 or undetermined. */\n capabilities?: McpCapabilities;\n /** How this entry was produced; optional — absent means a normal `add`-sourced entry. See QueueEntryOrigin. */\n origin?: QueueEntryOrigin;\n}\n\nexport interface Queue {\n version: 1;\n entries: QueueEntry[];\n}\n\n/** Input for a new entry — `id` and `createdAt` are generated by `appendQueueEntry`. */\nexport type NewQueueEntry = Omit<QueueEntry, 'id' | 'createdAt'>;\n\nexport function getQueuePath(): string {\n return join(getHomeDir(), 'queue.json');\n}\n\n/** Reads the queue from disk, defaulting to an empty queue when the file doesn't exist yet. */\nexport function readQueue(): Queue {\n const path = getQueuePath();\n if (!existsSync(path)) {\n return { version: 1, entries: [] };\n }\n const raw = readFileSync(path, 'utf-8');\n const parsed = JSON.parse(raw) as Partial<Queue>;\n return { version: 1, entries: parsed.entries ?? [] };\n}\n\n/**\n * Writes the queue atomically: serialize to a temp file in the same directory (so the rename\n * below is same-filesystem and therefore atomic), then `renameSync` it over the real path. A\n * reader can never observe a partially-written queue.json, and a crash mid-write leaves the\n * previous queue.json intact plus an orphaned temp file rather than a corrupted one. The temp\n * file is cleaned up on any write failure.\n */\nfunction writeQueue(queue: Queue): void {\n const home = getHomeDir();\n mkdirSync(home, { recursive: true }); // recursive mkdir is a no-op when the dir exists\n const finalPath = getQueuePath();\n const tempPath = join(home, `.queue.json.tmp-${randomUUID()}`);\n try {\n writeFileSync(tempPath, JSON.stringify(queue, null, 2) + '\\n', 'utf-8');\n renameSync(tempPath, finalPath);\n } catch (err) {\n rmSync(tempPath, { force: true });\n throw err;\n }\n}\n\n/** A count that also says whether the underlying store could actually be read. */\nexport interface CountResult {\n count: number;\n readable: boolean;\n}\n\n/**\n * Number of entries still awaiting a decide pass, reporting unreadable rather than 0 on a malformed\n * queue.json. Shared by `status`/`guide`/`routine`, which are all diagnostics — each must survive\n * a broken queue well enough to report everything else, and each previously inlined this with its\n * own try/catch, letting the counts drift apart.\n */\nexport function pendingQueueCount(): CountResult {\n try {\n return { count: readQueue().entries.filter((e) => e.status === 'pending').length, readable: true };\n } catch {\n // A MISSING queue.json is not an error — readQueue returns an empty queue for that. Reaching\n // here means the file exists and is corrupt, which must not be reported as \"0 pending\": the\n // queue records already-promoted skills still owed a decide-pass verb, so a silent 0 makes\n // unvetted installed skills vanish from exactly the commands meant to surface them.\n return { count: 0, readable: false };\n }\n}\n\n/** Appends a new entry (generating `id`/`createdAt`) and persists the queue. Returns the full entry. */\nexport function appendQueueEntry(entry: NewQueueEntry): QueueEntry {\n const queue = readQueue();\n const existingIds = new Set(queue.entries.map((e) => e.id));\n const full: QueueEntry = {\n ...entry,\n id: generateId(existingIds),\n createdAt: new Date().toISOString(),\n };\n queue.entries.push(full);\n writeQueue(queue);\n return full;\n}\n\n/** Applies a partial update to the entry with `id` (e.g. a status transition) and persists the queue. */\nexport function updateQueueEntry(id: string, patch: Partial<Omit<QueueEntry, 'id' | 'createdAt'>>): QueueEntry {\n const queue = readQueue();\n const index = queue.entries.findIndex((e) => e.id === id);\n if (index === -1) {\n throw new Error(`no queue entry with id: ${id}`);\n }\n const updated: QueueEntry = { ...queue.entries[index], ...patch };\n queue.entries[index] = updated;\n writeQueue(queue);\n return updated;\n}\n\n/** Short unique slug (8 hex chars from a UUID), collision-checked against ids already in the queue. */\nfunction generateId(existingIds: Set<string>): string {\n let id: string;\n do {\n id = randomUUID().split('-')[0];\n } while (existingIds.has(id));\n return id;\n}\n","import { spawnSync, type StdioOptions } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Generic ingestion handoff — after a skill is promoted (or held), optionally\n * launches a coding agent to run the deep decide/absorb pass described in the\n * bundled `assets/ingest-prompt.md`. Deliberately agent-neutral: nothing here\n * assumes Claude specifically (see `docs/queue-schema.md` for the pending\n * queue this hands off into).\n *\n * SECURITY: `config.handoffCommand` (and the built-in agent-detection\n * fallback) are **argv-style arrays**, not shell strings. `runHandoff` always\n * calls `spawnSync(bin, args, ...)` with no `shell` option, so a token like\n * `\"; rm -rf /\"` is passed to the child process as a single literal argv\n * element — it is never parsed by a shell. Do not add `shell: true` here, and\n * do not build the command by joining tokens into one string.\n */\n\n/** Minimal config shape this module needs — kept structural so it doesn't require editing src/config.ts's Config type. */\nexport interface HandoffConfig {\n /**\n * Optional argv-style command template, e.g.\n * `[\"claude\", \"-p\", \"read {prompt} and process the skill at {path}\"]`.\n * `{path}` is replaced with the absolute installed-skill path; `{prompt}`\n * is replaced with the absolute path to the bundled ingest-prompt.md.\n * An empty/absent array falls through to the detection chain below.\n */\n handoffCommand?: string[];\n}\n\nexport interface HandoffCommand {\n /** argv (index 0 is the binary) to spawn; empty when nothing runnable was found. */\n argv: string[];\n /** How this command was resolved. */\n source: 'template' | 'detected' | 'none';\n}\n\nexport interface HandoffDeps {\n /** Checks whether a binary is available on PATH. Overridable for tests; defaults to a real `which`/`where` check. */\n isOnPath?: (bin: string) => boolean;\n /**\n * Which bundled `assets/` file to hand off — passed through to `resolvePromptPath()`.\n * Defaults to `'ingest-prompt.md'` (unchanged for `add --ingest`). `skill-forge audit`'s\n * `--handoff` passes `'curation-prompt.md'` instead (v0.8).\n */\n promptAsset?: string;\n}\n\nexport interface RunHandoffOptions extends HandoffDeps {\n /** stdio for the spawned process. Defaults to 'inherit' (the handoff is an interactive agent session). Tests can override to capture output. */\n stdio?: StdioOptions;\n /** Overrides argv[0]-execution for tests; defaults to node:child_process spawnSync. */\n spawn?: typeof spawnSync;\n /**\n * Sink for the \"no agent available\" / \"binary not found on PATH\" print-only messages this\n * function itself emits (NOT the spawned child's own stdio, which `stdio` above controls).\n * Defaults to `console.log`. `skill-forge audit --json --handoff` passes `console.error` here\n * so a print-only message can never land on stdout after the one JSON document already\n * written there (v0.8 fix) — `add --ingest`'s call site is unaffected, still defaulting to\n * `console.log`.\n */\n log?: (message: string) => void;\n}\n\nexport interface RunHandoffResult {\n /** Whether a process was actually spawned (successfully or not). */\n ran: boolean;\n /** The resolved command, for callers that want to log/inspect it. */\n command: HandoffCommand;\n /** The message printed to the user in print-only mode or when the resolved binary was missing. */\n printed?: string;\n /** Set when spawning failed (e.g. ENOENT). */\n error?: string;\n}\n\n/** Binaries checked, in order, when no `handoffCommand` template is configured. */\nconst KNOWN_AGENT_BINARIES = ['claude', 'codex', 'cursor-agent', 'windsurf', 'opencode', 'gemini'];\n\n/**\n * The generic ingest instruction used in handoff argv templates — `{prompt}` and `{path}` are\n * replaced by `substitute()`. Shared by the detected-binary fallback below, the per-agent\n * templates in src/agents.ts, and `skill-forge init`'s best-effort fallback.\n */\nexport const HANDOFF_INSTRUCTION = 'Read {prompt} and follow its instructions for the skill installed at {path}';\n\n/**\n * Absolute path to a bundled handoff prompt asset (default `ingest-prompt.md`; `skill-forge\n * audit --handoff` passes `curation-prompt.md` — v0.8), resolved relative to this module so it\n * works both from source (tests run against `src/`) and from the built package (`dist/`).\n *\n * tsup's `publicDir` copies the *contents* of `assets/` into `dist/`\n * (flattening one level), so the built layout is `dist/<assetName>`\n * sitting next to the bundled `dist/cli.js` — see tsup.config.ts. The dev/\n * test layout is the repo's own `assets/<assetName>`, one level up from\n * `src/`.\n */\nexport function resolvePromptPath(assetName: string = 'ingest-prompt.md'): string {\n const here = dirname(fileURLToPath(import.meta.url));\n const builtCandidate = join(here, assetName);\n if (existsSync(builtCandidate)) return builtCandidate;\n return join(here, '..', 'assets', assetName);\n}\n\nfunction defaultIsOnPath(bin: string): boolean {\n const finder = process.platform === 'win32' ? 'where' : 'which';\n const result = spawnSync(finder, [bin], { stdio: 'ignore' });\n return result.status === 0;\n}\n\n/** Replaces `{path}` and `{prompt}` tokens in each argv element; unknown `{word}` tokens are left as-is. */\nfunction substitute(template: string[], vars: Record<string, string>): string[] {\n return template.map((token) => token.replace(/\\{(\\w+)\\}/g, (match, key: string) => vars[key] ?? match));\n}\n\n/**\n * Resolves the handoff command via a fallback chain:\n * 1. `config.handoffCommand` template, with `{path}`/`{prompt}` substituted.\n * 2. The first known agent binary found on PATH, invoked generically with\n * the ingest prompt.\n * 3. Nothing runnable (`source: 'none'`) — the caller should print\n * instructions instead of spawning anything.\n */\nexport function buildHandoffCommand(config: HandoffConfig, installedPath: string, deps: HandoffDeps = {}): HandoffCommand {\n const isOnPath = deps.isOnPath ?? defaultIsOnPath;\n const promptPath = resolvePromptPath(deps.promptAsset);\n const vars = { path: installedPath, prompt: promptPath };\n\n if (config.handoffCommand && config.handoffCommand.length > 0) {\n return { argv: substitute(config.handoffCommand, vars), source: 'template' };\n }\n\n for (const bin of KNOWN_AGENT_BINARIES) {\n if (isOnPath(bin)) {\n return { argv: substitute([bin, '-p', HANDOFF_INSTRUCTION], vars), source: 'detected' };\n }\n }\n\n return { argv: [], source: 'none' };\n}\n\n/** The message shown when there's nothing runnable to spawn (print-only mode, or a resolved binary that turned out missing). */\nfunction printOnlyMessage(installedPath: string, promptAsset?: string): string {\n const promptPath = resolvePromptPath(promptAsset);\n return (\n '[skill-forge] no agent handoff available — run one yourself:\\n' +\n ` Prompt: ${promptPath}\\n` +\n ` Skill path: ${installedPath}`\n );\n}\n\n/**\n * Executes the resolved handoff command WITHOUT a shell (`spawnSync(bin,\n * args, ...)` — no `shell: true`), so argv-style templates can never be\n * shell-interpreted. Falls back to printing the command for the user to run\n * manually in print-only mode, or if the resolved binary isn't actually on\n * PATH at spawn time (ENOENT).\n */\nexport function runHandoff(config: HandoffConfig, installedPath: string, options: RunHandoffOptions = {}): RunHandoffResult {\n const command = buildHandoffCommand(config, installedPath, options);\n const spawn = options.spawn ?? spawnSync;\n const log = options.log ?? console.log;\n\n if (command.source === 'none' || command.argv.length === 0) {\n const printed = printOnlyMessage(installedPath, options.promptAsset);\n log(printed);\n return { ran: false, command, printed };\n }\n\n const [bin, ...args] = command.argv;\n const result = spawn(bin, args, { stdio: options.stdio ?? 'inherit' });\n const error = result.error as NodeJS.ErrnoException | undefined;\n\n if (error?.code === 'ENOENT') {\n const printed = printOnlyMessage(installedPath, options.promptAsset);\n log(`[skill-forge] ${bin} not found on PATH — run this yourself:`);\n log(printed);\n return { ran: false, command, printed, error: error.message };\n }\n if (error) {\n console.error(`[skill-forge] failed to launch handoff: ${error.message}`);\n return { ran: false, command, error: error.message };\n }\n\n return { ran: true, command };\n}\n","import { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { createInterface, type Interface } from 'node:readline/promises';\nimport {\n AGENTS,\n agentCliPresence,\n detectAgentRoot,\n detectMcpConfigFiles,\n type Agent,\n type CliPresence,\n type DetectedMcpConfig,\n type DetectedRoot,\n} from '../agents.js';\nimport { getConfigPath, loadConfig, saveConfig, type Config } from '../config.js';\nimport { HANDOFF_INSTRUCTION } from '../handoff.js';\nimport { auditCommand } from './audit.js';\nimport type { AuditCommandOptions } from './audit.js';\n\nexport interface InitOptions {\n /** Non-interactive: use all detected agents as gate targets, first as the default target. */\n defaults?: boolean;\n /** Print detection results only — no writes. */\n list?: boolean;\n /** Overrides for tests; default to the real process cwd / home dir. */\n cwd?: string;\n home?: string;\n /**\n * When explicitly `false`, suppresses the interactive \"Run the skills & MCP audit now?\" tail\n * offer entirely — no question is asked and this run of `init` never calls `runAudit` itself.\n * Set only by `maybeOfferInit`'s call from `auditCommand` (v0.8 fix): that caller is already\n * about to run the single audit itself once `init()` returns, so init's own tail-audit offer\n * must stay silent — otherwise the audit (and its interactive business-profile Q&A) runs\n * twice. Direct `skill-forge init` (default/omitted, i.e. `true`) is unaffected.\n */\n offerAudit?: boolean;\n /**\n * Opt out of v0.11 PATH narrowing: keep every detected root pre-selected regardless of whether\n * any agent that maps to it has its CLI on PATH. Wired to `--all-agents`.\n */\n allAgents?: boolean;\n /** Override for tests; defaults to the real `process.env.PATH`. */\n pathEnv?: string;\n}\n\nexport interface InitDeps {\n /** Injected audit runner — init's final phase (v0.8) is `skill-forge audit`. Defaults to the real `auditCommand`; overridable so init tests don't have to run a full audit end-to-end. */\n runAudit?: (options?: AuditCommandOptions) => Promise<void>;\n}\n\ninterface DetectedAgent extends DetectedRoot {\n agent: Agent;\n /** Whether this agent's CLI is on PATH — see `agentCliPresence`. Drives the default selection. */\n presence: CliPresence;\n}\n\ninterface DetectedMcpTarget extends DetectedMcpConfig {\n agent: Agent;\n presence: CliPresence;\n}\n\n/**\n * One distinct skills root, plus every detected agent that resolves to it.\n *\n * Many agents share a root by design — 18 of the 73 entries in src/agents.ts use `.agents/skills`\n * as their project root — so a per-AGENT menu shows the same path up to 18 times, and toggling one\n * entry off changes nothing about what gets gated. Selection, the written `skillsRoots`, and the\n * default-target menu are all keyed on the PATH for that reason; the agent list survives only as\n * the label and as the informational `config.agents` id->root map.\n */\ninterface RootGroup {\n root: string;\n /** Every scope this root was matched under — both when cwd === home makes project and global collide. */\n scopes: Set<'project' | 'global'>;\n members: DetectedAgent[];\n}\n\n/** The MCP-config-file analog of `RootGroup` — same duplicate-path problem, same fix. */\ninterface McpGroup {\n path: string;\n scopes: Set<'project' | 'global'>;\n members: DetectedMcpTarget[];\n}\n\nfunction groupByRoot(detected: DetectedAgent[]): RootGroup[] {\n const byRoot = new Map<string, RootGroup>();\n for (const d of detected) {\n const existing = byRoot.get(d.root);\n if (existing) {\n existing.members.push(d);\n existing.scopes.add(d.scope);\n } else {\n byRoot.set(d.root, { root: d.root, scopes: new Set([d.scope]), members: [d] });\n }\n }\n return [...byRoot.values()];\n}\n\nfunction groupMcpByPath(detected: DetectedMcpTarget[]): McpGroup[] {\n const byPath = new Map<string, McpGroup>();\n for (const d of detected) {\n const existing = byPath.get(d.path);\n if (existing) {\n existing.members.push(d);\n existing.scopes.add(d.scope);\n } else {\n byPath.set(d.path, { path: d.path, scopes: new Set([d.scope]), members: [d] });\n }\n }\n return [...byPath.values()];\n}\n\n/**\n * True when at least one agent sharing this path has its CLI on PATH — i.e. there is positive\n * evidence somebody actually uses this root. Groups where every member is `absent`/`unknown` are\n * still listed, just not pre-selected.\n */\nfunction hasInstalledMember(group: { members: { presence: CliPresence }[] }): boolean {\n return group.members.some((m) => m.presence === 'installed');\n}\n\n/**\n * Label for one grouped path. Names the agents whose CLI is on PATH first, because on a shared\n * root those are the ones that justify gating it: `on PATH: Codex, Gemini CLI, OpenCode (+16 other\n * agents share this root)`. Falls back to naming the agents themselves when none are confirmed.\n */\nfunction describeGroup(members: { agent: Agent; presence: CliPresence }[], scopes: Set<'project' | 'global'>): string {\n const scope = [...scopes].join('+');\n const installed = members.filter((m) => m.presence === 'installed').map((m) => m.agent.displayName);\n const others = members.length - installed.length;\n\n if (installed.length > 0) {\n const share = others > 0 ? ` (+${others} other agent${others === 1 ? '' : 's'} share this path)` : '';\n return `on PATH: ${installed.join(', ')} (${scope})${share}`;\n }\n\n const names = members.map((m) => m.agent.displayName);\n const shown = names.slice(0, 3).join(', ');\n const more = names.length > 3 ? `, +${names.length - 3} more` : '';\n const suffix = members.every((m) => m.presence === 'absent') ? 'CLI not on PATH' : 'no verified CLI name to check';\n const who = names.length === 1 ? names[0] : `${names.length} agents: ${shown}${more}`;\n return `${who} (${scope}) — ${suffix}`;\n}\n\nfunction detectAgents(cwd: string, home: string, pathEnv?: string): DetectedAgent[] {\n const found: DetectedAgent[] = [];\n for (const agent of AGENTS) {\n const hit = detectAgentRoot(agent, cwd, home);\n if (hit) found.push({ agent, ...hit, presence: agentCliPresence(agent, pathEnv) });\n }\n return found;\n}\n\n/**\n * Probes every known agent's `mcpConfig` locations (see src/agents.ts) and returns every MCP\n * config file that exists on disk. Unlike `detectAgents`, an agent can contribute more than one\n * hit (project and global config can both exist and both matter as promotion targets).\n */\nfunction detectMcpTargets(cwd: string, home: string, pathEnv?: string): DetectedMcpTarget[] {\n const found: DetectedMcpTarget[] = [];\n for (const agent of AGENTS) {\n for (const hit of detectMcpConfigFiles(agent, cwd, home)) {\n found.push({ agent, ...hit, presence: agentCliPresence(agent, pathEnv) });\n }\n }\n return found;\n}\n\nfunction printDetectionResults(detected: DetectedAgent[]): void {\n console.log(`[skill-forge] init — agent detection (${AGENTS.length} known agents)`);\n if (detected.length === 0) {\n console.log(' none detected');\n return;\n }\n for (const d of detected) {\n console.log(` ${d.agent.displayName} (${d.agent.id}) — ${d.scope}: ${d.root}${presenceNote(d.presence)}`);\n }\n const distinct = new Set(detected.map((d) => d.root)).size;\n if (distinct < detected.length) {\n console.log(` → ${detected.length} agents share ${distinct} distinct skills root(s); gating is per root.`);\n }\n const installed = detected.filter((d) => d.presence === 'installed').length;\n if (installed > 0 && installed < detected.length) {\n console.log(` → ${installed} of ${detected.length} have their CLI on PATH; only those roots are pre-selected (--all-agents to keep all).`);\n }\n}\n\n/** Suffix disclosing why an entry is (or isn't) treated as installed. `unknown` stays silent per-line — the menu says it. */\nfunction presenceNote(presence: CliPresence): string {\n if (presence === 'installed') return ' [CLI on PATH]';\n if (presence === 'absent') return ' [CLI not on PATH]';\n return '';\n}\n\nfunction printMcpDetectionResults(detectedMcp: DetectedMcpTarget[]): void {\n if (detectedMcp.length === 0) return;\n console.log('\\n[skill-forge] init — MCP config detection');\n for (const d of detectedMcp) {\n console.log(` ${d.agent.displayName} (${d.agent.id}) — ${d.scope}: ${d.path}${presenceNote(d.presence)}`);\n }\n const distinct = new Set(detectedMcp.map((d) => d.path)).size;\n if (distinct < detectedMcp.length) {\n console.log(` → ${detectedMcp.length} entries resolve to ${distinct} distinct config file(s).`);\n }\n}\n\nfunction handoffTemplateFor(agent: Agent): string[] {\n // Best-effort fallback when we don't have a confirmed CLI invocation: use the agent id as\n // the binary name. May not match the real CLI for lesser-known agents — defaultHandoffTemplate\n // should be preferred whenever it's set.\n return agent.defaultHandoffTemplate ?? [agent.id, '-p', HANDOFF_INSTRUCTION];\n}\n\nfunction buildConfig(\n selected: RootGroup[],\n defaultTargetEntry: RootGroup | undefined,\n handoffAgent: Agent | undefined,\n selectedMcp: McpGroup[] = [],\n): Config {\n const existing = loadConfig();\n return {\n ...existing,\n // Skill-root selections are only overwritten when this run actually offered a choice —\n // an mcp-only run (no skill roots detected) must not blank out an existing skillsRoots/agents.\n // Both lists are one entry per DISTINCT path (see RootGroup); `agents` stays per-agent because\n // it is an id->root map, where several ids legitimately point at the same root.\n skillsRoots: selected.length > 0 ? selected.map((s) => s.root) : existing.skillsRoots,\n agents:\n selected.length > 0\n ? selected.flatMap((s) => s.members.map((m) => ({ id: m.agent.id, skillsRoot: s.root })))\n : existing.agents,\n defaultTarget: defaultTargetEntry?.root ?? existing.defaultTarget,\n handoffCommand: handoffAgent ? handoffTemplateFor(handoffAgent) : existing.handoffCommand,\n mcpTargets: selectedMcp.length > 0 ? selectedMcp.map((m) => m.path) : existing.mcpTargets,\n };\n}\n\nfunction printSummary(config: Config): void {\n console.log(`\\n[skill-forge] wrote config to ${getConfigPath()}`);\n console.log(` gate targets: ${config.skillsRoots.join(', ')}`);\n if (config.defaultTarget) console.log(` default target: ${config.defaultTarget}`);\n if (config.handoffCommand?.length) console.log(` handoff command: ${config.handoffCommand.join(' ')}`);\n if (config.mcpTargets?.length) console.log(` mcp targets: ${config.mcpTargets.join(', ')}`);\n}\n\n/**\n * True for the rejection `readline/promises` raises when a real terminal sends EOF mid-question\n * (Ctrl+D): `AbortError` / `ABORT_ERR`. It's a user cancelling, not a bug, so it must not reach\n * cli.ts's \"unexpected error ... re-run with DEBUG=skill-forge\" branch.\n *\n * Not covered by test/init.test.ts: this rejection is a TTY keypress behavior, and `withFakeTty`'s\n * PassThrough can't produce it — ENDING that stream leaves the pending `question()` unsettled\n * forever rather than aborting, so such a test hangs to timeout instead of asserting. Verified\n * manually against a pty (`script -q /dev/null node dist/cli.js init` with stdin closed): before\n * this guard it printed `skill-forge: unexpected error: Aborted with Ctrl+D`, after it prints\n * `[skill-forge] init cancelled.` and exits 0.\n */\nfunction isPromptAbort(err: unknown): boolean {\n return err instanceof Error && (err.name === 'AbortError' || (err as NodeJS.ErrnoException).code === 'ABORT_ERR');\n}\n\n/** Parses a 1-based menu answer into a zero-based index, or undefined when it isn't a listed number. */\nfunction parseChoice(answer: string, max: number): number | undefined {\n if (!/^\\d+$/.test(answer)) return undefined; // strict: \"6, 14\" / \"3x\" / \"0x2\" are answers we must reject, not coerce\n const n = Number(answer);\n return n >= 1 && n <= max ? n - 1 : undefined;\n}\n\nconst SELECTION_HELP = 'enter listed numbers (\"2\", \"1 3\", \"2-5\"), \"all\", \"none\", or press Enter to confirm';\n\n/**\n * Parses a multi-select answer: whitespace/comma-separated numbers and `a-b` ranges, in any mix.\n * Every token that isn't a listed number is returned in `invalid` so the caller can SAY SO —\n * silently ignoring an unparsed answer is what made a typed selection look like it was accepted\n * when it wasn't.\n */\nfunction parseSelection(answer: string, max: number): { indices: number[]; invalid: string[] } {\n const indices: number[] = [];\n const invalid: string[] = [];\n for (const token of answer.split(/[\\s,]+/).filter(Boolean)) {\n const range = /^(\\d+)-(\\d+)$/.exec(token);\n if (range) {\n const from = Number(range[1]);\n const to = Number(range[2]);\n if (from >= 1 && to >= from && to <= max) {\n for (let i = from; i <= to; i++) indices.push(i - 1);\n } else {\n invalid.push(token);\n }\n continue;\n }\n const idx = parseChoice(token, max);\n if (idx === undefined) invalid.push(token);\n else indices.push(idx);\n }\n return { indices, invalid };\n}\n\n/**\n * Shared toggle loop for the two multi-selects. Everything is pre-selected; each answer toggles\n * the numbers it names, `all`/`none` set the whole set at once, and Enter confirms.\n */\nasync function promptToggleSet<T>(\n rl: Interface,\n header: string,\n items: T[],\n render: (item: T, index: number, mark: string) => void,\n preselect: (item: T) => boolean = () => true,\n): Promise<T[]> {\n const chosen = new Set(items.map((_, i) => i).filter((i) => preselect(items[i])));\n for (;;) {\n console.log(`\\n${header}`);\n items.forEach((item, i) => render(item, i, chosen.has(i) ? '[x]' : '[ ]'));\n const answer = (await rl.question('> ')).trim();\n if (answer === '') break;\n\n const lowered = answer.toLowerCase();\n if (lowered === 'a' || lowered === 'all') {\n items.forEach((_, i) => chosen.add(i));\n continue;\n }\n if (lowered === 'n' || lowered === 'none') {\n chosen.clear();\n continue;\n }\n\n const { indices, invalid } = parseSelection(answer, items.length);\n for (const idx of indices) {\n if (chosen.has(idx)) chosen.delete(idx);\n else chosen.add(idx);\n }\n if (invalid.length > 0) {\n console.log(` (ignored: ${invalid.join(', ')} — ${SELECTION_HELP})`);\n }\n }\n return items.filter((_, i) => chosen.has(i));\n}\n\nfunction selectionHeader(kind: string, narrowed: boolean): string {\n const preset = narrowed\n ? `roots of agents found on PATH pre-selected; the rest are listed but off`\n : 'all detected entries pre-selected';\n return `${kind} (${preset} — ${SELECTION_HELP}):`;\n}\n\nasync function promptMultiSelect(rl: Interface, groups: RootGroup[], narrow: boolean): Promise<RootGroup[]> {\n return promptToggleSet(\n rl,\n selectionHeader('Gate targets', narrow),\n groups,\n (g, i, mark) => {\n console.log(` ${mark} ${i + 1}. ${g.root}`);\n console.log(` ${describeGroup(g.members, g.scopes)}`);\n },\n (g) => !narrow || hasInstalledMember(g),\n );\n}\n\n/** Same UX as `promptMultiSelect`, offering detected MCP config files as `mcpTargets` candidates. */\nasync function promptMcpMultiSelect(rl: Interface, groups: McpGroup[], narrow: boolean): Promise<McpGroup[]> {\n return promptToggleSet(\n rl,\n selectionHeader('MCP targets', narrow),\n groups,\n (g, i, mark) => {\n console.log(` ${mark} ${i + 1}. ${g.path}`);\n console.log(` ${describeGroup(g.members, g.scopes)}`);\n },\n (g) => !narrow || hasInstalledMember(g),\n );\n}\n\n/**\n * Asks for one listed number, RE-ASKING on anything unparseable. The previous implementation\n * fell back to option 1 on an unrecognized answer, so a mistyped or multi-value answer silently\n * wrote a default target the user never chose.\n */\nasync function promptSingleChoice(\n rl: Interface,\n prompt: string,\n max: number,\n onEmpty: 'default' | 'skip',\n): Promise<number | undefined> {\n for (;;) {\n const answer = (await rl.question(prompt)).trim();\n if (answer === '') return onEmpty === 'default' ? 0 : undefined;\n const idx = parseChoice(answer, max);\n if (idx !== undefined) return idx;\n console.log(` (not a listed choice — enter a single number from 1 to ${max}${onEmpty === 'skip' ? ', or press Enter to skip' : ''})`);\n }\n}\n\nasync function promptDefaultTarget(rl: Interface, selected: RootGroup[]): Promise<RootGroup> {\n console.log('\\nDefault promotion target:');\n selected.forEach((g, i) => {\n console.log(` ${i + 1}. ${g.root}`);\n console.log(` ${describeGroup(g.members, g.scopes)}`);\n });\n const idx = await promptSingleChoice(rl, `Choose [1-${selected.length}] (default 1): `, selected.length, 'default');\n return selected[idx ?? 0];\n}\n\nasync function promptHandoffAgent(rl: Interface, detected: DetectedAgent[], narrow: boolean): Promise<Agent | undefined> {\n // Deliberately NOT grouped by root: each entry here is a distinct CLI binary, so two agents\n // sharing a skills root are still two different handoff choices. Narrowing only REORDERS —\n // agents whose CLI was found on PATH float to the top, and everything detected stays reachable,\n // because a handoff command for an agent we have no verified binary name for is still a valid\n // thing to want.\n const ordered = narrow\n ? [...detected].sort((a, b) => Number(b.presence === 'installed') - Number(a.presence === 'installed'))\n : detected;\n\n console.log('\\nHandoff agent (used to hand off follow-up prompts after promotion; optional):');\n ordered.forEach((d, i) => {\n const note =\n d.presence === 'installed'\n ? ' — found on PATH'\n : d.agent.defaultHandoffTemplate\n ? ' (CLI not on PATH)'\n : ' (no confirmed CLI — best-effort)';\n console.log(` ${i + 1}. ${d.agent.displayName}${note}`);\n });\n const idx = await promptSingleChoice(rl, `Choose [1-${ordered.length}] or Enter to skip: `, ordered.length, 'skip');\n return idx === undefined ? undefined : ordered[idx].agent;\n}\n\n/**\n * `skill-forge init` — setup wizard. Probes all known agents (see src/agents.ts), lets the\n * user pick which detected skill roots become gate targets, a default promotion target, an\n * optional handoff agent, and which detected MCP config files become `mcpTargets`, then writes\n * the result via saveConfig.\n */\nexport async function init(options: InitOptions = {}, deps: InitDeps = {}): Promise<void> {\n const runAudit = deps.runAudit ?? auditCommand;\n const cwd = options.cwd ?? process.cwd();\n const home = options.home ?? homedir();\n const detected = detectAgents(cwd, home, options.pathEnv);\n const detectedMcp = detectMcpTargets(cwd, home, options.pathEnv);\n\n if (options.list) {\n printDetectionResults(detected);\n printMcpDetectionResults(detectedMcp);\n return;\n }\n\n if (detected.length === 0 && detectedMcp.length === 0) {\n console.log('[skill-forge] No known agent skill directories or MCP config files were found.');\n console.log(' Install a coding agent (Claude Code, Codex, Cursor, ...) or create a skills root, then re-run `skill-forge init`.');\n return;\n }\n\n const rootGroups = groupByRoot(detected);\n const mcpGroups = groupMcpByPath(detectedMcp);\n\n // v0.11 narrowing: a skills directory existing proves nothing about which of the (up to 18)\n // agents sharing it you actually use, so the default selection is the roots where at least one\n // agent's CLI is on PATH. Disabled by --all-agents, and self-disabling when it would select\n // nothing at all (no verified CLI anywhere on this machine) — narrowing must never turn a\n // working detection into an empty config.\n const narrowable = rootGroups.some(hasInstalledMember) || mcpGroups.some(hasInstalledMember);\n const narrow = !options.allAgents && narrowable;\n if (!options.allAgents && !narrowable && (rootGroups.length > 0 || mcpGroups.length > 0)) {\n console.log(\n '[skill-forge] No detected agent CLI was found on PATH — keeping every detected target selected.',\n );\n }\n\n if (options.defaults) {\n const selectedRoots = narrow ? rootGroups.filter(hasInstalledMember) : rootGroups;\n const selectedMcpTargets = narrow ? mcpGroups.filter(hasInstalledMember) : mcpGroups;\n const config = buildConfig(selectedRoots, selectedRoots[0], undefined, selectedMcpTargets);\n saveConfig(config);\n printSummary(config);\n // Non-interactive final phase (v0.8): report written, no profile Q&A, no foundation, no handoff.\n await runAudit({ yes: true });\n return;\n }\n\n if (!process.stdin.isTTY) {\n console.log('[skill-forge] Not an interactive terminal — run with --defaults for non-interactive setup.');\n return;\n }\n\n printDetectionResults(detected);\n printMcpDetectionResults(detectedMcp);\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n let runAuditNow = false;\n try {\n const selected = rootGroups.length > 0 ? await promptMultiSelect(rl, rootGroups, narrow) : [];\n const selectedMcp = mcpGroups.length > 0 ? await promptMcpMultiSelect(rl, mcpGroups, narrow) : [];\n if (selected.length === 0 && selectedMcp.length === 0) {\n console.log('[skill-forge] No targets selected — aborting, nothing written.');\n return;\n }\n const target = selected.length > 0 ? await promptDefaultTarget(rl, selected) : undefined;\n const handoffAgent = detected.length > 0 ? await promptHandoffAgent(rl, detected, narrow) : undefined;\n const config = buildConfig(selected, target, handoffAgent, selectedMcp);\n saveConfig(config);\n printSummary(config);\n\n if (options.offerAudit !== false) {\n const answer = (await rl.question('\\nRun the skills & MCP audit now? [Y/n] ')).trim().toLowerCase();\n runAuditNow = answer === '' || answer === 'y' || answer === 'yes';\n }\n } catch (err) {\n if (!isPromptAbort(err)) throw err;\n // Ctrl+D / closed stdin at any prompt: a cancel, not a crash. Anything already saved before\n // this point (the config write happens above) stays written and was reported by printSummary.\n console.log('\\n[skill-forge] init cancelled.');\n return;\n } finally {\n // Closed BEFORE any audit run below — auditCommand's own interactive prompts open a fresh\n // readline interface over the same stdin, and two concurrent interfaces would race on it.\n rl.close();\n }\n\n if (runAuditNow) {\n await runAudit();\n }\n}\n\nexport interface OfferInitOptions {\n json?: boolean;\n yes?: boolean;\n /**\n * Set by `auditCommand`'s own call (v0.8 fix): if the user accepts this offer, the `init` run\n * it launches must not ALSO offer/run its own tail audit — `auditCommand` is already the\n * single audit run that will happen once `init()` returns here.\n */\n suppressAuditOffer?: boolean;\n}\n\n/**\n * Prints a one-line offer to run `skill-forge init` when no config.json exists yet, and (in an\n * interactive terminal) asks whether to run it now. Never fires for automation: no-ops entirely\n * when stdin isn't a TTY, or `--json`/`--yes` was passed, so scripted invocations never see this\n * output or block on a prompt. Called at startup by add/scan/list/status.\n */\nexport async function maybeOfferInit(options: OfferInitOptions = {}): Promise<void> {\n if (options.json || options.yes || !process.stdin.isTTY) return;\n if (existsSync(getConfigPath())) return;\n\n console.log(\n '[skill-forge] No config found — run `skill-forge init` to detect your agents and set gate targets ' +\n '(init finishes with a `skill-forge audit` health check over what it just configured).'\n );\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n let answer = '';\n try {\n answer = (await rl.question('Run `skill-forge init` now? [y/N] ')).trim().toLowerCase();\n } finally {\n rl.close();\n }\n if (answer === 'y' || answer === 'yes') {\n await init(options.suppressAuditOffer ? { offerAudit: false } : {});\n }\n}\n","import { statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { HANDOFF_INSTRUCTION } from './handoff.js';\nimport { isDir, resolveBinaryOnPath } from './util.js';\n\n/** True if `p` exists and is a regular file (false on any stat error). Mirrors util.ts's isDir. */\nfunction isFile(p: string): boolean {\n try {\n return statSync(p).isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * A known agent-skill ecosystem: where it looks for skills, and (optionally) how to hand a\n * prompt off to it once a skill has been promoted.\n *\n * Path data (`projectRoot` / `globalRoot`) is verified against the vercel-labs/skills CLI's own\n * source of truth, `src/agents.ts`\n * (raw.githubusercontent.com/vercel-labs/skills/main/src/agents.ts, cross-checked via the\n * GitHub contents API 2026-07-11) — not just its README, which can drift from the code. All 73\n * entries below were diffed programmatically against that file and matched exactly (id,\n * displayName, skillsDir, and the default/no-env-override form of globalSkillsDir). A handful of\n * agents resolve their global root via an overridable env var upstream (e.g. Amp/OpenCode/Goose/\n * Devin/Universal/Replit via `XDG_CONFIG_HOME`, Codex via `CODEX_HOME`, Claude Code via\n * `CLAUDE_CONFIG_DIR`) — the tilde path here is upstream's default when that var is unset.\n * Entries marked `verified: false` could not be confirmed against that source and should be\n * treated as best-effort, never invented.\n */\n/**\n * Where an agent reads its MCP server configuration from, if known (added in v0.5 for MCP\n * gating). Each entry is verified against that agent's official docs/source — see the citation\n * comment above the entry in AGENTS below — and `mcpConfig` is left absent entirely on agents we\n * couldn't confirm a path for; never invented.\n */\nexport interface McpConfigLocation {\n /** Project-relative MCP config file path (relative to cwd), e.g. `.mcp.json`. */\n project?: string;\n /** User-home MCP config file path, `~`-relative, e.g. `~/.claude.json`. */\n global?: string;\n /**\n * File format when it isn't plain JSON with a top-level `mcpServers` key. v0.5's MCP promote\n * only WRITES json-shaped targets (see the v0.5 contract) — `format: 'toml'` targets are\n * detect/report-only.\n */\n format?: 'toml';\n /** True once the path(s) above were confirmed against this agent's official docs (cited in a comment on this entry). */\n verified: boolean;\n}\n\nexport interface Agent {\n id: string;\n displayName: string;\n /** Project-relative skills root (relative to cwd), e.g. `.claude/skills`. */\n projectRoot?: string;\n /** User-home skills root, `~`-relative, e.g. `~/.claude/skills`. Absent = no global install path (project-only agent). */\n globalRoot?: string;\n /** False if this entry's paths are inferred rather than confirmed against the vercel-labs/skills source — never invented, always disclosed. */\n verified: boolean;\n /**\n * Best-effort argv template for handing a prompt to this agent's CLI, e.g.\n * `['claude', '-p', '{prompt}']`. Substituted by `src/handoff.ts` — never shell-parsed, so\n * each element is a literal argv token. Undefined where we don't have a confirmed CLI binary\n * name (IDE-only agents, or agents we couldn't verify an invocation for) — callers should\n * fall back to something explicit rather than guess.\n */\n defaultHandoffTemplate?: string[];\n /**\n * Command name(s) this agent installs on PATH, e.g. `['claude']`. Used by `skill-forge init`\n * (v0.11) to pre-select only the skills roots of agents that are actually installed — 18 of the\n * entries below share `.agents/skills`, so a directory existing proves nothing about which of\n * them you use.\n *\n * Held to the same evidence bar as `projectRoot`/`globalRoot`: populated ONLY where the binary\n * name is confirmed (today, exactly the agents with a `defaultHandoffTemplate`, whose argv[0] IS\n * that binary — cited on each entry). Absent means \"unknown\", never \"not installed\", and init\n * says so rather than hiding the root. Guessing here would be worse than leaving it out: many\n * plausible ids (`pi`, `bob`, `eve`, `mux`, `continue`, `crush`) collide with unrelated\n * executables, so a wrong guess would report an agent as installed on the strength of someone\n * else's binary.\n */\n cliBinaries?: string[];\n /** Where this agent reads its MCP server config from. Absent when unverified — see McpConfigLocation. */\n mcpConfig?: McpConfigLocation;\n /**\n * True if either the project or global skills root already exists on disk — i.e. this agent\n * looks installed. `cwd`/`home` default to the real process cwd / home dir; pass overrides in\n * tests to probe against temp dirs instead of the real filesystem.\n */\n detect(cwd?: string, home?: string): boolean;\n}\n\ninterface AgentSpec {\n id: string;\n displayName: string;\n projectRoot?: string;\n globalRoot?: string;\n verified?: boolean;\n defaultHandoffTemplate?: string[];\n cliBinaries?: string[];\n mcpConfig?: McpConfigLocation;\n}\n\n/** Resolves a project-relative skills root against `cwd`, or undefined if the agent has none. */\nfunction projectPath(root: string | undefined, cwd: string): string | undefined {\n return root ? resolve(cwd, root) : undefined;\n}\n\n/** Resolves a `~`-relative global skills root against `home`, or undefined if the agent has none. */\nfunction globalPath(root: string | undefined, home: string): string | undefined {\n if (!root) return undefined;\n if (root === '~') return home;\n if (root.startsWith('~/')) return join(home, root.slice(2));\n return root;\n}\n\nfunction makeAgent(spec: AgentSpec): Agent {\n const agent: Agent = {\n id: spec.id,\n displayName: spec.displayName,\n projectRoot: spec.projectRoot,\n globalRoot: spec.globalRoot,\n verified: spec.verified ?? true,\n defaultHandoffTemplate: spec.defaultHandoffTemplate,\n cliBinaries: spec.cliBinaries,\n mcpConfig: spec.mcpConfig,\n detect: (cwd?: string, home?: string): boolean => detectAgentRoot(agent, cwd, home) !== undefined,\n };\n return agent;\n}\n\n/**\n * Resolves the absolute filesystem path for one of an agent's roots, without probing for\n * existence. `cwd`/`home` default to the real process cwd / home dir.\n */\nexport function resolveAgentRoot(\n agent: Agent,\n scope: 'project' | 'global',\n cwd: string = process.cwd(),\n home: string = homedir(),\n): string | undefined {\n return scope === 'project' ? projectPath(agent.projectRoot, cwd) : globalPath(agent.globalRoot, home);\n}\n\n/**\n * Whether this agent's CLI could be found on PATH — the signal `skill-forge init` narrows its\n * default selection with. Three-state on purpose:\n *\n * - `'installed'` — a `cliBinaries` name resolved to an executable on PATH.\n * - `'absent'` — we know this agent's binary name and it is NOT on PATH.\n * - `'unknown'` — no verified binary name for this agent (see `Agent#cliBinaries`), so PATH\n * proves nothing either way. Never treated as `'absent'`.\n *\n * Look-up only — `resolveBinaryOnPath` never executes what it finds.\n */\nexport type CliPresence = 'installed' | 'absent' | 'unknown';\n\nexport function agentCliPresence(agent: Agent, pathEnv: string | undefined = process.env.PATH): CliPresence {\n if (!agent.cliBinaries || agent.cliBinaries.length === 0) return 'unknown';\n return agent.cliBinaries.some((bin) => resolveBinaryOnPath(bin, pathEnv) !== undefined) ? 'installed' : 'absent';\n}\n\n/** A skills root that exists on disk for an agent: which scope matched, and its absolute path. */\nexport interface DetectedRoot {\n scope: 'project' | 'global';\n root: string;\n}\n\n/**\n * Probes an agent's skills roots on disk — project first, then global — and returns the first\n * that exists, or undefined when neither does. Single source of truth for agent detection:\n * `Agent#detect` and `skill-forge init` both resolve through it. `cwd`/`home` default to the\n * real process cwd / home dir; pass overrides in tests to probe temp dirs instead.\n */\nexport function detectAgentRoot(\n agent: Agent,\n cwd: string = process.cwd(),\n home: string = homedir(),\n): DetectedRoot | undefined {\n for (const scope of ['project', 'global'] as const) {\n const root = resolveAgentRoot(agent, scope, cwd, home);\n if (root && isDir(root)) return { scope, root };\n }\n return undefined;\n}\n\n/**\n * Resolves the absolute filesystem path for one of an agent's MCP config files, without probing\n * for existence. Returns undefined when the agent has no `mcpConfig`, or no path for that scope.\n * `cwd`/`home` default to the real process cwd / home dir.\n */\nexport function resolveMcpConfigPath(\n agent: Agent,\n scope: 'project' | 'global',\n cwd: string = process.cwd(),\n home: string = homedir(),\n): string | undefined {\n if (!agent.mcpConfig) return undefined;\n return scope === 'project' ? projectPath(agent.mcpConfig.project, cwd) : globalPath(agent.mcpConfig.global, home);\n}\n\n/** An MCP config file that exists on disk for an agent: which scope it is, and its absolute path. */\nexport interface DetectedMcpConfig {\n scope: 'project' | 'global';\n path: string;\n}\n\n/**\n * Probes an agent's MCP config file locations on disk and returns every one that exists as a\n * file. Unlike `detectAgentRoot` (which returns only the first matching skills root), both\n * project and global MCP config files can legitimately coexist and both matter as promotion\n * targets — e.g. a project `.mcp.json` and a user-scope `~/.claude.json` — so this returns all\n * matches, not just the first. `cwd`/`home` default to the real process cwd / home dir.\n */\nexport function detectMcpConfigFiles(\n agent: Agent,\n cwd: string = process.cwd(),\n home: string = homedir(),\n): DetectedMcpConfig[] {\n if (!agent.mcpConfig) return [];\n const found: DetectedMcpConfig[] = [];\n for (const scope of ['project', 'global'] as const) {\n const path = resolveMcpConfigPath(agent, scope, cwd, home);\n if (path && isFile(path)) found.push({ scope, path });\n }\n return found;\n}\n\n/** Full agent matrix, in the order the vercel-labs/skills README lists them. */\nexport const AGENTS: Agent[] = (\n [\n { id: 'aider-desk', displayName: 'AiderDesk', projectRoot: '.aider-desk/skills', globalRoot: '~/.aider-desk/skills' },\n { id: 'amp', displayName: 'Amp', projectRoot: '.agents/skills', globalRoot: '~/.config/agents/skills' },\n { id: 'replit', displayName: 'Replit', projectRoot: '.agents/skills', globalRoot: '~/.config/agents/skills' },\n { id: 'universal', displayName: 'Universal', projectRoot: '.agents/skills', globalRoot: '~/.config/agents/skills' },\n { id: 'antigravity', displayName: 'Antigravity', projectRoot: '.agents/skills', globalRoot: '~/.gemini/antigravity/skills' },\n {\n id: 'antigravity-cli',\n displayName: 'Antigravity CLI',\n projectRoot: '.agents/skills',\n globalRoot: '~/.gemini/antigravity-cli/skills',\n },\n { id: 'astrbot', displayName: 'AstrBot', projectRoot: 'data/skills', globalRoot: '~/.astrbot/data/skills' },\n { id: 'autohand-code', displayName: 'Autohand Code CLI', projectRoot: '.autohand/skills', globalRoot: '~/.autohand/skills' },\n { id: 'augment', displayName: 'Augment', projectRoot: '.augment/skills', globalRoot: '~/.augment/skills' },\n { id: 'bob', displayName: 'IBM Bob', projectRoot: '.bob/skills', globalRoot: '~/.bob/skills' },\n {\n id: 'claude-code',\n displayName: 'Claude Code',\n projectRoot: '.claude/skills',\n globalRoot: '~/.claude/skills',\n // `claude -p \"<prompt>\"` runs one non-interactive turn and exits. Verified 2026-07-11:\n // https://code.claude.com/docs/en/headless\n defaultHandoffTemplate: ['claude', '-p', HANDOFF_INSTRUCTION],\n cliBinaries: ['claude'],\n // Project scope writes to `.mcp.json` at the project root (shared via version control).\n // User scope writes to a top-level `mcpServers` key in `~/.claude.json` (not exclusively\n // an MCP file — it's Claude Code's broader state file — but it's the correct read/write\n // target for user-scoped servers). Verified 2026-07-12: https://code.claude.com/docs/en/mcp\n mcpConfig: { project: '.mcp.json', global: '~/.claude.json', verified: true },\n },\n { id: 'openclaw', displayName: 'OpenClaw', projectRoot: 'skills', globalRoot: '~/.openclaw/skills' },\n { id: 'cline', displayName: 'Cline', projectRoot: '.agents/skills', globalRoot: '~/.agents/skills' },\n { id: 'dexto', displayName: 'Dexto', projectRoot: '.agents/skills', globalRoot: '~/.agents/skills' },\n { id: 'kimi-code-cli', displayName: 'Kimi Code CLI', projectRoot: '.agents/skills', globalRoot: '~/.agents/skills' },\n { id: 'loaf', displayName: 'Loaf', projectRoot: '.agents/skills', globalRoot: '~/.agents/skills' },\n { id: 'warp', displayName: 'Warp', projectRoot: '.agents/skills', globalRoot: '~/.agents/skills' },\n { id: 'zed', displayName: 'Zed', projectRoot: '.agents/skills', globalRoot: '~/.agents/skills' },\n {\n id: 'codearts-agent',\n displayName: 'CodeArts Agent',\n projectRoot: '.codeartsdoer/skills',\n globalRoot: '~/.codeartsdoer/skills',\n },\n { id: 'codebuddy', displayName: 'CodeBuddy', projectRoot: '.codebuddy/skills', globalRoot: '~/.codebuddy/skills' },\n { id: 'codemaker', displayName: 'Codemaker', projectRoot: '.codemaker/skills', globalRoot: '~/.codemaker/skills' },\n { id: 'codestudio', displayName: 'Code Studio', projectRoot: '.codestudio/skills', globalRoot: '~/.codestudio/skills' },\n {\n id: 'codex',\n displayName: 'Codex',\n projectRoot: '.agents/skills',\n globalRoot: '~/.codex/skills',\n // `codex exec \"<prompt>\"` runs one non-interactive task and prints the final message.\n // Verified 2026-07-11: https://developers.openai.com/codex/noninteractive\n defaultHandoffTemplate: ['codex', 'exec', HANDOFF_INSTRUCTION],\n cliBinaries: ['codex'],\n // TOML, not JSON: `[mcp_servers.<name>]` tables. User config at `~/.codex/config.toml`;\n // project config at `.codex/config.toml` (only honored for trusted projects). Verified\n // 2026-07-12: https://learn.chatgpt.com/docs/extend/mcp?surface=cli — detect/report-only\n // per the v0.5 contract (promote only writes JSON targets).\n mcpConfig: { project: '.codex/config.toml', global: '~/.codex/config.toml', format: 'toml', verified: true },\n },\n {\n id: 'command-code',\n displayName: 'Command Code',\n projectRoot: '.commandcode/skills',\n globalRoot: '~/.commandcode/skills',\n },\n { id: 'continue', displayName: 'Continue', projectRoot: '.continue/skills', globalRoot: '~/.continue/skills' },\n { id: 'cortex', displayName: 'Cortex Code', projectRoot: '.cortex/skills', globalRoot: '~/.snowflake/cortex/skills' },\n { id: 'crush', displayName: 'Crush', projectRoot: '.crush/skills', globalRoot: '~/.config/crush/skills' },\n {\n id: 'cursor',\n displayName: 'Cursor',\n projectRoot: '.agents/skills',\n globalRoot: '~/.cursor/skills',\n // Both scopes use a top-level `mcpServers` key. Verified 2026-07-12:\n // https://cursor.com/docs/context/mcp (\"Create `.cursor/mcp.json` in your project ...\";\n // \"Create `~/.cursor/mcp.json` in your home directory ...\").\n mcpConfig: { project: '.cursor/mcp.json', global: '~/.cursor/mcp.json', verified: true },\n },\n {\n id: 'deepagents',\n displayName: 'Deep Agents',\n projectRoot: '.agents/skills',\n globalRoot: '~/.deepagents/agent/skills',\n },\n { id: 'devin', displayName: 'Devin for Terminal', projectRoot: '.devin/skills', globalRoot: '~/.config/devin/skills' },\n { id: 'droid', displayName: 'Droid', projectRoot: '.factory/skills', globalRoot: '~/.factory/skills' },\n { id: 'eve', displayName: 'Eve', projectRoot: 'agent/skills' },\n { id: 'firebender', displayName: 'Firebender', projectRoot: '.agents/skills', globalRoot: '~/.firebender/skills' },\n { id: 'forgecode', displayName: 'ForgeCode', projectRoot: '.forge/skills', globalRoot: '~/.forge/skills' },\n {\n id: 'gemini-cli',\n displayName: 'Gemini CLI',\n projectRoot: '.agents/skills',\n globalRoot: '~/.gemini/skills',\n // `gemini -p \"<prompt>\"` executes a single turn headlessly and exits. Verified 2026-07-11:\n // https://google-gemini.github.io/gemini-cli/docs/cli/headless.html\n defaultHandoffTemplate: ['gemini', '-p', HANDOFF_INSTRUCTION],\n cliBinaries: ['gemini'],\n // `mcpServers` key inside `settings.json` (shared with other CLI settings, not an\n // MCP-exclusive file, but the correct read/write target). User config at\n // `~/.gemini/settings.json`, project config at `.gemini/settings.json`. Verified\n // 2026-07-12: https://google-gemini.github.io/gemini-cli/docs/tools/mcp-server.html\n mcpConfig: { project: '.gemini/settings.json', global: '~/.gemini/settings.json', verified: true },\n },\n {\n id: 'github-copilot',\n displayName: 'GitHub Copilot',\n projectRoot: '.agents/skills',\n globalRoot: '~/.copilot/skills',\n // Copilot CLI (matches the `~/.copilot` skills convention above, not the VS Code\n // extension — which uses a different `servers`-keyed `.vscode/mcp.json` and isn't covered\n // by this entry). User config `~/.copilot/mcp-config.json` (`mcpServers` key), repo-level\n // config `.github/mcp.json`. Verified 2026-07-12:\n // https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers\n mcpConfig: { project: '.github/mcp.json', global: '~/.copilot/mcp-config.json', verified: true },\n },\n { id: 'goose', displayName: 'Goose', projectRoot: '.goose/skills', globalRoot: '~/.config/goose/skills' },\n { id: 'hermes-agent', displayName: 'Hermes Agent', projectRoot: '.hermes/skills', globalRoot: '~/.hermes/skills' },\n {\n id: 'inference-sh',\n displayName: 'inference.sh',\n projectRoot: '.inferencesh/skills',\n globalRoot: '~/.inferencesh/skills',\n },\n { id: 'jazz', displayName: 'Jazz', projectRoot: '.jazz/skills', globalRoot: '~/.jazz/skills' },\n { id: 'junie', displayName: 'Junie', projectRoot: '.junie/skills', globalRoot: '~/.junie/skills' },\n { id: 'iflow-cli', displayName: 'iFlow CLI', projectRoot: '.iflow/skills', globalRoot: '~/.iflow/skills' },\n { id: 'kilo', displayName: 'Kilo Code', projectRoot: '.kilocode/skills', globalRoot: '~/.kilocode/skills' },\n { id: 'kiro-cli', displayName: 'Kiro CLI', projectRoot: '.kiro/skills', globalRoot: '~/.kiro/skills' },\n { id: 'kode', displayName: 'Kode', projectRoot: '.kode/skills', globalRoot: '~/.kode/skills' },\n { id: 'lingma', displayName: 'Lingma', projectRoot: '.lingma/skills', globalRoot: '~/.lingma/skills' },\n { id: 'mcpjam', displayName: 'MCPJam', projectRoot: '.mcpjam/skills', globalRoot: '~/.mcpjam/skills' },\n { id: 'mistral-vibe', displayName: 'Mistral Vibe', projectRoot: '.vibe/skills', globalRoot: '~/.vibe/skills' },\n { id: 'moxby', displayName: 'Moxby', projectRoot: '.moxby/skills', globalRoot: '~/.moxby/skills' },\n { id: 'mux', displayName: 'Mux', projectRoot: '.mux/skills', globalRoot: '~/.mux/skills' },\n {\n id: 'opencode',\n displayName: 'OpenCode',\n projectRoot: '.agents/skills',\n globalRoot: '~/.config/opencode/skills',\n // `opencode run \"<prompt>\"` runs one non-interactive prompt outside the TUI.\n // Verified 2026-07-11: https://opencode.ai/docs/cli/\n defaultHandoffTemplate: ['opencode', 'run', HANDOFF_INSTRUCTION],\n cliBinaries: ['opencode'],\n },\n { id: 'openhands', displayName: 'OpenHands', projectRoot: '.openhands/skills', globalRoot: '~/.openhands/skills' },\n { id: 'ona', displayName: 'Ona', projectRoot: '.ona/skills', globalRoot: '~/.ona/skills' },\n { id: 'pi', displayName: 'Pi', projectRoot: '.pi/skills', globalRoot: '~/.pi/agent/skills' },\n { id: 'qoder', displayName: 'Qoder', projectRoot: '.qoder/skills', globalRoot: '~/.qoder/skills' },\n { id: 'qoder-cn', displayName: 'Qoder CN', projectRoot: '.qoder/skills', globalRoot: '~/.qoder-cn/skills' },\n { id: 'qwen-code', displayName: 'Qwen Code', projectRoot: '.qwen/skills', globalRoot: '~/.qwen/skills' },\n { id: 'reasonix', displayName: 'Reasonix', projectRoot: '.reasonix/skills', globalRoot: '~/.reasonix/skills' },\n { id: 'rovodev', displayName: 'Rovo Dev', projectRoot: '.rovodev/skills', globalRoot: '~/.rovodev/skills' },\n { id: 'roo', displayName: 'Roo Code', projectRoot: '.roo/skills', globalRoot: '~/.roo/skills' },\n {\n id: 'tabnine-cli',\n displayName: 'Tabnine CLI',\n projectRoot: '.tabnine/agent/skills',\n globalRoot: '~/.tabnine/agent/skills',\n },\n { id: 'terramind', displayName: 'Terramind', projectRoot: '.terramind/skills', globalRoot: '~/.terramind/skills' },\n { id: 'tinycloud', displayName: 'Tinycloud', projectRoot: '.tinycloud/skills', globalRoot: '~/.tinycloud/skills' },\n { id: 'trae', displayName: 'Trae', projectRoot: '.trae/skills', globalRoot: '~/.trae/skills' },\n { id: 'trae-cn', displayName: 'Trae CN', projectRoot: '.trae/skills', globalRoot: '~/.trae-cn/skills' },\n {\n id: 'windsurf',\n displayName: 'Windsurf',\n projectRoot: '.windsurf/skills',\n globalRoot: '~/.codeium/windsurf/skills',\n // `mcpServers` key; global only — no project-scoped MCP config is documented. Verified\n // 2026-07-12: https://docs.windsurf.com/windsurf/cascade/mcp\n mcpConfig: { global: '~/.codeium/windsurf/mcp_config.json', verified: true },\n },\n { id: 'zcode', displayName: 'ZCode', projectRoot: '.zcode/skills', globalRoot: '~/.zcode/skills' },\n { id: 'zencoder', displayName: 'Zencoder', projectRoot: '.zencoder/skills', globalRoot: '~/.zencoder/skills' },\n { id: 'zenflow', displayName: 'Zenflow', projectRoot: '.zencoder/skills', globalRoot: '~/.zencoder/skills' },\n { id: 'neovate', displayName: 'Neovate', projectRoot: '.neovate/skills', globalRoot: '~/.neovate/skills' },\n { id: 'pochi', displayName: 'Pochi', projectRoot: '.pochi/skills', globalRoot: '~/.pochi/skills' },\n { id: 'promptscript', displayName: 'PromptScript', projectRoot: '.agents/skills' },\n { id: 'adal', displayName: 'AdaL', projectRoot: '.adal/skills', globalRoot: '~/.adal/skills' },\n ] satisfies AgentSpec[]\n).map(makeAgent);\n\n/** Looks up a known agent by its `--agent` id (e.g. `claude-code`). */\nexport function getAgent(id: string): Agent | undefined {\n return AGENTS.find((a) => a.id === id);\n}\n","import { existsSync } from 'node:fs';\nimport { writeNewFile } from '../util.js';\nimport { dirname, join, resolve as resolvePath } from 'node:path';\nimport { createInterface, type Interface } from 'node:readline/promises';\nimport { auditConfiguredSet, renderAuditMarkdown, scaffoldFoundationSkill, type AuditReport } from '../audit.js';\nimport { summarizeAudit, writeAuditState } from '../auditState.js';\nimport { getConfigPath, getHomeDir, loadConfig, saveConfig, type BusinessProfile, type Config } from '../config.js';\nimport { logOutcome } from '../decision.js';\nimport { UserError } from '../errors.js';\nimport { readAcceptedFindings } from '../findings.js';\nimport { runHandoff } from '../handoff.js';\nimport { maybeOfferInit } from './init.js';\n\n/** Bundled agent-side curation prompt handed off by `--handoff` — see assets/curation-prompt.md and src/handoff.ts's `resolvePromptPath`. */\nconst CURATION_PROMPT_ASSET = 'curation-prompt.md';\n\nexport interface AuditCommandOptions {\n json?: boolean;\n yes?: boolean;\n /** Overrides the default `~/.skill-forge/reports/audit-<ISO-ts>.md` path. */\n report?: string;\n /** Scaffold a business-foundation skill after the audit — same effect as answering yes to the interactive prompt. */\n foundation?: boolean;\n /** Hand the report off to a coding agent after the audit — same effect as answering yes to the interactive prompt. */\n handoff?: boolean;\n}\n\nfunction splitCsv(s: string): string[] {\n return s\n .split(',')\n .map((x) => x.trim())\n .filter(Boolean);\n}\n\n/** Asks `prompt` and returns the trimmed, lowercased answer — the mechanical part shared by this file's four y/n prompts; each call site keeps its own exact comparison/default logic. */\nasync function askAnswer(rl: Interface, prompt: string): Promise<string> {\n return (await rl.question(prompt)).trim().toLowerCase();\n}\n\n/**\n * Interactive business-profile capture — offers to reuse an existing stored profile (never\n * re-invents one silently), warns against entering credentials/confidential data before asking\n * anything, and shows a summarized confirm before returning. Returns `existing` unchanged if the\n * user declines to reuse-or-replace it (including on a fresh capture the user doesn't confirm).\n */\nasync function captureBusinessProfile(rl: Interface, existing: BusinessProfile | undefined): Promise<BusinessProfile | undefined> {\n console.log('\\n[skill-forge] Business profile — grounds the business-foundation skill scaffold and the curation handoff prompt.');\n console.log(\n '[skill-forge] Do not enter credentials, API keys, tokens, or confidential customer data — this is stored in plain text in config.json.'\n );\n\n if (existing) {\n const keep = await askAnswer(rl, `Reuse the existing business profile (captured ${existing.updatedAt})? [Y/n] `);\n if (keep === '' || keep === 'y' || keep === 'yes') return existing;\n }\n\n const businessName = (await rl.question('Business name (optional, Enter to skip): ')).trim() || undefined;\n const industry = (await rl.question('Industry (optional, Enter to skip): ')).trim() || undefined;\n const audiences = splitCsv(await rl.question('Audiences (comma-separated, optional): '));\n const workflows = splitCsv(await rl.question('Workflows (comma-separated, optional): '));\n const constraints = splitCsv(await rl.question('Constraints (comma-separated, optional): '));\n\n const profile: BusinessProfile = { businessName, industry, audiences, workflows, constraints, updatedAt: new Date().toISOString() };\n\n console.log('\\n[skill-forge] Business profile summary:');\n console.log(` Business name: ${profile.businessName ?? '(none)'}`);\n console.log(` Industry: ${profile.industry ?? '(none)'}`);\n console.log(` Audiences: ${profile.audiences.join(', ') || '(none)'}`);\n console.log(` Workflows: ${profile.workflows.join(', ') || '(none)'}`);\n console.log(` Constraints: ${profile.constraints.join(', ') || '(none)'}`);\n const confirm = await askAnswer(rl, 'Save this business profile? [Y/n] ');\n if (confirm === 'n' || confirm === 'no') return existing;\n\n return profile;\n}\n\nfunction resolveReportPath(reportOption: string | undefined): string {\n if (reportOption) return resolvePath(reportOption);\n const iso = new Date().toISOString().replace(/[:.]/g, '-');\n return join(getHomeDir(), 'reports', `audit-${iso}.md`);\n}\n\n\nfunction printTerminalSummary(report: AuditReport, reportPath: string): void {\n const { summary } = report;\n console.log('[skill-forge] audit complete');\n console.log(` skill roots: ${summary.rootCount} skills: ${summary.skillCount} MCP targets: ${summary.mcpTargetCount}`);\n console.log(\n ` findings: ${summary.findingCounts.CRITICAL} CRITICAL, ${summary.findingCounts.HIGH} HIGH, ` +\n `${summary.findingCounts.MEDIUM} MEDIUM, ${summary.findingCounts.LOW} LOW`\n );\n console.log(` accepted: ${summary.acceptedCount} (suppressed from counts; see report)`);\n console.log(` overlap clusters: ${summary.overlapClusterCount} evolve-eligible: ${summary.evolveEligibleCount}`);\n console.log(` report -> ${reportPath}`);\n for (const notice of report.notices) console.log(` [notice] ${notice}`);\n console.log(\n ' next steps: `skill-forge add <source>` to install something new; ' +\n '`skill-forge evolve <skill-dir>` to refine an evolve-eligible skill.'\n );\n}\n\nasync function maybeScaffoldFoundation(\n config: Config,\n report: AuditReport,\n options: AuditCommandOptions,\n rl: Interface | undefined\n): Promise<Config> {\n let wantFoundation = options.foundation ?? false;\n\n if (!wantFoundation && rl && config.businessProfile && report.opportunities.foundation.available) {\n const answer = await askAnswer(rl, '\\nCreate a business-foundation skill now? [y/N] ');\n wantFoundation = answer === 'y' || answer === 'yes';\n }\n if (!wantFoundation) return config;\n\n if (!config.businessProfile) {\n throw new UserError(\n '--foundation requires a business profile — run `skill-forge audit` interactively first to capture one.'\n );\n }\n\n const targetRoot = config.defaultTarget ?? config.skillsRoots[0];\n if (!targetRoot) {\n throw new UserError('cannot scaffold a foundation skill: no skills root configured.');\n }\n\n const foundationPath = await scaffoldFoundationSkill(config, config.businessProfile, targetRoot);\n const updated: Config = { ...config, foundationSkillPath: foundationPath };\n saveConfig(updated);\n logOutcome(`[skill-forge] scaffolded business-foundation skill -> ${foundationPath}`, options.json);\n return updated;\n}\n\nasync function maybeHandoff(config: Config, reportPath: string, options: AuditCommandOptions, rl: Interface | undefined): Promise<void> {\n let wantHandoff = options.handoff ?? false;\n\n if (!wantHandoff && rl) {\n const answer = await askAnswer(rl, '\\nHand the curation report to a coding agent now? [y/N] ');\n wantHandoff = answer === 'y' || answer === 'yes';\n }\n if (!wantHandoff) return;\n\n // In --json mode, stdout must stay exactly one JSON document (already printed above) — route\n // runHandoff's own print-only messaging (no agent found / configured binary missing) to\n // stderr instead of its default stdout sink (v0.8 fix).\n runHandoff(config, reportPath, { promptAsset: CURATION_PROMPT_ASSET, log: options.json ? console.error : console.log });\n}\n\n/**\n * `skill-forge audit` (alias `doctor`) — a re-runnable, doctor-style health check over the\n * configured skill/MCP set: inventory, hygiene/safety findings, and consolidation/refinement\n * opportunities (src/audit.ts). Requires a real, persisted config — never silently audits an\n * invented default. `--foundation`/`--handoff` are separate, explicit opt-ins: `--yes`/`--json`\n * never imply either, matching the plan's \"agent handoff and foundation writes are opt-in\" hard\n * invariant.\n */\nexport async function auditCommand(options: AuditCommandOptions = {}): Promise<void> {\n // suppressAuditOffer: true — if this offers and runs `init`, init must NOT also offer/run its\n // own tail audit (that would run the whole interactive profile Q&A + report a second time);\n // this function is already the single audit run that happens once init returns (v0.8 fix).\n await maybeOfferInit({ json: options.json, yes: options.yes, suppressAuditOffer: true });\n\n if (!existsSync(getConfigPath())) {\n throw new UserError(\n 'no skill-forge config found — run `skill-forge init` first (audit requires a configured skill/MCP set; it never audits an invented default).'\n );\n }\n\n let config = loadConfig();\n const interactive = !options.json && !options.yes && Boolean(process.stdin.isTTY);\n\n const rl = interactive ? createInterface({ input: process.stdin, output: process.stdout }) : undefined;\n try {\n if (rl) {\n const profile = await captureBusinessProfile(rl, config.businessProfile);\n if (profile !== config.businessProfile) {\n config = { ...config, businessProfile: profile };\n saveConfig(config);\n }\n }\n\n // v0.12: load human-accepted findings and fold any store-read notices (missing/corrupt store,\n // dropped records) straight into the returned report — the engine itself stays pure (only\n // takes the already-loaded `records`, see AuditOptions.accepted's doc comment).\n const { records: accepted, notices: storeNotices } = readAcceptedFindings();\n const report = await auditConfiguredSet(config, { accepted });\n if (storeNotices.length > 0) report.notices = [...report.notices, ...storeNotices];\n\n const reportPath = resolveReportPath(options.report);\n writeNewFile(reportPath, renderAuditMarkdown(report), '--report');\n\n // v0.11: leave a machine-readable residue so add/scan/status/guide/routine can cite this run.\n // Advisory only — nothing derived from it may change a gate verdict (see src/auditState.ts).\n // A failure here must not lose the report that was just written successfully.\n try {\n writeAuditState(summarizeAudit(report, reportPath));\n } catch (err) {\n console.error(`[skill-forge] wrote ${reportPath}, but recording audit state failed: ${(err as Error).message}`);\n }\n\n if (options.json) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n printTerminalSummary(report, reportPath);\n }\n\n config = await maybeScaffoldFoundation(config, report, options, rl);\n await maybeHandoff(config, reportPath, options, rl);\n } finally {\n rl?.close();\n }\n}\n","import { createInterface } from 'node:readline/promises';\nimport type { Verdict } from './gate/safety.js';\n\n/**\n * Shared promote/hold/reject decision helpers — the same decision flow\n * `add`/`scan` use (`docs/gate-policy.md`'s \"Decision flow\" section),\n * reused by `evolve.ts` for its re-gate step. Moved here (v0.7 simplify\n * pass) from `commands/add.ts`, which imports these instead of defining\n * its own copies.\n */\n\nexport type Decision = 'promote' | 'hold' | 'reject';\n\n/** Prints a decision-outcome line: stdout normally, stderr in --json mode so stdout stays a single parseable JSON payload. */\nexport function logOutcome(message: string, json: boolean | undefined): void {\n if (json) console.error(message);\n else console.log(message);\n}\n\n/** --yes/--json honors the verdict: a block is rejected (nonzero exit); pass/warn is promoted. */\nexport function yesDecision(verdict: Verdict): Decision {\n return verdict === 'block' ? 'reject' : 'promote';\n}\n\n/**\n * Prompts interactively for promote/hold/reject, defaulting the suggested\n * answer to the verdict. `subject` names what's being decided in the\n * prompt text (e.g. `\"this skill\"`, `\"this evolution proposal\"`) so each\n * caller keeps its own exact prompt wording.\n */\nexport async function promptDecision(verdict: Verdict, subject: string): Promise<Decision> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const defaultChoice: Decision = verdict === 'block' ? 'reject' : verdict === 'warn' ? 'hold' : 'promote';\n const prompt = `Promote, hold, or reject ${subject}? [promote/hold/reject] (default: ${defaultChoice}) `;\n try {\n // Iterate lines via the async-iterator protocol rather than looping on\n // rl.question(): question() attaches a one-shot 'line' listener per call,\n // and on piped (non-TTY) input where several answer lines arrive in a\n // single chunk, a line can be emitted — and silently dropped — between\n // one question() resolving and the next one attaching its listener. The\n // async iterator queues lines internally, so no answer is ever lost.\n process.stdout.write(prompt);\n for await (const raw of rl) {\n const answer = raw.trim().toLowerCase() || defaultChoice;\n if (answer === 'p' || answer === 'promote') return 'promote';\n if (answer === 'h' || answer === 'hold') return 'hold';\n if (answer === 'r' || answer === 'reject') return 'reject';\n process.stdout.write(`Please answer promote, hold, or reject (or p/h/r).\\n${prompt}`);\n }\n // stdin closed without a valid answer — fail safe to the verdict's default.\n return defaultChoice;\n } finally {\n rl.close();\n }\n}\n","import { loadConfig } from '../config.js';\nimport { resolve as resolveSource, type ArtifactType } from '../install/resolve.js';\nimport { quarantine } from '../install/quarantine.js';\nimport { reject } from '../install/promote.js';\nimport { printGateOutput, runGate, resolveMcpCandidatePath } from './gatePipeline.js';\nimport { maybeOfferInit } from './init.js';\n\nexport interface ScanOptions {\n json?: boolean;\n /** Gate as a skill (default) or an MCP server — v0.5. */\n artifact?: ArtifactType;\n /** Path to a generated rhize-plugins skill map; reports existing-capability overlap against it (skill artifacts only). */\n skillMap?: string;\n}\n\n/**\n * `skill-forge scan <source>` — run the safety gate against a skill (or, with\n * `--artifact mcp`, an MCP server — v0.5) without installing it into a target\n * skills root/MCP config: quarantine-install for inspection, gate + report,\n * then always clean up the quarantine sandbox regardless of verdict. Exits\n * nonzero when the safety verdict is \"block\". `--json` prints the same\n * gate-result payload shape as `add`'s.\n */\nexport async function scan(source: string, options: ScanOptions = {}): Promise<void> {\n await maybeOfferInit({ json: options.json });\n\n const config = loadConfig();\n const artifact: ArtifactType = options.artifact ?? 'skill';\n // Same resolution chain as `add`, so scan and add rank overlap against the same root.\n // Only required for skill artifacts — MCP overlap ranks against config.mcpTargets instead.\n const skillsRoot = config.defaultTarget ?? config.skillsRoots[0];\n if (artifact === 'skill' && !skillsRoot) {\n throw new Error('no skills root configured; set skillsRoots in the config');\n }\n\n const resolved = resolveSource(source, artifact);\n const entry = await quarantine(resolved, config.quarantineDir);\n\n try {\n let candidatePath: string;\n if (artifact === 'mcp') {\n candidatePath = resolveMcpCandidatePath(entry);\n } else {\n if (entry.skillDirs.length === 0) {\n console.error(`[skill-forge] no SKILL.md found in installed source: ${source}`);\n process.exitCode = 1;\n return;\n }\n if (entry.skillDirs.length > 1) {\n console.error(\n `[skill-forge] ${entry.skillDirs.length} skills found in ${source}; scanning the first:\\n` +\n entry.skillDirs.map((d) => ` - ${d}`).join('\\n')\n );\n }\n candidatePath = entry.skillDirs[0];\n }\n\n const { safety, gateResult } = await runGate(candidatePath, skillsRoot ?? '', {\n strictness: config.strictness,\n artifact,\n mcpTargets: config.mcpTargets ?? [],\n skillMapPath: options.skillMap,\n });\n\n if (artifact === 'mcp' && gateResult.profile.kind === 'mcp' && gateResult.profile.serverCount > 1) {\n console.error(\n `[skill-forge] ${gateResult.profile.serverCount} MCP servers found in ${source}; scanning the first — ` +\n gateResult.profile.servers.join(', ')\n );\n }\n\n printGateOutput(gateResult, options.json);\n\n if (safety.verdict === 'block') {\n process.exitCode = 1;\n }\n } finally {\n reject(entry);\n }\n}\n","import { join } from 'node:path';\nimport { loadConfig } from '../config.js';\nimport { findSkillDirs, listQuarantineIds } from '../install/quarantine.js';\nimport { readFrontmatter } from '../gate/overlap.js';\nimport { maybeOfferInit } from './init.js';\n\n/**\n * `skill-forge list` — list skills currently held in quarantine (installed\n * but not yet promoted or rejected via `add`).\n */\nexport async function list(): Promise<void> {\n await maybeOfferInit();\n\n const config = loadConfig();\n const { quarantineDir } = config;\n\n const ids = listQuarantineIds(quarantineDir);\n\n if (ids.length === 0) {\n console.log(`[skill-forge] quarantine dir: ${quarantineDir}`);\n console.log(' (empty — nothing held)');\n return;\n }\n\n console.log(`[skill-forge] ${ids.length} held in quarantine (${quarantineDir}):`);\n for (const id of ids) {\n const skillDirs = findSkillDirs(join(quarantineDir, id));\n if (skillDirs.length === 0) {\n console.log(` ${id} (no SKILL.md found)`);\n continue;\n }\n const names = skillDirs.map((dir) => {\n try {\n return readFrontmatter(join(dir, 'SKILL.md')).name;\n } catch {\n return dir;\n }\n });\n console.log(` ${id} -> ${names.join(', ')}`);\n }\n}\n","import { existsSync } from 'node:fs';\nimport { STALE_AUDIT_DAYS, auditAgeDays, formatAuditFreshness, readAuditState, type AuditState } from '../auditState.js';\nimport { getConfigPath, loadConfig } from '../config.js';\nimport { UserError } from '../errors.js';\nimport { pendingQueueCount } from '../queue.js';\nimport { heldCount } from './held.js';\n\n/**\n * `skill-forge guide [topic]` — in-session orientation: what this CLI does, what state THIS\n * machine is currently in, and the one command that most likely comes next.\n *\n * Deliberately different from the two help surfaces that already exist. Commander's `--help`\n * lists flags but can't say what to do; `status` prints configuration but not what it means or\n * what follows. `guide` is the \"I'm mid-session, orient me\" answer, which is also the shape an\n * agent needs when it lands in a repo and has to decide whether to gate, ingest, or refine.\n *\n * Hard constraints: read-only and NON-INTERACTIVE — no prompts (so it's safe inside an agent\n * turn), no `maybeOfferInit`, and no writes anywhere. It never invents a config: with no\n * config.json it says so and points at `init`, rather than describing `defaultConfig()`'s\n * placeholder paths as if they were real settings.\n */\n\ninterface Topic {\n name: string;\n summary: string;\n body: string[];\n}\n\n/**\n * Topic pages stay deliberately short and point at the authoritative doc rather than restating\n * flag lists — a second copy of the flag reference here would drift from `--help` within a\n * release. Anything longer than this belongs in README.md or docs/.\n */\nconst TOPICS: Topic[] = [\n {\n name: 'pipeline',\n summary: 'how a candidate travels from source to installed skill',\n body: [\n 'Every candidate — a skills.sh slug, a git URL, a local path, or an npm MCP server —',\n 'goes through the same stages, and NOTHING is written to a skills root until you promote:',\n '',\n ' 1. resolve fetch the source into quarantine (~/.skill-forge/quarantine)',\n ' 2. profile read what it declares: name, description, files, MCP capabilities',\n ' 3. safety static deny-pattern scan (never executes the candidate)',\n ' 4. overlap score it against what you already have',\n ' 5. report one verdict: promote / hold / reject',\n ' 6. promote copy into the target skills root, record provenance in SOURCES.md',\n '',\n '`skill-forge add <source>` runs all six. `skill-forge scan <source>` stops after 5 —',\n 'same gate, no install. See README \"add\" and docs/gate-policy.md.',\n ],\n },\n {\n name: 'verbs',\n summary: 'the five decision verbs used after a candidate clears the gate',\n body: [\n 'The gate decides whether something is SAFE. It does not decide whether it is WORTH',\n 'having — that judgment is an agent/human pass over the queue, recorded as one verb:',\n '',\n ' DEFER adopt as-is; no absorb/fork judgment needed',\n ' ABSORB take the useful part into a skill you already have, drop the rest',\n ' FORK keep your own version; the candidate diverges too much to merge',\n ' REJECT do not adopt',\n ' WATCH not now, but track the upstream for changes',\n '',\n 'The verb lands in the skill\\'s SOURCES.md ledger, which is what `watch` later checks',\n 'for drift. See docs/forge-workflow.md for the full decision matrix.',\n ],\n },\n {\n name: 'queue',\n summary: 'the pending-decision queue and how it gets drained',\n body: [\n 'Promoting a candidate appends an entry to ~/.skill-forge/queue.json — a reminder that a',\n 'human/agent decide pass still owes this skill a verb. The gate is code; the verb is',\n 'judgment, so they are separate steps on purpose.',\n '',\n ' skill-forge ingest hand every pending entry to your agent with the prompt',\n ' skill-forge queue close <id> --status ingested|dismissed',\n '',\n 'Entry paths are validated against your quarantine dir and configured roots before any',\n 'handoff — an entry pointing somewhere else is reported, not silently included.',\n 'Schema: schemas/queue-entry.schema.json + docs/queue-schema.md.',\n ],\n },\n {\n name: 'mcp',\n summary: 'gating MCP servers, not just skills',\n body: [\n 'MCP server candidates run through the same six stages as skills. Classification is',\n 'automatic from a source\\'s .mcp.json / MCP-shaped package.json; force it with',\n '`--artifact mcp` (required for a bare npm package name, which is otherwise ambiguous).',\n '',\n 'Extra guarantees specific to MCP: an npm candidate is `npm pack`ed and EXTRACTED, never',\n 'installed and never scripted; capabilities are read statically from source text, never by',\n 'running the server; promotion writes env var NAMES with empty values, never values copied',\n 'from a candidate\\'s examples; and an unpinned `npx` launch spec is auto-pinned or refused.',\n '',\n 'Codex-style TOML configs are detect-and-report only — promote writes JSON targets.',\n ],\n },\n {\n name: 'refine',\n summary: 'fixing an installed skill without editing it in place',\n body: [\n 'When an installed skill misbehaves, capture the gap instead of hand-editing it:',\n '',\n ' skill-forge refine capture expected-vs-actual + a scoped patch',\n ' skill-forge refine list what has been captured so far',\n ' skill-forge refine patterns gaps seen in more than one project',\n ' skill-forge refine promote <id> fold a proven pattern into the base skill',\n ' skill-forge refine which <skill> which override files apply, in what order',\n '',\n 'Capture only ever writes PROJECT-scope override artifacts — the base SKILL.md is touched',\n 'only by `promote`, and only for a pattern that has recurred in a second, different',\n 'project. `evolve` is the other direction: whole-skill, automated, then re-gated.',\n ],\n },\n {\n name: 'maintenance',\n summary: 'commands that operate on the set you already have',\n body: [\n ' skill-forge audit health check over everything configured: inventory, hygiene,',\n ' safety findings, overlap clusters, consolidation opportunities',\n ' skill-forge organize capability registry + dependency graph across every root',\n ' skill-forge watch drift check against each SOURCES.md ledger (--offline to skip',\n ' all network checks; a ledger\\'s stored command is never executed)',\n ' skill-forge find discover skills via skills.sh',\n '',\n 'audit is the one to reach for first — it is re-runnable, read-only by default, and',\n 'writes its report 0600 to ~/.skill-forge/reports/.',\n ],\n },\n {\n name: 'pro',\n summary: 'which features are Pro, and what that means during the 0.x beta',\n body: [\n 'Pro features: overlap analysis (skill + MCP), the pending-ingestion queue and `ingest`,',\n 'provenance/`watch`, `organize`, `evolve`, and `refine`.',\n '',\n 'During the 0.x beta they all run for everyone — each prints a one-line notice, which a',\n 'valid license key suppresses. Everything else (quarantine, profiling, the full safety',\n 'scan, promotion, MCP promotion, `audit`, `find`, `init`) is MIT and free permanently.',\n ],\n },\n];\n\n/** Read-only snapshot of what this machine currently has. Every field is cheap to compute. */\ninterface GuideState {\n configured: boolean;\n configPath: string;\n gateTargets: number;\n defaultTarget?: string;\n quarantined: number;\n /** False when the quarantine dir exists but cannot be listed — reported, never shown as 0. */\n quarantineReadable: boolean;\n pendingQueue: number;\n /** False when queue.json exists but is corrupt. */\n queueReadable: boolean;\n hasBusinessProfile: boolean;\n foundationSkillPath?: string;\n audit?: AuditState;\n}\n\nfunction readState(): GuideState {\n const configPath = getConfigPath();\n const configured = existsSync(configPath);\n const base = {\n configured,\n configPath,\n gateTargets: 0,\n quarantined: 0,\n quarantineReadable: true,\n pendingQueue: 0,\n queueReadable: true,\n hasBusinessProfile: false,\n audit: readAuditState(),\n };\n if (!configured) return base;\n\n const config = loadConfig();\n // Both helpers degrade to 0 on an unreadable quarantine dir or a malformed queue.json — never\n // abort the one command a confused user is most likely to run.\n const quarantined = heldCount(config.quarantineDir);\n const pending = pendingQueueCount();\n\n return {\n ...base,\n gateTargets: config.skillsRoots.length,\n defaultTarget: config.defaultTarget,\n quarantined: quarantined.count,\n quarantineReadable: quarantined.readable,\n pendingQueue: pending.count,\n queueReadable: pending.readable,\n hasBusinessProfile: config.businessProfile !== undefined,\n foundationSkillPath: config.foundationSkillPath,\n };\n}\n\n/** The single most useful next command, in priority order — unfinished work first. */\nfunction nextStep(state: GuideState): { command: string; why: string } {\n if (!state.configured) {\n return { command: 'skill-forge init', why: 'no config yet — detect your agents and set gate targets' };\n }\n if (state.pendingQueue > 0) {\n const n = state.pendingQueue;\n return { command: 'skill-forge ingest', why: `${n} promoted item${n === 1 ? '' : 's'} still awaiting a decide-pass verb` };\n }\n if (state.quarantined > 0) {\n const n = state.quarantined;\n return {\n command: 'skill-forge list',\n why: `${n} candidate${n === 1 ? '' : 's'} held in quarantine — finish each with \\`promote <id>\\` or \\`reject <id>\\``,\n };\n }\n if (!state.audit) {\n return { command: 'skill-forge audit', why: 'no audit on record — start with a health check of what you already have' };\n }\n const age = auditAgeDays(state.audit);\n if (age !== undefined && age >= STALE_AUDIT_DAYS) {\n return { command: 'skill-forge audit', why: `last audit was ${age} days ago — your installed set has probably moved since` };\n }\n return { command: 'skill-forge find <query>', why: 'nothing pending — discover a skill, then `add` it to run the gate' };\n}\n\nfunction printState(state: GuideState): void {\n console.log('\\nWhere you are:');\n if (!state.configured) {\n console.log(` config: none yet (${state.configPath})`);\n } else {\n console.log(` config: ${state.configPath}`);\n console.log(` gate targets: ${state.gateTargets}${state.defaultTarget ? ` (default: ${state.defaultTarget})` : ''}`);\n console.log(` quarantined: ${state.quarantineReadable ? state.quarantined : 'UNREADABLE'}`);\n console.log(` queue: ${state.queueReadable ? `${state.pendingQueue} pending` : 'UNREADABLE (queue.json is corrupt)'}`);\n console.log(` last audit: ${formatAuditFreshness(state.audit)}`);\n if (state.foundationSkillPath) console.log(` foundation: ${state.foundationSkillPath}`);\n else if (state.hasBusinessProfile) console.log(' foundation: business profile captured, no foundation skill scaffolded');\n }\n}\n\nexport async function guide(topic?: string): Promise<void> {\n if (topic !== undefined) {\n const found = TOPICS.find((t) => t.name === topic.toLowerCase());\n if (!found) {\n throw new UserError(\n `unknown guide topic \"${topic}\" — available topics: ${TOPICS.map((t) => t.name).join(', ')}`,\n );\n }\n console.log(`[skill-forge] guide: ${found.name} — ${found.summary}\\n`);\n for (const line of found.body) console.log(line);\n return;\n }\n\n const state = readState();\n const next = nextStep(state);\n\n console.log('[skill-forge] the supply-chain gate for agent skills');\n console.log('');\n console.log('Skills and MCP servers are third-party code that your agent will read and act on.');\n console.log('skill-forge quarantines a candidate, scans it, shows you what it overlaps with, and');\n console.log('installs it only when you say so — then keeps track of what you installed and why.');\n\n printState(state);\n\n console.log('\\nSuggested next step:');\n console.log(` ${next.command}`);\n console.log(` ${next.why}`);\n\n console.log('\\nTopics — `skill-forge guide <topic>`:');\n for (const t of TOPICS) {\n console.log(` ${t.name.padEnd(12)} ${t.summary}`);\n }\n console.log('\\n`skill-forge --help` lists every command and flag; `skill-forge status` prints raw config.');\n}\n","import { existsSync, realpathSync } from 'node:fs';\nimport { basename, join, resolve as resolvePath, sep } from 'node:path';\nimport { loadConfig, type Config } from '../config.js';\nimport { UserError } from '../errors.js';\nimport { findSkillDirs, listQuarantineIds, type QuarantineEntry } from '../install/quarantine.js';\nimport { promote, reject } from '../install/promote.js';\nimport { fingerprintDirs } from '../install/fingerprint.js';\nimport { promoteMcp, type McpServerProfile } from '../install/promoteMcp.js';\nimport { resolveMcpCandidateDetail } from '../install/mcpCandidate.js';\nimport { appendProvenance } from '../provenance.js';\nimport { appendQueueEntry, type CountResult } from '../queue.js';\nimport { readFrontmatter, suggestVerbCode } from '../gate/overlap.js';\nimport { formatSafetyFinding } from '../gate/scanShared.js';\nimport { printGateOutput, runGate, resolveMcpCandidatePath } from './gatePipeline.js';\nimport { logOutcome, promptDecision, yesDecision } from '../decision.js';\n\n/**\n * `skill-forge promote <id>` / `skill-forge reject <id>` — finish a decision that was HELD.\n *\n * Before v0.11 \"hold\" was a dead end: `promote()`/`reject()` were reachable only from inside a\n * single `add`/`scan` run, so answering \"hold\" parked the sandbox in the quarantine dir with no\n * command that could ever finish the decision. `list` showed it; nothing could act on it.\n *\n * `promote` RE-GATES the held sandbox rather than promoting it blind. The original run's gate\n * result was never persisted, so the alternative would be writing a queue entry with a fabricated\n * `gate` record — and a hold may be days old, with the ruleset (or your installed set, which\n * overlap scores against) since changed. Re-scanning is static and cheap, so the honest answer is\n * to just run the gate again and show it.\n */\n\nconst PRO_FEATURE_LABEL = 'overlap analysis, the provenance ledger, and the pending-ingestion queue';\n\n/**\n * Resolves a held id to its sandbox, refusing anything that escapes the configured quarantine dir.\n *\n * The id is untrusted CLI input used to build a path that then gets DELETED or MOVED, so\n * `../../foo`, an absolute path, or a symlinked sandbox must never resolve outside the quarantine\n * root. Enforced by rejecting any id that isn't a bare path segment, then canonicalizing both\n * sides with `realpathSync` and requiring a true path-segment prefix match (comparing against\n * `root + sep` stops `/q-evil` from passing as a child of `/q`).\n */\nfunction resolveHeldPath(quarantineDir: string, id: string): string {\n if (id === '' || id !== basename(id) || id === '.' || id === '..') {\n throw new UserError(`invalid quarantine id \"${id}\" — pass a single id exactly as printed by \\`skill-forge list\\``);\n }\n const candidate = join(quarantineDir, id);\n if (!existsSync(candidate)) {\n throw new UserError(`no held entry with id \"${id}\" — run \\`skill-forge list\\` to see what is in quarantine`);\n }\n\n const realRoot = realpathSync(resolvePath(quarantineDir));\n const realCandidate = realpathSync(candidate);\n if (!realCandidate.startsWith(realRoot + sep)) {\n throw new UserError(`refusing to act on \"${id}\": it resolves outside the quarantine dir (${realRoot})`);\n }\n return realCandidate;\n}\n\n/**\n * Rebuilds the `QuarantineEntry` promote/reject need from a sandbox already on disk. The original\n * `ResolvedSource` only ever existed in memory during the `add` run, so it is reconstructed as the\n * local sandbox path — accurate for what promote/reject do with it, and never presented as the\n * original upstream (provenance below records `quarantine:<id>` rather than inventing a URL).\n */\nfunction heldEntry(id: string, quarantinePath: string): QuarantineEntry {\n return {\n id,\n quarantinePath,\n skillDirs: findSkillDirs(quarantinePath),\n source: { type: 'local', value: quarantinePath },\n };\n}\n\n/** What `reject <id>` accepts — deliberately narrow, so no caller can believe it honors --force/--json. */\nexport interface RejectHeldOptions {\n /** Skip the confirmation prompt. */\n yes?: boolean;\n}\n\nexport interface HeldOptions extends RejectHeldOptions {\n /** Skills root to promote into (defaults to config defaultTarget, then skillsRoots[0]). */\n target?: string;\n /** Overwrite an existing same-name skill dir / MCP server entry in the target. */\n force?: boolean;\n /** Print the re-gate result as JSON instead of the terminal report. */\n json?: boolean;\n /** MCP config file to promote an MCP candidate into (defaults to config.mcpTargets[0], then <cwd>/.mcp.json). */\n mcpTarget?: string;\n}\n\nfunction resolveTargetRoot(config: Config, target: string | undefined): string {\n const root = target ?? config.defaultTarget ?? config.skillsRoots[0];\n if (!root) {\n throw new UserError('no target skills root configured; pass --target <dir> or run `skill-forge init`');\n }\n return root;\n}\n\n/** Names of the skills inside a held sandbox, for the confirmation text. Falls back to the dir name. */\nfunction heldSkillNames(skillDirs: string[]): string[] {\n return skillDirs.map((dir) => {\n try {\n return readFrontmatter(join(dir, 'SKILL.md')).name;\n } catch {\n return basename(dir);\n }\n });\n}\n\n/**\n * True when a held sandbox looks like an MCP candidate rather than a skill: `resolveMcpCandidatePath`\n * resolves to a directory carrying an `.mcp.json`/`mcp.json`, or an npm-packed `package/` dir.\n * Static probe only — nothing in the sandbox is executed.\n */\nfunction looksLikeMcpSandbox(quarantinePath: string): boolean {\n const candidate = resolveMcpCandidatePath({ quarantinePath } as QuarantineEntry);\n return ['.mcp.json', 'mcp.json', 'package.json'].some((f) => existsSync(join(candidate, f)));\n}\n\n/**\n * The MCP half of `promote <id>`: re-gate the held server candidate, then write its entry into an\n * MCP config target. Mirrors `add`'s MCP promote path exactly — env values are never copied from\n * the candidate, the target is backed up first, and an unpinned npx spec is auto-pinned or refused.\n */\nasync function promoteHeldMcp(\n id: string,\n entry: QuarantineEntry,\n config: Config,\n options: HeldOptions\n): Promise<void> {\n const candidatePath = resolveMcpCandidatePath(entry);\n\n logOutcome(`[skill-forge] re-gating held MCP entry ${id}`, options.json);\n const { safety, gateResult, proEnabled } = await runGate(candidatePath, '', {\n proFeatureLabel: PRO_FEATURE_LABEL,\n strictness: config.strictness,\n artifact: 'mcp',\n mcpTargets: config.mcpTargets ?? [],\n });\n printGateOutput(gateResult, options.json);\n\n const { profile, overlap } = gateResult;\n if (profile.kind !== 'mcp') {\n throw new Error(`expected an MCP candidate, found a ${profile.kind}`);\n }\n\n const decision = options.yes ? yesDecision(safety.verdict) : await promptDecision(safety.verdict, `held entry ${id}`);\n if (decision === 'hold') {\n logOutcome('[skill-forge] still held — quarantine sandbox left in place.', options.json);\n return;\n }\n if (decision === 'reject') {\n reject(entry);\n logOutcome(`[skill-forge] rejected — removed ${entry.quarantinePath}`, options.json);\n return;\n }\n\n const detail = resolveMcpCandidateDetail(profile);\n const serverProfile: McpServerProfile = {\n name: detail.name,\n version: detail.version,\n packageName: detail.packageName,\n envVars: detail.env ? Object.keys(detail.env) : undefined,\n candidateEntry: detail.command ? { command: detail.command, args: detail.args, env: detail.env } : undefined,\n };\n const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join(process.cwd(), '.mcp.json');\n\n const result = promoteMcp(entry, serverProfile, mcpTargetPath, { force: options.force });\n reject(entry); // promoteMcp never touches the sandbox — clean it up now the entry is written\n\n logOutcome(`[skill-forge] promoted -> ${result.target} (server \"${result.serverName}\")`, options.json);\n if (result.autoPinned) {\n logOutcome(`[skill-forge] pinned launch spec ${result.autoPinned.from} -> ${result.autoPinned.to}`, options.json);\n }\n if (result.backupPath) logOutcome(`[skill-forge] backed up existing config -> ${result.backupPath}`, options.json);\n if (result.envVarsToFill.length > 0) {\n logOutcome(`[skill-forge] fill in these env vars before use: ${result.envVarsToFill.join(', ')}`, options.json);\n }\n\n if (!proEnabled) return;\n appendQueueEntry({\n source: `quarantine:${id}`,\n sourceType: 'local',\n installedPath: result.target,\n quarantinePath: entry.quarantinePath,\n gate: {\n license: null,\n safetyVerdict: safety.verdict,\n safetyFindings: safety.findings.map(formatSafetyFinding),\n overlapTop: overlap ? overlap.ranking.slice(0, 3).map((r) => ({ skill: r.skill, score: r.score })) : [],\n suggestedVerb: overlap ? suggestVerbCode(overlap.nearestScore) : null,\n },\n status: 'pending',\n artifactType: 'mcp',\n capabilities: profile.capabilities,\n });\n logOutcome('[skill-forge] queued for the decide pass — run `skill-forge ingest`.', options.json);\n}\n\n/** `skill-forge promote <id>` — re-gate a held sandbox, then promote / hold / reject it. */\nexport async function promoteHeld(id: string, options: HeldOptions = {}): Promise<void> {\n const config = loadConfig();\n const quarantinePath = resolveHeldPath(config.quarantineDir, id);\n const entry = heldEntry(id, quarantinePath);\n\n // A held MCP candidate has no SKILL.md anywhere — `add --artifact mcp` holds the same way `add`\n // does, so refusing here would leave MCP entries in exactly the dead end this command exists to\n // close (reject was their only exit).\n if (entry.skillDirs.length === 0) {\n if (looksLikeMcpSandbox(quarantinePath)) {\n await promoteHeldMcp(id, entry, config, options);\n return;\n }\n throw new UserError(\n `held entry \"${id}\" contains neither a SKILL.md nor an MCP manifest — use \\`skill-forge reject ${id}\\` to discard it`\n );\n }\n\n const targetRoot = resolveTargetRoot(config, options.target);\n const candidatePath = entry.skillDirs[0];\n // Captured before the re-gate and verified inside promote(). This is the case the check exists\n // for: a hold can sit for days, so the gap between \"scanned\" and \"installed\" is wide open.\n const gatedFingerprint = fingerprintDirs(entry.skillDirs).digest;\n\n logOutcome(`[skill-forge] re-gating held entry ${id} (${heldSkillNames(entry.skillDirs).join(', ')})`, options.json);\n const { safety, gateResult, proEnabled } = await runGate(candidatePath, targetRoot, {\n proFeatureLabel: PRO_FEATURE_LABEL,\n strictness: config.strictness,\n });\n printGateOutput(gateResult, options.json);\n\n const { profile, overlap } = gateResult;\n if (profile.kind !== 'skill') {\n // Unreachable in practice — narrows the Profile|McpProfile union, same guard add.ts uses.\n throw new Error(`expected a skill candidate, found a ${profile.kind}`);\n }\n\n const decision = options.yes ? yesDecision(safety.verdict) : await promptDecision(safety.verdict, `held entry ${id}`);\n\n if (decision === 'hold') {\n logOutcome('[skill-forge] still held — quarantine sandbox left in place.', options.json);\n return;\n }\n if (decision === 'reject') {\n reject(entry);\n logOutcome(`[skill-forge] rejected — removed ${quarantinePath}`, options.json);\n return;\n }\n\n const result = promote(entry, targetRoot, { force: options.force, expectedFingerprint: gatedFingerprint });\n logOutcome(`[skill-forge] promoted ${result.promoted.length} skill dir(s):`, options.json);\n for (const promotedPath of result.promoted) logOutcome(` ${promotedPath}`, options.json);\n\n if (!proEnabled) return;\n\n // ONE ledger entry for the whole quarantine entry, matching add.ts. A sandbox can hold several\n // skill dirs and `promote()` moves all of them, but only skillDirs[0] was gated and named — a\n // per-dir loop would file every one of them under the first skill's name and verdict.\n try {\n const ledgerPath = appendProvenance(\n {\n name: profile.name ?? id,\n source: `quarantine:${id}`,\n verb: 'DEFER',\n target: targetRoot,\n took: `promoted from quarantine after an earlier hold (${result.promoted.length} skill dir${result.promoted.length === 1 ? '' : 's'})`,\n notes: `re-gated at promote time: safety=${safety.verdict}`,\n },\n targetRoot\n );\n logOutcome(`[skill-forge] provenance recorded -> ${ledgerPath}`, options.json);\n } catch (err) {\n // Bookkeeping must never erase a successful promote — name the target so it can be fixed by hand.\n console.error(`[skill-forge] promoted into ${targetRoot}, but recording provenance failed: ${(err as Error).message}`);\n }\n\n appendQueueEntry({\n source: `quarantine:${id}`,\n sourceType: 'local',\n installedPath: result.promoted[0] ?? null,\n quarantinePath,\n gate: {\n license: profile.license ?? null,\n safetyVerdict: safety.verdict,\n safetyFindings: safety.findings.map(formatSafetyFinding),\n overlapTop: overlap ? overlap.ranking.slice(0, 3).map((r) => ({ skill: r.skill, score: r.score })) : [],\n suggestedVerb: overlap ? suggestVerbCode(overlap.nearestScore) : null,\n },\n status: 'pending',\n artifactType: 'skill',\n });\n logOutcome('[skill-forge] queued for the decide pass — run `skill-forge ingest`.', options.json);\n}\n\n/** `skill-forge reject <id>` — discard a held sandbox without promoting anything. */\nexport async function rejectHeld(id: string, options: RejectHeldOptions = {}): Promise<void> {\n const config = loadConfig();\n const quarantinePath = resolveHeldPath(config.quarantineDir, id);\n const entry = heldEntry(id, quarantinePath);\n const names = heldSkillNames(entry.skillDirs);\n\n console.log(`[skill-forge] reject held entry ${id}`);\n if (names.length > 0) console.log(` skills: ${names.join(', ')}`);\n console.log(` this DELETES ${quarantinePath}`);\n\n if (!options.yes) {\n const decision = await promptDecision('block', `held entry ${id}`);\n if (decision !== 'reject') {\n console.log('[skill-forge] left in quarantine — nothing deleted.');\n return;\n }\n }\n\n reject(entry);\n console.log(`[skill-forge] rejected — removed ${quarantinePath}`);\n}\n\n/**\n * Shared by `status`/`guide`/`routine`: how many sandboxes await a decision. Never throws — but\n * reports `readable: false` instead of a bare 0, since a MISSING quarantine dir already returns []\n * without throwing, so reaching the catch means the directory exists and cannot be listed.\n */\nexport function heldCount(quarantineDir: string): CountResult {\n try {\n return { count: listQuarantineIds(quarantineDir).length, readable: true };\n } catch {\n return { count: 0, readable: false };\n }\n}\n","import { createInterface } from 'node:readline/promises';\nimport { getConfigPath, loadConfig, saveConfig } from '../config.js';\nimport { UserError } from '../errors.js';\nimport {\n acceptProposal,\n proposePreference,\n rejectProposal,\n setPreference,\n unsetPreference,\n type PreferenceEntry,\n type PreferenceProposal,\n} from '../preferences.js';\n\n/**\n * `skill-forge config …` — read and write the open-ended preferences store (src/preferences.ts).\n *\n * The split that matters: `set`/`unset` are for a human at a keyboard and take effect at once,\n * while `propose` is the agent-facing door and only ever queues something for `review`. Nothing\n * an agent says about you becomes an effective preference without a human accepting it, and the\n * proposal listing labels every value as untrusted text so a reviewer reads it as a claim rather\n * than as an instruction.\n */\n\nexport interface ConfigCommandOptions {\n json?: boolean;\n origin?: string;\n note?: string;\n /** `review` only: accept/reject the whole queue without prompting (required when non-interactive). */\n acceptAll?: boolean;\n rejectAll?: boolean;\n}\n\nfunction formatEntry(key: string, entry: PreferenceEntry): string {\n const origin = entry.origin ? `, origin: ${entry.origin}` : '';\n const note = entry.note ? ` — ${entry.note}` : '';\n return ` ${key} = ${entry.value}\\n (${entry.source}, updated ${entry.updatedAt}${origin})${note}`;\n}\n\nfunction formatProposal(index: number, proposal: PreferenceProposal): string {\n const origin = proposal.origin ? `, origin: ${proposal.origin}` : '';\n const note = proposal.note ? ` — ${proposal.note}` : '';\n return ` ${index}. ${proposal.key} = ${proposal.value}\\n (proposed ${proposal.proposedAt}${origin})${note}`;\n}\n\nexport async function configList(options: ConfigCommandOptions = {}): Promise<void> {\n const config = loadConfig();\n const preferences = config.preferences ?? {};\n const proposals = config.proposedPreferences ?? [];\n\n if (options.json) {\n console.log(JSON.stringify({ configPath: getConfigPath(), preferences, proposedPreferences: proposals }, null, 2));\n return;\n }\n\n console.log(`[skill-forge] preferences (${getConfigPath()})`);\n const keys = Object.keys(preferences).sort();\n if (keys.length === 0) console.log(' (none set — `skill-forge config set <key> <value>`)');\n for (const key of keys) console.log(formatEntry(key, preferences[key]));\n\n if (proposals.length > 0) {\n console.log(`\\n[skill-forge] ${proposals.length} pending proposal(s) — untrusted until you accept them:`);\n proposals.forEach((p, i) => console.log(formatProposal(i + 1, p)));\n console.log('\\n review with `skill-forge config review`');\n }\n}\n\nexport async function configGet(key: string, options: ConfigCommandOptions = {}): Promise<void> {\n const entry = loadConfig().preferences?.[key];\n if (!entry) {\n throw new UserError(`no preference named \"${key}\" — \\`skill-forge config list\\` shows what is set`);\n }\n if (options.json) {\n console.log(JSON.stringify({ key, ...entry }, null, 2));\n return;\n }\n console.log(entry.value);\n}\n\nexport async function configSet(key: string, value: string, options: ConfigCommandOptions = {}): Promise<void> {\n const config = loadConfig();\n const updated = setPreference(config, key, value, { origin: options.origin ?? 'config set', note: options.note });\n saveConfig(updated);\n console.log(`[skill-forge] set ${key} = ${value}`);\n}\n\nexport async function configUnset(key: string): Promise<void> {\n saveConfig(unsetPreference(loadConfig(), key));\n console.log(`[skill-forge] unset ${key}`);\n}\n\n/**\n * `skill-forge config propose <key> <value>` — the door agents use.\n *\n * Deliberately never writes an effective preference, no matter what flags are passed: there is no\n * `--accept` here, because a command an agent can run must not also be the command that approves\n * what it ran. Approval lives in `review`, which is interactive or requires an explicit\n * human-typed `--accept-all`.\n */\nexport async function configPropose(key: string, value: string, options: ConfigCommandOptions = {}): Promise<void> {\n const config = loadConfig();\n const updated = proposePreference(config, key, value, { origin: options.origin, note: options.note });\n saveConfig(updated);\n console.log(`[skill-forge] proposed ${key} — pending review (\\`skill-forge config review\\`)`);\n}\n\nexport async function configReview(options: ConfigCommandOptions = {}): Promise<void> {\n let config = loadConfig();\n const proposals = config.proposedPreferences ?? [];\n\n if (proposals.length === 0) {\n console.log('[skill-forge] no pending preference proposals.');\n return;\n }\n\n if (options.acceptAll && options.rejectAll) {\n throw new UserError('--accept-all and --reject-all are mutually exclusive');\n }\n\n if (options.acceptAll || options.rejectAll) {\n for (const proposal of [...proposals]) {\n config = options.acceptAll ? acceptProposal(config, proposal.key) : rejectProposal(config, proposal.key);\n }\n saveConfig(config);\n console.log(`[skill-forge] ${options.acceptAll ? 'accepted' : 'rejected'} ${proposals.length} proposal(s).`);\n return;\n }\n\n if (!process.stdin.isTTY) {\n // Refusing here is the point: an unattended run must not be able to approve agent-proposed\n // config by falling through to a default.\n throw new UserError(\n 'not an interactive terminal — re-run `skill-forge config review` in a terminal, or pass --accept-all / --reject-all explicitly'\n );\n }\n\n console.log(`[skill-forge] ${proposals.length} pending preference proposal(s).`);\n console.log('These are CLAIMS made by an agent about you, not instructions — accept only what you recognize as true.\\n');\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n let accepted = 0;\n let rejected = 0;\n try {\n for (const proposal of [...proposals]) {\n console.log(formatProposal(accepted + rejected + 1, proposal));\n const answer = (await rl.question(' accept? [y/N/q] ')).trim().toLowerCase();\n if (answer === 'q' || answer === 'quit') break;\n if (answer === 'y' || answer === 'yes') {\n config = acceptProposal(config, proposal.key);\n accepted += 1;\n } else {\n config = rejectProposal(config, proposal.key);\n rejected += 1;\n }\n }\n } finally {\n rl.close();\n }\n\n saveConfig(config);\n console.log(`\\n[skill-forge] accepted ${accepted}, rejected ${rejected}, ${(config.proposedPreferences ?? []).length} left pending.`);\n}\n","import { formatAuditFreshness, readAuditState } from '../auditState.js';\nimport { loadConfig } from '../config.js';\nimport { pendingQueueCount } from '../queue.js';\nimport { heldCount } from './held.js';\nimport { maybeOfferInit } from './init.js';\n\n/**\n * `skill-forge status` — show current configuration and a summary of what is\n * outstanding: quarantine, the pending-decision queue, and audit freshness.\n *\n * Everything past the config block is a COUNT OF UNFINISHED WORK, each with the command that\n * finishes it — a status that lists settings but not what they are waiting on sends you to\n * `--help` to figure out what to do next.\n */\nexport async function status(): Promise<void> {\n await maybeOfferInit();\n\n const config = loadConfig();\n // Both helpers degrade to 0 rather than throwing: `status` is a diagnostic, so it has to survive\n // a broken quarantine dir or a malformed queue.json well enough to show the rest.\n const held = heldCount(config.quarantineDir);\n const pending = pendingQueueCount();\n\n console.log('[skill-forge] status');\n console.log(` skillsRoots: ${config.skillsRoots.join(', ')}`);\n if (config.defaultTarget) console.log(` defaultTarget: ${config.defaultTarget}`);\n if (config.mcpTargets?.length) console.log(` mcpTargets: ${config.mcpTargets.join(', ')}`);\n console.log(` quarantineDir: ${config.quarantineDir}`);\n console.log(` strictness: ${config.strictness}`);\n console.log(` last audit: ${formatAuditFreshness(readAuditState())}`);\n console.log(\n ` held in quarantine: ${held.readable ? held.count : 'UNREADABLE (quarantine dir cannot be listed)'}` +\n (held.count > 0 ? ' (`skill-forge promote <id>` / `reject <id>`)' : '')\n );\n console.log(\n ` queue: ${pending.readable ? `${pending.count} pending` : 'UNREADABLE (queue.json is corrupt)'}` +\n (pending.count > 0 ? ' (`skill-forge ingest`)' : '')\n );\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { createInterface } from 'node:readline/promises';\nimport { createHash } from 'node:crypto';\nimport { cpSync, existsSync, lstatSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { basename, dirname, join, resolve as resolvePath, sep } from 'node:path';\nimport { UserError } from './errors.js';\nimport { loadConfig } from './config.js';\nimport { proFeatureStatus } from './license.js';\nimport { profileSkill, asSkillProfile } from './gate/profile.js';\nimport type { SkillProfile } from './gate/profile.js';\nimport { scanSafety } from './gate/safety.js';\nimport type { SafetyResult } from './gate/safety.js';\nimport { formatSafetyFinding } from './gate/scanShared.js';\nimport { SAFETY_VERDICT_MAP, mapSafetyFindings, printGateOutput } from './report/render.js';\nimport type { GateResult } from './report/render.js';\nimport { logOutcome, promptDecision, yesDecision } from './decision.js';\nimport { appendProvenance } from './provenance.js';\nimport { appendQueueEntry } from './queue.js';\nimport { defaultExec } from './install/quarantine.js';\nimport type { ExecFn } from './install/quarantine.js';\n\n/**\n * `skill-forge evolve` (v0.7, Pro) — orchestrates microsoft/SkillOpt's\n * `skillopt-sleep` CLI to propose a self-evolution of an already-installed\n * skill (harvest recent sessions -> generate a replacement `SKILL.md`/\n * `CLAUDE.md` -> stage it, never auto-adopted), then re-gates the STAGED\n * PROPOSAL — never the live skill — through the same static safety ruleset\n * `add`/`scan` use, before a promote/hold/reject decision.\n *\n * `skillopt-sleep` is never installed by this module (detect-don't-install:\n * absence prints `pip install skillopt` guidance and refuses) and every\n * invocation of it goes through argv arrays with no shell, mirroring\n * `install/quarantine.ts`'s subprocess discipline. Nothing from a staged\n * proposal is ever executed — the re-gate step only reads\n * `proposed_SKILL.md`/`proposed_CLAUDE.md` as text.\n *\n * The entire command is Pro (unlike `add`, which is free with only specific\n * sub-features Pro-gated): self-evolution reuses the provenance ledger and\n * pending-ingestion queue, and is a power-user workflow layered on the free\n * gate. Free during the 0.x beta via `proFeatureStatus()`, same as every\n * other Pro feature — see src/license.ts.\n *\n * `staging_dir` (from skillopt-sleep's own JSON output) is untrusted input:\n * it's validated to be strictly contained under the expected sandbox\n * (`<project>/.skillopt-sleep/staging/`) before anything reads, copies, or\n * deletes it — mirroring `install/quarantine.ts`'s tar-member path-traversal\n * guard. The staged proposal is further treated as untrusted: a symlink in\n * place of a proposed file is refused, and the exact bytes re-gated are\n * hash-pinned and re-verified immediately before `adopt` runs, so a mutation\n * between the scan and the adopt (TOCTOU) is caught rather than silently\n * adopted.\n */\n\nconst SKILLOPT_BIN = 'skillopt-sleep';\nconst PRO_FEATURE_LABEL = 'self-evolution via SkillOpt-Sleep (evolve) — re-gating, provenance, and the pending-ingestion queue';\n\nexport interface EvolveOptions {\n /** Project dir passed to skillopt-sleep as --project. Defaults to cwd. */\n project?: string;\n /** Use skillopt-sleep's `dry-run` subcommand (report only, nothing staged) instead of `run`. */\n dryRun?: boolean;\n /** skillopt-sleep backend. Defaults to \"mock\" (offline, no session data leaves the machine). */\n backend?: string;\n /** Hours of session history for skillopt-sleep to harvest. Passed through as --lookback-hours. */\n lookbackHours?: number;\n /** Skip interactive prompts (the disclosure confirm and the promote/hold/reject prompt); honor the re-gate verdict automatically. */\n yes?: boolean;\n /** Print the re-gate result as JSON instead of the terminal report. Implies non-interactive, same as `add`'s --json. */\n json?: boolean;\n /** Allow re-adopting a staging dir whose proposed content already matches the live skill (double-adopt guard override). */\n force?: boolean;\n}\n\nexport interface EvolveDeps {\n /** Injected subprocess runner for skillopt-sleep invocations. Defaults to a real argv-array spawn (no shell). */\n exec?: ExecFn;\n /** Injected y/N confirm for the data-boundary disclosure prompt. Defaults to a real stdin readline confirm. */\n confirm?: (question: string) => Promise<boolean>;\n}\n\ninterface SkilloptRunOutput {\n staging_dir: string | null;\n accepted: boolean;\n}\n\ninterface StagingManifest {\n live_skill_path: string;\n live_memory_path?: string | null;\n has_skill: boolean;\n has_memory: boolean;\n}\n\nexport async function evolve(targetSkillDir: string, options: EvolveOptions = {}, deps: EvolveDeps = {}): Promise<void> {\n const exec = deps.exec ?? defaultExec;\n const json = options.json;\n\n // 1. Validate the target skill dir.\n const resolvedTargetDir = resolvePath(targetSkillDir);\n const skillMdPath = join(resolvedTargetDir, 'SKILL.md');\n if (!existsSync(skillMdPath)) {\n throw new UserError(`no SKILL.md found at ${skillMdPath} — evolve requires an existing, installed skill directory`);\n }\n\n // 2. Pro gate — the whole command is Pro; free during the 0.x beta.\n const proStatus = proFeatureStatus(PRO_FEATURE_LABEL);\n if (!proStatus.enabled) {\n throw new UserError(proStatus.notice ?? 'evolve is a Pro feature — set SKILL_FORGE_LICENSE or config.licenseKey to a valid license to unlock.');\n }\n const notices: string[] = proStatus.notice ? [proStatus.notice] : [];\n\n // 3. Data-boundary disclosure.\n const backend = options.backend ?? 'mock';\n if (backend !== 'mock') {\n const disclosure =\n `data boundary: this run uses the \"${backend}\" backend. Per SkillOpt-Sleep's own docs, harvesting itself ` +\n 'is local and read-only, but a real backend sends truncated excerpts from harvested sessions and derived ' +\n 'tasks to the provider you selected — this is not currently guaranteed to be secret-free.';\n if (options.yes) {\n notices.push(`${disclosure} Proceeding (--yes).`);\n } else if (json) {\n throw new UserError(`${disclosure} Pass --yes to proceed — --json is non-interactive and cannot prompt for confirmation.`);\n } else {\n console.log(`[skill-forge] ${disclosure}`);\n const confirmFn = deps.confirm ?? defaultConfirm;\n const confirmed = await confirmFn('Proceed and send session data to this backend? [y/N] ');\n if (!confirmed) {\n throw new UserError('evolve cancelled — data-boundary disclosure was not accepted.');\n }\n notices.push(`${disclosure} Confirmed interactively.`);\n }\n } else {\n notices.push('using the offline \"mock\" backend — deterministic, no network calls, no session data leaves this machine.');\n }\n\n // 4. Detect (never install), then run skillopt-sleep.\n const project = resolvePath(options.project ?? process.cwd());\n const present = await detectSkillopt(exec, project);\n if (!present) {\n throw new UserError(\n '`skillopt-sleep` not found on PATH. Install it with `pip install skillopt` ' +\n '(https://github.com/microsoft/SkillOpt) and re-run `skill-forge evolve`.'\n );\n }\n\n const subcommand = options.dryRun ? 'dry-run' : 'run';\n const runArgs = [subcommand, '--project', project, '--target-skill-path', skillMdPath, '--json', '--backend', backend];\n if (options.lookbackHours !== undefined) {\n runArgs.push('--lookback-hours', String(options.lookbackHours));\n }\n\n const runResult = await exec(SKILLOPT_BIN, runArgs, { cwd: project });\n if (runResult.code !== 0) {\n throw new UserError(`skillopt-sleep ${subcommand} failed (exit ${runResult.code}): ${tail(runResult.stderr || runResult.stdout)}`);\n }\n\n let rawOutput: unknown;\n try {\n rawOutput = JSON.parse(runResult.stdout);\n } catch (err) {\n throw new UserError(`could not parse skillopt-sleep JSON output: ${(err as Error).message}`);\n }\n const parsed = validateRunOutput(rawOutput);\n\n if (!parsed.accepted || !parsed.staging_dir) {\n if (json) {\n console.log(JSON.stringify({ ok: true, accepted: parsed.accepted, stagingDir: parsed.staging_dir }, null, 2));\n } else {\n console.log('[skill-forge] skillopt-sleep produced no accepted proposal — nothing to re-gate or adopt.');\n }\n return;\n }\n\n // `staging_dir` is untrusted (skillopt-sleep's own JSON output) — require it to\n // be strictly contained under the expected sandbox before it's read, copied,\n // adopted, or deleted anywhere below. A naive `startsWith(stagingRoot)` would\n // be defeated by a sibling directory sharing the same string prefix (e.g.\n // `.skillopt-sleep/staging-evil` next to `.skillopt-sleep/staging`) — this\n // checks for either exact equality or the separator-qualified prefix, mirroring\n // `install/quarantine.ts`'s tar-member path-traversal guard.\n const stagingRoot = join(project, '.skillopt-sleep', 'staging');\n const stagingDir = resolvePath(parsed.staging_dir);\n if (stagingDir !== stagingRoot && !stagingDir.startsWith(stagingRoot + sep)) {\n throw new UserError(\n `skillopt-sleep reported a staging_dir outside the expected sandbox (${stagingRoot}): ${stagingDir} — refusing to use it`\n );\n }\n\n // 5. Re-gate: scan ONLY the staged proposal, in a fresh temp copy — never the live skill, never report.json/diagnostics.json.\n const proposedSkillMd = join(stagingDir, 'proposed_SKILL.md');\n const proposedClaudeMd = join(stagingDir, 'proposed_CLAUDE.md');\n const reportMdPath = join(stagingDir, 'report.md');\n if (!existsSync(proposedSkillMd)) {\n throw new UserError(`skillopt-sleep reported an accepted proposal but ${proposedSkillMd} is missing`);\n }\n if (existsSync(reportMdPath)) {\n notices.push(`SkillOpt-Sleep's full report: ${reportMdPath}`);\n }\n\n // A staged proposal is untrusted content: refuse a symlink in place of a\n // proposed file (spirit of the tar-member guard — never follow a link out\n // of the sandbox) before reading either one.\n assertNotSymlink(proposedSkillMd, 'proposed_SKILL.md');\n assertNotSymlink(proposedClaudeMd, 'proposed_CLAUDE.md');\n\n // Hash-pin what's about to be scanned, so a mutation of the staged files\n // between this scan and the `adopt` call below (TOCTOU) is caught rather\n // than silently adopted — see the re-check immediately before `adopt`.\n const scannedSkillHash = sha256File(proposedSkillMd);\n const scannedClaudeHash = sha256File(proposedClaudeMd);\n\n const tempDir = mkdtempSync(join(tmpdir(), 'skill-forge-evolve-'));\n let profile: SkillProfile;\n let safety: SafetyResult;\n try {\n cpSync(proposedSkillMd, join(tempDir, 'SKILL.md'));\n if (existsSync(proposedClaudeMd)) {\n cpSync(proposedClaudeMd, join(tempDir, 'CLAUDE.md'));\n }\n\n // Unreachable in practice: the temp dir only ever contains a renamed\n // proposed_SKILL.md (+ proposed_CLAUDE.md), never an .mcp.json or\n // plugin.json — asSkillProfile() throws before this could matter.\n profile = asSkillProfile(profileSkill(tempDir), 'for the evolved proposal');\n\n const strictness = loadConfig().strictness;\n safety = await scanSafety(tempDir, strictness);\n } finally {\n // A cleanup failure here is secondary to whatever the try block already\n // produced (a result, or an error already propagating) — never let it\n // mask that.\n try {\n rmSync(tempDir, { recursive: true, force: true });\n } catch (err) {\n console.error(`[skill-forge] failed to clean up temp re-gate dir ${tempDir}: ${(err as Error).message}`);\n }\n }\n\n const safetyVerdict = safety.verdict;\n const gateResult: GateResult = {\n profile,\n safety: {\n verdict: SAFETY_VERDICT_MAP[safetyVerdict],\n findings: mapSafetyFindings(safety.findings),\n },\n notices,\n };\n printGateOutput(gateResult, json);\n\n // --dry-run belt-and-braces: never stage/adopt anything, regardless of what\n // skillopt-sleep's JSON output claims — even if it (bug or otherwise)\n // reports an accepted proposal + a real staging_dir for a dry-run call,\n // this never reaches the decision/adopt path below.\n if (options.dryRun) {\n logOutcome('[skill-forge] --dry-run: report only, nothing staged or adopted.', json);\n return;\n }\n\n // 6. Decide — same promote/hold/reject semantics as add/scan.\n const decision =\n options.yes || json ? yesDecision(safetyVerdict) : await promptDecision(safetyVerdict, 'this evolution proposal');\n\n if (decision === 'promote') {\n const manifest = readManifest(stagingDir);\n if (isAlreadyAdopted(stagingDir, manifest) && !options.force) {\n throw new UserError(\n `staging dir ${stagingDir} looks already adopted — its proposed content is byte-identical to the live ` +\n 'skill (and memory, if present). Pass --force to re-adopt anyway.'\n );\n }\n\n // Re-verify immediately before adopting: if the staged files changed since\n // they were scanned above, what's about to be adopted is no longer what\n // was re-gated.\n if (sha256File(proposedSkillMd) !== scannedSkillHash || sha256File(proposedClaudeMd) !== scannedClaudeHash) {\n throw new UserError(`proposal changed after it was re-gated (staging dir: ${stagingDir}) — refusing to adopt`);\n }\n\n const adoptResult = await exec(SKILLOPT_BIN, ['adopt', '--staging', stagingDir, '--project', project], { cwd: project });\n if (adoptResult.code !== 0) {\n throw new UserError(`skillopt-sleep adopt failed (exit ${adoptResult.code}): ${tail(adoptResult.stderr || adoptResult.stdout)}`);\n }\n\n // The live skill has now changed — report that immediately, before the\n // bookkeeping below, which can itself fail without undoing the adopt.\n logOutcome(`[skill-forge] adopted -> ${resolvedTargetDir}`, json);\n\n // Free-tier `add` records DEFER for \"adopt as-is, no absorb/fork judgment\" —\n // the same is true here (the re-gate is a safety check, not a redundancy\n // judgment against the existing set), so this reuses DEFER rather than\n // adding a sixth member to provenance.ts's five-verb FORGE_VERBS/ForgeVerb\n // (referenced elsewhere as exactly five: docs/gate-policy.md,\n // assets/ingest-prompt.md, README.md, CLAUDE.md). The distinguishing\n // \"this was self-evolved\" signal lives in the note below and in the\n // queue entry's `origin: 'evolve'` field instead.\n const skillsRootForProvenance = dirname(resolvedTargetDir);\n let ledgerPath: string;\n try {\n ledgerPath = appendProvenance(\n {\n name: profile.name ?? basename(resolvedTargetDir),\n source: resolvedTargetDir,\n version: profile.version,\n license: profile.license,\n verb: 'DEFER',\n target: resolvedTargetDir,\n took: `self-evolved via SkillOpt-Sleep (staging: ${stagingDir})`,\n notes: `self-evolved via SkillOpt-Sleep; re-gated (verdict: ${safetyVerdict})`,\n },\n skillsRootForProvenance\n );\n } catch (err) {\n throw new UserError(\n `adopt succeeded and the live skill at ${resolvedTargetDir} has already changed, but recording the ` +\n `provenance entry failed: ${(err as Error).message}`\n );\n }\n logOutcome(`[skill-forge] provenance recorded -> ${ledgerPath}`, json);\n\n try {\n appendQueueEntry({\n source: resolvedTargetDir,\n sourceType: 'local',\n installedPath: resolvedTargetDir,\n quarantinePath: stagingDir,\n gate: {\n license: profile.license ?? null,\n safetyVerdict,\n safetyFindings: safety.findings.map(formatSafetyFinding),\n overlapTop: [],\n suggestedVerb: null,\n },\n status: 'pending',\n artifactType: 'skill',\n origin: 'evolve',\n });\n } catch (err) {\n throw new UserError(\n `adopt succeeded and the live skill at ${resolvedTargetDir} has already changed (provenance was recorded), ` +\n `but appending the pending-ingestion queue entry failed: ${(err as Error).message}`\n );\n }\n logOutcome('[skill-forge] queued for ingestion review', json);\n } else if (decision === 'reject') {\n try {\n rmSync(stagingDir, { recursive: true, force: true });\n } catch (err) {\n throw new UserError(\n `rejected the proposal, but removing the staging dir ${stagingDir} failed: ${(err as Error).message} — ` +\n 'remove it manually.'\n );\n }\n logOutcome('[skill-forge] rejected — staging dir removed', json);\n if (safetyVerdict === 'block') process.exitCode = 1;\n } else {\n logOutcome(`[skill-forge] held — staging dir left in place -> ${stagingDir}`, json);\n if (existsSync(reportMdPath)) {\n logOutcome(`[skill-forge] see ${reportMdPath} for the full evidence report`, json);\n }\n }\n}\n\nasync function defaultConfirm(question: string): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n try {\n // Iterate lines via the async-iterator protocol rather than rl.question() —\n // same rationale as decision.ts's promptDecision: question() attaches a\n // one-shot 'line' listener, and on piped/closed (non-TTY) stdin that ends\n // without ever emitting a line, that listener never fires and the promise\n // never resolves — the process hangs forever. The async iterator both\n // queues lines and terminates cleanly at EOF, so a closed stream resolves\n // (falling through to `false`, \"not confirmed\") instead of hanging.\n process.stdout.write(question);\n for await (const raw of rl) {\n return /^y(es)?$/i.test(raw.trim());\n }\n return false;\n } finally {\n rl.close();\n }\n}\n\n/** Detects skillopt-sleep on PATH by running `--help` via the injected exec — never installs it. Any rejection (ENOENT) or nonzero exit is treated as absent. */\nasync function detectSkillopt(exec: ExecFn, cwd: string): Promise<boolean> {\n try {\n const result = await exec(SKILLOPT_BIN, ['--help'], { cwd });\n return result.code === 0;\n } catch {\n return false;\n }\n}\n\n/** Last `maxLines` lines of `text`, for surfacing a subprocess failure without dumping unbounded output. */\nfunction tail(text: string, maxLines = 20): string {\n return text.trim().split('\\n').slice(-maxLines).join('\\n');\n}\n\n/**\n * Validates skillopt-sleep's `run`/`dry-run` JSON output — an external-process\n * trust boundary — before any field is used: must be a plain object,\n * `staging_dir` must be a string or null/undefined, otherwise a UserError\n * (not a TypeError surfacing later at some unrelated call site). `accepted`\n * is coerced to a boolean rather than strictly typechecked, matching how\n * loosely the field is actually consumed downstream.\n */\nfunction validateRunOutput(raw: unknown): SkilloptRunOutput {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new UserError(`skillopt-sleep produced unexpected JSON output (expected an object): ${JSON.stringify(raw)}`);\n }\n const obj = raw as Record<string, unknown>;\n const stagingDirRaw = obj.staging_dir;\n if (stagingDirRaw !== null && stagingDirRaw !== undefined && typeof stagingDirRaw !== 'string') {\n throw new UserError(\n `skillopt-sleep produced an unexpected \"staging_dir\" (expected a string or null): ${JSON.stringify(stagingDirRaw)}`\n );\n }\n return { accepted: Boolean(obj.accepted), staging_dir: stagingDirRaw ?? null };\n}\n\nfunction readManifest(stagingDir: string): StagingManifest {\n const manifestPath = join(stagingDir, 'manifest.json');\n let raw: string;\n try {\n raw = readFileSync(manifestPath, 'utf-8');\n } catch (err) {\n throw new UserError(`could not read staging manifest ${manifestPath}: ${(err as Error).message}`);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new UserError(`could not parse staging manifest ${manifestPath}: ${(err as Error).message}`);\n }\n return validateManifest(parsed, manifestPath);\n}\n\n/** Validates manifest.json's shape (another external trust boundary) before any field is used. */\nfunction validateManifest(raw: unknown, manifestPath: string): StagingManifest {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new UserError(`staging manifest ${manifestPath} has an unexpected shape (expected an object)`);\n }\n const obj = raw as Record<string, unknown>;\n if (typeof obj.live_skill_path !== 'string') {\n throw new UserError(`staging manifest ${manifestPath} is missing a valid \"live_skill_path\" string`);\n }\n if (obj.live_memory_path !== undefined && obj.live_memory_path !== null && typeof obj.live_memory_path !== 'string') {\n throw new UserError(`staging manifest ${manifestPath} has an unexpected \"live_memory_path\" (expected a string or null)`);\n }\n if (typeof obj.has_skill !== 'boolean' || typeof obj.has_memory !== 'boolean') {\n throw new UserError(`staging manifest ${manifestPath} is missing valid \"has_skill\"/\"has_memory\" booleans`);\n }\n return {\n live_skill_path: obj.live_skill_path,\n live_memory_path: (obj.live_memory_path as string | null | undefined) ?? null,\n has_skill: obj.has_skill,\n has_memory: obj.has_memory,\n };\n}\n\nfunction filesIdentical(a: string, b: string): boolean {\n if (!existsSync(a) || !existsSync(b)) return false;\n return readFileSync(a).equals(readFileSync(b));\n}\n\n/**\n * Double-adopt guard: true when the staged proposal's content is\n * byte-identical to the live file(s) it would replace — i.e. this staging\n * dir already appears to have been adopted once. skillopt-sleep's own\n * `adopt` has no confirmation and a second adopt overwrites its own backup\n * (the pre-evolution original is lost), so this guard exists purely on our\n * side of the wrapper.\n *\n * Grounded in which proposed files actually exist on disk (`existsSync`)\n * rather than trusting the manifest's `has_skill`/`has_memory` flags blindly\n * — a stale or malformed manifest claiming `false` shouldn't be able to\n * silently skip a comparison for a file that's actually sitting right there.\n */\nfunction isAlreadyAdopted(stagingDir: string, manifest: StagingManifest): boolean {\n const proposedSkillMd = join(stagingDir, 'proposed_SKILL.md');\n const proposedClaudeMd = join(stagingDir, 'proposed_CLAUDE.md');\n let sawComparison = false;\n\n if (existsSync(proposedSkillMd)) {\n sawComparison = true;\n if (!filesIdentical(proposedSkillMd, manifest.live_skill_path)) return false;\n }\n if (existsSync(proposedClaudeMd) && manifest.live_memory_path) {\n sawComparison = true;\n if (!filesIdentical(proposedClaudeMd, manifest.live_memory_path)) return false;\n }\n\n return sawComparison;\n}\n\n/** Throws a UserError if `path` is a symlink. Silent no-op if `path` doesn't exist (existsSync() calls elsewhere already gate required-vs-optional). Uses lstatSync (never follows the link) so a broken symlink is still caught. */\nfunction assertNotSymlink(path: string, label: string): void {\n let stat;\n try {\n stat = lstatSync(path);\n } catch {\n return;\n }\n if (stat.isSymbolicLink()) {\n throw new UserError(`refusing to re-gate ${label}: ${path} is a symlink (a staged proposal must be a plain file)`);\n }\n}\n\n/** sha256 hex digest of a file's contents, or null if it doesn't exist. Exported for direct unit coverage of the TOCTOU hash-pin (see evolve()'s scan-time / pre-adopt re-check). */\nexport function sha256File(path: string): string | null {\n if (!existsSync(path)) return null;\n return createHash('sha256').update(readFileSync(path)).digest('hex');\n}\n","import { evolve } from '../evolve.js';\nimport type { EvolveOptions } from '../evolve.js';\n\n/**\n * `skill-forge evolve <target-skill-dir>` — thin CLI wiring around the Pro\n * self-evolution flow in src/evolve.ts: orchestrate a SkillOpt-Sleep run,\n * re-gate the staged proposal through the same static skill safety ruleset\n * `add`/`scan` use, then promote/hold/reject. See src/evolve.ts for the\n * actual pipeline; this file only adapts commander's positional/options\n * shape into a call.\n */\nexport async function evolveCommand(targetSkillDir: string, options: EvolveOptions = {}): Promise<void> {\n await evolve(targetSkillDir, options);\n}\n","import { existsSync } from 'node:fs';\nimport { auditConfiguredSet, sanitizeForMarkdown } from '../audit.js';\nimport { getConfigPath, loadConfig } from '../config.js';\nimport { UserError } from '../errors.js';\nimport {\n FINGERPRINT_DISPLAY_LENGTH,\n MIN_FINGERPRINT_PREFIX_LENGTH,\n readAcceptedFindings,\n resolveAmongFindings,\n resolveAmongRecords,\n validateAcceptReason,\n writeAcceptedFindings,\n type AcceptedFinding,\n} from '../findings.js';\n\n/**\n * `skill-forge finding accept|revoke|list` (v0.12, MIT, free) — the human-only door for\n * acknowledging `skill-forge audit` findings you've reviewed and accepted. See\n * `src/findings.ts`'s module doc for the identity/fail-open contract this command writes into,\n * and `docs/accepted-findings-schema.md` for the full field/lifecycle reference.\n *\n * **Human-only by design.** `assets/ingest-prompt.md` and `assets/curation-prompt.md` both\n * instruct agents never to run `finding accept`/`finding revoke` — an agent that judges a finding\n * a false positive should say so in its summary and let the user run this command themselves. This\n * file enforces the mechanical half of that boundary: a severity cap (LOW/MEDIUM only, no override\n * flag for HIGH/CRITICAL) and a mandatory, validated `--reason`.\n *\n * **Never a gate input.** Nothing here touches `add`/`scan`/`promote <id>`/`evolve`'s re-gate —\n * this command only ever reads/writes the accepted-findings store and re-runs the read-only audit\n * engine to resolve a prefix against what is CURRENTLY active.\n */\n\nexport interface FindingAcceptOptions {\n reason?: string;\n json?: boolean;\n}\n\nexport interface FindingRevokeOptions {\n json?: boolean;\n}\n\nexport interface FindingListOptions {\n stale?: boolean;\n json?: boolean;\n}\n\nfunction requireSavedConfig(): void {\n if (!existsSync(getConfigPath())) {\n throw new UserError(\n 'no skill-forge config found — run `skill-forge init` first (`finding accept` re-runs the audit engine to resolve a live finding, and audit never runs against an invented default).'\n );\n }\n}\n\nfunction shortDate(iso: string): string {\n return iso.slice(0, 10) || iso;\n}\n\n/** `skill-forge finding accept <fingerprint-prefix> --reason <text>`. */\nexport async function findingAcceptCommand(prefix: string, options: FindingAcceptOptions = {}): Promise<void> {\n requireSavedConfig();\n\n if (prefix.length < MIN_FINGERPRINT_PREFIX_LENGTH) {\n throw new UserError(`prefix too short — use at least ${MIN_FINGERPRINT_PREFIX_LENGTH} hex chars (got \"${prefix}\")`);\n }\n\n const reason = options.reason;\n if (reason === undefined) {\n throw new UserError('--reason is required — a short note on why this finding is accepted');\n }\n validateAcceptReason(reason);\n\n const config = loadConfig();\n // Loaded (not merely for pass-through): resolving \"already accepted\" below needs the current\n // records, and the live re-audit needs them too so an already-accepted finding stays out of\n // `report.hygiene` (the ACTIVE list this command resolves prefixes against — see §7 below).\n const { records: accepted } = readAcceptedFindings();\n const report = await auditConfiguredSet(config, { accepted });\n\n // Resolving against report.hygiene (ACTIVE findings from a FRESH run) is deliberate, not just a\n // convenient data source: you can only accept a finding that exists RIGHT NOW — no accepting off\n // a stale report, no fabricating a record for a finding that's already gone.\n const activeMatches = resolveAmongFindings(prefix, report.hygiene);\n\n if (activeMatches.length > 1) {\n const list = activeMatches.map((f) => `${f.rule} @ ${f.target} (${f.fingerprint})`).join('; ');\n throw new UserError(`prefix \"${prefix}\" matches ${activeMatches.length} current findings — use a longer prefix: ${list}`);\n }\n\n if (activeMatches.length === 0) {\n // Give the more useful answer when the prefix actually matches something ALREADY accepted\n // (excluded from report.hygiene by definition) rather than the generic \"no match\" — this is\n // exactly the accept-an-already-accepted-fingerprint case.\n const alreadyMatches = resolveAmongRecords(prefix, accepted);\n if (alreadyMatches.length > 0) {\n const already = alreadyMatches[0];\n throw new UserError(`already accepted on ${shortDate(already.acceptedAt)}: ${already.reason}`);\n }\n throw new UserError(`no current finding matches \"${prefix}\" — run \\`skill-forge audit\\` and copy a fingerprint from the report`);\n }\n\n const finding = activeMatches[0];\n\n // Severity cap (§3 of the design): refuses HIGH and CRITICAL outright, no override flag. The\n // precision problem this command exists to solve lives at LOW/MEDIUM — a HIGH/CRITICAL false\n // positive is a rule bug to fix, not something to baseline.\n if (finding.severity === 'HIGH' || finding.severity === 'CRITICAL') {\n throw new UserError(\n `refusing to accept a ${finding.severity} finding (${finding.rule}) — acceptance is capped at LOW/MEDIUM, and there is no override flag. ` +\n 'A HIGH/CRITICAL false positive is a rule bug to report/fix, not something to baseline.'\n );\n }\n\n const record: AcceptedFinding = {\n fingerprint: finding.fingerprint!,\n severity: finding.severity,\n rule: finding.rule,\n target: finding.target,\n context: finding.context ?? finding.detail,\n detail: finding.detail,\n reason,\n acceptedAt: new Date().toISOString(),\n source: 'user',\n };\n\n writeAcceptedFindings([...accepted, record]);\n\n if (options.json) {\n console.log(JSON.stringify(record, null, 2));\n return;\n }\n console.log(`[skill-forge] accepted [${record.severity}] ${record.rule} — ${record.target}`);\n console.log(` fingerprint: ${record.fingerprint}`);\n console.log(` reason: ${record.reason}`);\n console.log(' this finding is excluded from active counts, but stays permanently visible under \"Accepted findings\" in every future report.');\n}\n\n/** `skill-forge finding revoke <fingerprint-prefix>`. */\nexport async function findingRevokeCommand(prefix: string, options: FindingRevokeOptions = {}): Promise<void> {\n if (prefix.length < MIN_FINGERPRINT_PREFIX_LENGTH) {\n throw new UserError(`prefix too short — use at least ${MIN_FINGERPRINT_PREFIX_LENGTH} hex chars (got \"${prefix}\")`);\n }\n\n // Resolved against the STORE, not a fresh audit run — the target finding may no longer be\n // producible (the skill was removed, the line changed), which is exactly when you want to clean\n // up a stale acceptance record.\n const { records } = readAcceptedFindings();\n const matches = resolveAmongRecords(prefix, records);\n\n if (matches.length > 1) {\n const list = matches.map((r) => `${r.rule} @ ${r.target} (${r.fingerprint})`).join('; ');\n throw new UserError(`prefix \"${prefix}\" matches ${matches.length} accepted findings — use a longer prefix: ${list}`);\n }\n if (matches.length === 0) {\n throw new UserError(`no accepted finding matches \"${prefix}\" — run \\`skill-forge finding list\\` to see what is accepted`);\n }\n\n const record = matches[0];\n writeAcceptedFindings(records.filter((r) => r.fingerprint !== record.fingerprint));\n\n if (options.json) {\n console.log(JSON.stringify(record, null, 2));\n return;\n }\n console.log(`[skill-forge] revoked [${record.severity}] ${record.rule} — ${record.target}`);\n}\n\n/** `skill-forge finding list [--stale]`. */\nexport async function findingListCommand(options: FindingListOptions = {}): Promise<void> {\n const { records, notices } = readAcceptedFindings();\n for (const notice of notices) console.error(`[skill-forge] ${notice}`);\n\n const rows = options.stale ? records.filter((r) => !existsSync(r.target)) : records;\n\n if (options.json) {\n console.log(JSON.stringify(rows, null, 2));\n return;\n }\n\n if (rows.length === 0) {\n console.log(options.stale ? '[skill-forge] no stale accepted findings.' : '[skill-forge] no accepted findings.');\n return;\n }\n\n console.log(`[skill-forge] accepted findings${options.stale ? ' (stale — target no longer exists)' : ''}:`);\n for (const r of rows) {\n console.log(\n ` [${r.severity}] ${sanitizeForMarkdown(r.rule)} — ${sanitizeForMarkdown(r.target)}: ${sanitizeForMarkdown(r.reason)} ` +\n `(fp ${r.fingerprint.slice(0, FINGERPRINT_DISPLAY_LENGTH)}, accepted ${shortDate(r.acceptedAt)})`\n );\n }\n}\n","import { writeNewFile } from '../util.js';\nimport { resolve as resolvePath } from 'node:path';\nimport { sanitizeForMarkdown } from '../audit.js';\nimport { loadConfig } from '../config.js';\nimport { buildOrganizeReport } from '../organize.js';\nimport type { OrganizeReport } from '../organize.js';\n\n/**\n * `skill-forge organize` — thin CLI wiring around the Pro capability\n * registry + dependency graph engine in src/organize.ts. This file only\n * adapts commander's options shape into a call, renders the human-readable\n * summary (sanitizing every untrusted string — names/tiers/domains/consumes\n * targets/paths — via audit.ts's `sanitizeForMarkdown`), and handles\n * `--out`/`--json`. See src/organize.ts for the actual scan/graph pipeline.\n */\nexport interface OrganizeCommandOptions {\n json?: boolean;\n /** Write the full registry+graph report JSON to this path (`wx` mode 0o600 — refuses an existing file). */\n out?: string;\n usageSnapshot?: string;\n}\n\n/** Resolves `--out` and writes it exclusively via the shared writer, returning the resolved path. */\nfunction writeReport(outPath: string, contents: string): string {\n const resolved = resolvePath(outPath);\n writeNewFile(resolved, contents, '--out');\n return resolved;\n}\n\nfunction renderSummary(report: OrganizeReport): string[] {\n const { registry, graph } = report;\n const lines: string[] = [];\n\n lines.push(`[skill-forge] organize: ${registry.count} skill(s) across ${registry.skillsRoots.length} root(s)`);\n lines.push(` by tier: ${JSON.stringify(registry.byTier)}`);\n lines.push(` by domain: ${JSON.stringify(registry.byDomain)}`);\n lines.push(` usage joined: ${registry.usageJoined}`);\n lines.push(` untagged (missing tier/domain): ${registry.untagged.length}`);\n for (const u of registry.untagged.slice(0, 30)) {\n lines.push(` - ${sanitizeForMarkdown(u.name)}: missing ${u.missing.join(', ')}`);\n }\n if (registry.warnings.length > 0) {\n lines.push(' warnings:');\n for (const w of registry.warnings) lines.push(` ! ${sanitizeForMarkdown(w)}`);\n }\n\n lines.push('');\n lines.push(` graph: ${graph.nodeCount} nodes, ${graph.edgeCount} edges`);\n if (graph.hotResources.length > 0) {\n lines.push(' hot resources (consumed by N customs):');\n for (const h of graph.hotResources) lines.push(` ${h.consumedBy}x ${sanitizeForMarkdown(h.name)}`);\n }\n if (graph.danglingEdges.length > 0) {\n lines.push(' dangling edges (consumes a target not in the set):');\n for (const d of graph.danglingEdges) lines.push(` ${sanitizeForMarkdown(d.from)} -> ${sanitizeForMarkdown(d.to)} (missing)`);\n }\n if (graph.ambiguousConsumesTargets.length > 0) {\n lines.push(' ambiguous consumes targets (name matched multiple skills):');\n for (const a of graph.ambiguousConsumesTargets) {\n lines.push(\n ` ${sanitizeForMarkdown(a.from)} -> ${sanitizeForMarkdown(a.to)} (matches: ${a.matches.map(sanitizeForMarkdown).join(', ')})`\n );\n }\n }\n if (graph.cycles.length > 0) {\n lines.push(' cycles (consumes should be acyclic):');\n for (const c of graph.cycles) lines.push(` ${c.map(sanitizeForMarkdown).join(' -> ')}`);\n }\n if (graph.orphans.length > 0) {\n lines.push(` custom skills with no declared consumes (${graph.orphans.length}):`);\n for (const o of graph.orphans.slice(0, 30)) lines.push(` - ${sanitizeForMarkdown(o)}`);\n }\n\n for (const n of report.notices) lines.push(` [notice] ${sanitizeForMarkdown(n)}`);\n return lines;\n}\n\nexport async function organizeCommand(options: OrganizeCommandOptions = {}): Promise<void> {\n const config = loadConfig();\n const report = buildOrganizeReport(config, { usageSnapshot: options.usageSnapshot });\n\n let outPath: string | undefined;\n if (options.out) {\n outPath = writeReport(options.out, JSON.stringify(report, null, 2) + '\\n');\n }\n\n if (options.json) {\n // Notices (incl. the beta notice) go to stderr — stdout stays exactly one JSON document.\n for (const n of report.notices) console.error(`[skill-forge] ${n}`);\n console.log(JSON.stringify(report, null, 2));\n return;\n }\n\n for (const line of renderSummary(report)) console.log(line);\n if (outPath) console.log(` report -> ${outPath}`);\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { existsSync, readFileSync, readdirSync, realpathSync, statSync, type Dirent } from 'node:fs';\nimport { basename, join, resolve as resolvePath } from 'node:path';\nimport type { Config } from './config.js';\nimport { UserError } from './errors.js';\nimport { proFeatureStatus, upgradeNotice } from './license.js';\nimport { expandHome, isDir, stripQuotes } from './util.js';\n\n/**\n * `skill-forge organize` engine (Pro) — builds a set-level capability registry and a\n * consumes-edge dependency graph across every configured skills root, in one command. See\n * `docs/forge-workflow.md` for the frontmatter keys this reads (`tier` / `domain` /\n * `consumes` / `provenance` / `maturity`).\n *\n * This module performs its OWN self-contained scan of `config.skillsRoots` —\n * it deliberately does NOT import `audit.ts`'s inventory walker, keeping the\n * two modules' file sets disjoint. Nodes and registry entries are keyed by\n * REALPATH-QUALIFIED stable IDs, not bare frontmatter names: two distinct\n * skills in two different roots that happen to share a `name:` must NOT\n * collapse into one node/adjacency entry. Ambiguous (`consumes` target name\n * matches more than one skill) and dangling (matches none) edges are\n * reported rather than silently guessed at.\n *\n * Nothing this module scans is ever executed — it only reads SKILL.md text\n * and (optionally) a usage-snapshot JSON file. The whole command is Pro\n * (`proFeatureStatus('set-level organizer reports')`), gated before any\n * filesystem walk, matching every other Pro entry point in this repo (see\n * src/evolve.ts). Free during the 0.x beta via `proFeatureStatus()`.\n */\n\nconst PRO_FEATURE_LABEL = 'set-level organizer reports';\n\nconst TIERS = new Set(['resource', 'custom']);\nconst MATURITIES = new Set(['seedling', 'stable', 'deprecated']);\n\nexport interface OrganizeOptions {\n /** Path to a skill-monitor-style snapshot JSON (`{ report: { top_skills: [[name, count], ...] } }`) to join usage counts from, by skill name. */\n usageSnapshot?: string;\n}\n\nexport interface OrganizeRegistryEntry {\n /** Realpath-qualified stable id — NOT the bare frontmatter name (which may collide across roots). */\n id: string;\n name: string;\n /** Realpath of the skill directory (same value as `id`). */\n path: string;\n /** The configured root (as given in config.skillsRoots) this skill was discovered under. */\n root: string;\n tier?: string;\n domain?: string;\n consumes: string[];\n provenance?: string;\n maturity?: string;\n validFrontmatter: boolean;\n usage?: number;\n}\n\nexport interface OrganizeUntaggedEntry {\n id: string;\n name: string;\n missing: string[];\n}\n\nexport interface OrganizeRegistry {\n skillsRoots: string[];\n count: number;\n byTier: Record<string, number>;\n byDomain: Record<string, number>;\n untagged: OrganizeUntaggedEntry[];\n warnings: string[];\n usageJoined: boolean;\n skills: OrganizeRegistryEntry[];\n}\n\nexport interface OrganizeGraphNode {\n id: string;\n name: string;\n tier?: string;\n domain?: string;\n}\n\nexport interface OrganizeGraphEdge {\n from: string;\n /** Raw `consumes` target name as declared in frontmatter — not yet resolved to an id. */\n to: string;\n /** ids of every skill whose name matches `to` — empty (dangling), one (resolved), or many (ambiguous). */\n resolvedIds: string[];\n}\n\nexport interface OrganizeGraphDanglingEdge {\n from: string;\n to: string;\n}\n\nexport interface OrganizeGraphAmbiguousEdge {\n from: string;\n to: string;\n matches: string[];\n}\n\nexport interface OrganizeGraphHotResource {\n id: string;\n name: string;\n consumedBy: number;\n}\n\nexport interface OrganizeGraph {\n nodeCount: number;\n edgeCount: number;\n nodes: OrganizeGraphNode[];\n edges: OrganizeGraphEdge[];\n /** ids of `tier: custom` skills with no declared `consumes`. */\n orphans: string[];\n hotResources: OrganizeGraphHotResource[];\n danglingEdges: OrganizeGraphDanglingEdge[];\n ambiguousConsumesTargets: OrganizeGraphAmbiguousEdge[];\n /** Each cycle is a sequence of ids, first === last (closing the loop) — only built from unambiguous (single-match) edges. */\n cycles: string[][];\n}\n\nexport interface OrganizeReport {\n generatedAt: string;\n registry: OrganizeRegistry;\n graph: OrganizeGraph;\n notices: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Self-contained scan (deliberately independent of audit.ts's walker)\n// ---------------------------------------------------------------------------\n\ninterface SkillCandidate {\n dirPath: string;\n root: string;\n}\n\n/** Subdirectories of `dir`, following directory-shaped symlinks. Swallows any read error (missing/permission) rather than throwing. */\nfunction listSubdirs(dir: string): string[] {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const out: string[] = [];\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n out.push(full);\n continue;\n }\n if (entry.isSymbolicLink()) {\n try {\n if (statSync(full).isDirectory()) out.push(full);\n } catch {\n // broken symlink — skip\n }\n }\n }\n return out;\n}\n\n/** Mirrors index_skills.py's three glob shapes (top-level dirs, a \"skills\" subdir, and nested \"skills\" subdirs one level down), scoped to one resolved root. */\nfunction findSkillDirsUnderRoot(root: string): string[] {\n const found = new Set<string>();\n const topDirs = listSubdirs(root);\n\n for (const dir of topDirs) {\n if (existsSync(join(dir, 'SKILL.md'))) found.add(dir);\n }\n for (const dir of listSubdirs(join(root, 'skills'))) {\n if (existsSync(join(dir, 'SKILL.md'))) found.add(dir);\n }\n for (const topDir of topDirs) {\n for (const dir of listSubdirs(join(topDir, 'skills'))) {\n if (existsSync(join(dir, 'SKILL.md'))) found.add(dir);\n }\n }\n\n return Array.from(found).filter((p) => !p.includes('/.git/'));\n}\n\nfunction collectCandidates(roots: readonly string[]): SkillCandidate[] {\n const out: SkillCandidate[] = [];\n for (const root of roots) {\n const resolvedRoot = resolvePath(expandHome(root));\n if (!isDir(resolvedRoot)) continue;\n for (const dirPath of findSkillDirsUnderRoot(resolvedRoot)) {\n out.push({ dirPath, root });\n }\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Capability frontmatter parsing — port of index_skills.py's parse_frontmatter/parse_list\n// ---------------------------------------------------------------------------\n\ninterface ParsedCapabilityFrontmatter {\n name?: string;\n tier?: string;\n domain?: string;\n provenance?: string;\n maturity?: string;\n consumes: string[];\n validFrontmatter: boolean;\n}\n\n/** Parses a YAML list value, inline (`key: [a, b]`) or block (`- a` lines) — port of index_skills.py's parse_list. */\nfunction parseYamlList(block: string, key: string): string[] {\n const inlineMatch = block.match(new RegExp(`^${key}:\\\\s*\\\\[(.*?)\\\\]\\\\s*$`, 'm'));\n if (inlineMatch) {\n return inlineMatch[1]\n .split(',')\n .map((x) => stripQuotes(x.trim()))\n .filter(Boolean);\n }\n if (!new RegExp(`^${key}:\\\\s*$`, 'm').test(block)) return [];\n\n const lines = block.split(/\\r\\n|\\r|\\n/);\n const startIdx = lines.findIndex((l) => new RegExp(`^${key}:\\\\s*$`).test(l));\n if (startIdx === -1) return [];\n\n const out: string[] = [];\n for (const line of lines.slice(startIdx + 1)) {\n const itemMatch = line.match(/^\\s*-\\s*(.+)$/);\n if (itemMatch) {\n out.push(stripQuotes(itemMatch[1].trim()));\n } else if (line.trim() === '') {\n continue;\n } else {\n break;\n }\n }\n return out;\n}\n\n/** Extracts the frontmatter block (text between the opening and closing `---` fences), or null if unfenced/unclosed. */\nfunction extractFrontmatterBlock(text: string): string | null {\n if (!text.startsWith('---\\n')) return null;\n const afterOpen = text.slice(4);\n const closeIdx = afterOpen.indexOf('\\n---');\n if (closeIdx === -1) return null;\n return afterOpen.slice(0, closeIdx);\n}\n\n/** Port of index_skills.py's parse_frontmatter, scoped to the capability-schema fields organize needs (name/tier/domain/provenance/maturity/consumes). */\nfunction parseCapabilityFrontmatter(text: string, fallbackName: string): ParsedCapabilityFrontmatter {\n const block = extractFrontmatterBlock(text);\n if (block === null) {\n return { name: fallbackName, consumes: [], validFrontmatter: false };\n }\n\n const fields: Record<string, string> = {};\n for (const key of ['name', 'tier', 'domain', 'provenance', 'maturity']) {\n const m = block.match(new RegExp(`^${key}:\\\\s*(.+)$`, 'm'));\n if (m) fields[key] = stripQuotes(m[1].trim());\n }\n\n return {\n name: fields.name ?? fallbackName,\n tier: fields.tier,\n domain: fields.domain,\n provenance: fields.provenance,\n maturity: fields.maturity,\n consumes: parseYamlList(block, 'consumes'),\n validFrontmatter: true,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Usage snapshot join — port of index_skills.py's load_usage\n// ---------------------------------------------------------------------------\n\ninterface UsageSnapshotResult {\n usage: Map<string, number>;\n warning?: string;\n}\n\n/** Best-effort join of usage counts from a skill-monitor-style snapshot's `report.top_skills`. Never throws — any failure becomes a warning and an empty map. */\nfunction loadUsageSnapshot(snapshotPath: string): UsageSnapshotResult {\n const resolvedPath = resolvePath(expandHome(snapshotPath));\n let raw: string;\n try {\n raw = readFileSync(resolvedPath, 'utf-8');\n } catch (err) {\n return { usage: new Map(), warning: `could not read usage snapshot (${(err as Error).message}); proceeding without usage` };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n return { usage: new Map(), warning: `could not parse usage snapshot (${(err as Error).message}); proceeding without usage` };\n }\n\n const usage = new Map<string, number>();\n const outer = parsed as Record<string, unknown> | null;\n const report = outer && typeof outer === 'object' ? (outer.report as Record<string, unknown> | undefined) : undefined;\n const topSkills = report && typeof report === 'object' ? report.top_skills : undefined;\n\n if (Array.isArray(topSkills)) {\n for (const row of topSkills) {\n if (Array.isArray(row) && row.length === 2 && typeof row[0] === 'string' && typeof row[1] === 'number') {\n const [name, count] = row as [string, number];\n usage.set(name, count);\n if (name.includes(':')) {\n // bare-slug fallback for `plugin:skill` names, matching index_skills.py.\n const bare = name.split(':').pop()!;\n if (!usage.has(bare)) usage.set(bare, count);\n }\n }\n }\n }\n\n return { usage };\n}\n\n// ---------------------------------------------------------------------------\n// Dependency graph — port of build_dependency_graph.py\n// ---------------------------------------------------------------------------\n\nconst WHITE = 0;\nconst GRAY = 1;\nconst BLACK = 2;\n\n/** DFS cycle detection over an id-keyed adjacency map — direct port of build_dependency_graph.py's find_cycles. Only ever called with unambiguous (single-match) edges, since an ambiguous target has no single node to traverse to. */\nfunction findCycles(adjacency: Map<string, string[]>): string[][] {\n const color = new Map<string, number>();\n for (const id of adjacency.keys()) color.set(id, WHITE);\n const stack: string[] = [];\n const cycles: string[][] = [];\n\n function dfs(u: string): void {\n color.set(u, GRAY);\n stack.push(u);\n for (const v of adjacency.get(u) ?? []) {\n if (!color.has(v)) continue; // not a node in this adjacency map\n if (color.get(v) === GRAY && stack.includes(v)) {\n const idx = stack.indexOf(v);\n cycles.push([...stack.slice(idx), v]);\n } else if (color.get(v) === WHITE) {\n dfs(v);\n }\n }\n color.set(u, BLACK);\n stack.pop();\n }\n\n for (const id of adjacency.keys()) {\n if (color.get(id) === WHITE) dfs(id);\n }\n return cycles;\n}\n\nfunction buildGraph(entries: OrganizeRegistryEntry[], nameToIds: Map<string, string[]>): OrganizeGraph {\n const nodes: OrganizeGraphNode[] = entries.map((e) => ({ id: e.id, name: e.name, tier: e.tier, domain: e.domain }));\n const edges: OrganizeGraphEdge[] = [];\n const dangling: OrganizeGraphDanglingEdge[] = [];\n const ambiguous: OrganizeGraphAmbiguousEdge[] = [];\n const incoming = new Map<string, number>(entries.map((e) => [e.id, 0]));\n const adjacency = new Map<string, string[]>(entries.map((e) => [e.id, []]));\n\n for (const e of entries) {\n for (const target of e.consumes) {\n const matches = nameToIds.get(target) ?? [];\n edges.push({ from: e.id, to: target, resolvedIds: matches });\n\n if (matches.length === 0) {\n dangling.push({ from: e.id, to: target });\n } else if (matches.length === 1) {\n incoming.set(matches[0], (incoming.get(matches[0]) ?? 0) + 1);\n adjacency.get(e.id)!.push(matches[0]);\n } else {\n ambiguous.push({ from: e.id, to: target, matches });\n }\n }\n }\n\n const orphans = entries.filter((e) => e.tier === 'custom' && e.consumes.length === 0).map((e) => e.id);\n\n const byId = new Map(entries.map((e) => [e.id, e]));\n const hotResources: OrganizeGraphHotResource[] = Array.from(incoming.entries())\n .filter(([, count]) => count > 0)\n .map(([id, count]) => ({ id, name: byId.get(id)!.name, consumedBy: count }))\n .sort((a, b) => b.consumedBy - a.consumedBy);\n\n const cycles = findCycles(adjacency);\n\n return {\n nodeCount: nodes.length,\n edgeCount: edges.length,\n nodes,\n edges,\n orphans,\n hotResources,\n danglingEdges: dangling,\n ambiguousConsumesTargets: ambiguous,\n cycles,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Top-level engine\n// ---------------------------------------------------------------------------\n\n/** Resolves each candidate to a deduped, realpath-qualified registry entry. Read/resolve failures become warnings, not thrown errors. */\nfunction buildRegistryEntries(candidates: SkillCandidate[], warnings: string[]): OrganizeRegistryEntry[] {\n const entries: OrganizeRegistryEntry[] = [];\n const seenRealPaths = new Set<string>();\n\n for (const candidate of candidates) {\n let realPath: string;\n try {\n realPath = realpathSync(candidate.dirPath);\n } catch (err) {\n warnings.push(`could not resolve skill directory ${candidate.dirPath}: ${(err as Error).message}`);\n continue;\n }\n if (seenRealPaths.has(realPath)) continue;\n seenRealPaths.add(realPath);\n\n const skillMdPath = join(realPath, 'SKILL.md');\n let text: string;\n try {\n text = readFileSync(skillMdPath, 'utf-8');\n } catch (err) {\n warnings.push(`could not read ${skillMdPath}: ${(err as Error).message}`);\n continue;\n }\n\n const fm = parseCapabilityFrontmatter(text, basename(realPath));\n const name = fm.name ?? basename(realPath);\n if (fm.tier && !TIERS.has(fm.tier)) {\n warnings.push(`${name}: tier '${fm.tier}' not in ${JSON.stringify(Array.from(TIERS).sort())}`);\n }\n if (fm.maturity && !MATURITIES.has(fm.maturity)) {\n warnings.push(`${name}: maturity '${fm.maturity}' not in ${JSON.stringify(Array.from(MATURITIES).sort())}`);\n }\n\n entries.push({\n id: realPath,\n name,\n path: realPath,\n root: candidate.root,\n tier: fm.tier,\n domain: fm.domain,\n consumes: fm.consumes,\n provenance: fm.provenance,\n maturity: fm.maturity,\n validFrontmatter: fm.validFrontmatter,\n });\n }\n\n return entries;\n}\n\n/** Joins usage counts from a snapshot onto `entries` by name (mutates entries in place). Returns whether a non-empty snapshot was loaded. */\nfunction joinUsageSnapshot(entries: OrganizeRegistryEntry[], snapshotPath: string, warnings: string[]): boolean {\n const { usage, warning } = loadUsageSnapshot(snapshotPath);\n if (warning) warnings.push(warning);\n if (usage.size === 0) return false;\n\n for (const entry of entries) {\n const count = usage.get(entry.name);\n if (count !== undefined) entry.usage = count;\n }\n if (!entries.some((e) => e.usage !== undefined)) {\n warnings.push(\n 'usage snapshot loaded but 0 skills matched by name — registry `name:` vs telemetry key mismatch; the usage column will be empty'\n );\n }\n return true;\n}\n\n/** Groups entry ids by name, warning on any name shared across >1 id (e.g. duplicate skill names across roots). */\nfunction buildNameToIds(entries: OrganizeRegistryEntry[], warnings: string[]): Map<string, string[]> {\n const nameToIds = new Map<string, string[]>();\n for (const e of entries) {\n const ids = nameToIds.get(e.name) ?? [];\n ids.push(e.id);\n nameToIds.set(e.name, ids);\n }\n for (const [name, ids] of nameToIds) {\n if (ids.length > 1) {\n warnings.push(`duplicate skill name \"${name}\" across ${ids.length} locations: ${ids.join(', ')}`);\n }\n }\n return nameToIds;\n}\n\n/** Entries missing `tier` and/or `domain` — rot detection. */\nfunction findUntagged(entries: OrganizeRegistryEntry[]): OrganizeUntaggedEntry[] {\n return entries\n .filter((e) => !e.tier || !e.domain)\n .map((e) => ({\n id: e.id,\n name: e.name,\n missing: [!e.tier ? 'tier' : undefined, !e.domain ? 'domain' : undefined].filter((v): v is string => Boolean(v)),\n }));\n}\n\n/** Entry counts grouped by tier and by domain, with `undefined` bucketed as `'untagged'`. */\nfunction countByTierAndDomain(entries: OrganizeRegistryEntry[]): { byTier: Record<string, number>; byDomain: Record<string, number> } {\n const byTier: Record<string, number> = {};\n const byDomain: Record<string, number> = {};\n for (const e of entries) {\n byTier[e.tier ?? 'untagged'] = (byTier[e.tier ?? 'untagged'] ?? 0) + 1;\n byDomain[e.domain ?? 'untagged'] = (byDomain[e.domain ?? 'untagged'] ?? 0) + 1;\n }\n return { byTier, byDomain };\n}\n\n/**\n * Builds the capability registry + dependency graph for every skill under\n * `config.skillsRoots`. Pro-gated BEFORE any filesystem walk — a locked\n * license (post-1.0, no key) throws a UserError and never touches disk.\n * Never executes anything it scans.\n */\nexport function buildOrganizeReport(config: Config, options: OrganizeOptions = {}): OrganizeReport {\n const proStatus = proFeatureStatus(PRO_FEATURE_LABEL);\n if (!proStatus.enabled) {\n throw new UserError(proStatus.notice ?? upgradeNotice(PRO_FEATURE_LABEL));\n }\n const notices: string[] = proStatus.notice ? [proStatus.notice] : [];\n const warnings: string[] = [];\n\n const candidates = collectCandidates(config.skillsRoots);\n if (candidates.length === 0) {\n throw new UserError(\n `no SKILL.md found under any configured skills root (${config.skillsRoots.join(', ') || '(none configured)'})`\n );\n }\n\n const entries = buildRegistryEntries(candidates, warnings);\n\n const usageJoined = options.usageSnapshot ? joinUsageSnapshot(entries, options.usageSnapshot, warnings) : false;\n\n const nameToIds = buildNameToIds(entries, warnings);\n const untagged = findUntagged(entries);\n const { byTier, byDomain } = countByTierAndDomain(entries);\n\n const registry: OrganizeRegistry = {\n skillsRoots: [...config.skillsRoots],\n count: entries.length,\n byTier,\n byDomain,\n untagged,\n warnings,\n usageJoined,\n skills: entries,\n };\n\n const graph = buildGraph(entries, nameToIds);\n\n return {\n generatedAt: new Date().toISOString(),\n registry,\n graph,\n notices,\n };\n}\n","/**\n * skillsShApi.ts — hardened HTTP client for the skills.sh discovery/audit API\n * (https://skills.sh/docs/api, base https://skills.sh/api/v1). TypeScript port of\n * the free-tier `skills_sh.py` script (rhize-plugins repo) — MIT, no commercial header.\n *\n * Hardening (all required by the Lane B1 spec, see .claude/plans/forge-plugin-merge.md):\n * - every URL is built from a fixed origin constant via `new URL` + `URLSearchParams`\n * - candidate ids (\"{source}/{slug}\") are validated against a strict charset and each\n * path segment is individually `encodeURIComponent`-encoded (slashes preserved)\n * - every request uses `redirect: 'error'` and a 15s `AbortSignal.timeout`\n * - response bodies are capped at 2 MB (checked before JSON.parse) and the parsed shape\n * is validated before use — malformed/oversized bodies fail loud, never partially used\n * - the bearer token is read from `process.env.VERCEL_OIDC_TOKEN` only, is never logged,\n * echoed, or included in any thrown error message\n * - `fetch` is injectable so tests never hit the network (mirrors the `ExecFn` DI idiom\n * in src/install/quarantine.ts)\n */\nexport const SKILLS_SH_ORIGIN = 'https://skills.sh';\nexport const SKILLS_SH_API_BASE = '/api/v1';\nconst MAX_RESPONSE_BYTES = 2 * 1024 * 1024; // 2 MB\nconst REQUEST_TIMEOUT_MS = 15_000;\nconst ID_PATTERN = /^[A-Za-z0-9._/-]+$/;\n\nexport const TOKEN_HINT =\n 'No VERCEL_OIDC_TOKEN found. skills.sh authenticates with a short-lived Vercel OIDC token:\\n' +\n ' 1. Vercel dashboard -> your project -> Settings -> OIDC Federation -> enable.\\n' +\n ' 2. npm i -g vercel && vercel link && vercel env pull (writes VERCEL_OIDC_TOKEN to .env.local)\\n' +\n ' 3. export VERCEL_OIDC_TOKEN from .env.local into your shell before running this command\\n' +\n ' (or set it directly: export VERCEL_OIDC_TOKEN=...) - docs: https://skills.sh/docs/api';\n\n/** Thrown for missing token / hostile id / oversized or malformed body / unexpected response shape. Callers set `process.exitCode` from `.exitCode`. */\nexport class SkillsShError extends Error {\n exitCode: number;\n constructor(message: string, exitCode: number) {\n super(message);\n this.name = 'SkillsShError';\n this.exitCode = exitCode;\n }\n}\n\nexport type FetchFn = typeof fetch;\n\nexport interface SkillsShSearchItem {\n id: string;\n name?: string;\n installs?: number | string;\n installUrl?: string;\n [key: string]: unknown;\n}\n\nexport interface SkillsShSearchResponse {\n data: SkillsShSearchItem[];\n count?: number;\n searchType?: string;\n [key: string]: unknown;\n}\n\nexport interface SkillsShAuditEntry {\n provider?: string;\n status?: string;\n riskLevel?: string;\n summary?: string;\n [key: string]: unknown;\n}\n\nexport interface SkillsShAuditResponse {\n audits: SkillsShAuditEntry[];\n [key: string]: unknown;\n}\n\nconst RISK_ORDER: Record<string, number> = { NONE: 0, LOW: 1, MEDIUM: 2, HIGH: 3, CRITICAL: 4 };\n\n/** Reads the OIDC token from the environment only — never from a config file or CLI flag, so it's never persisted or accidentally echoed by `status`/`--json`. */\nexport function getToken(env: NodeJS.ProcessEnv = process.env): string | undefined {\n const tok = env.VERCEL_OIDC_TOKEN;\n return tok ? tok : undefined;\n}\n\n/** Validates a skills.sh candidate id (\"{source}/{slug}\", e.g. \"vercel-labs/skills/find-skills\") against a strict charset before it ever reaches URL construction. */\nfunction assertValidId(id: string): void {\n if (id.length === 0 || !ID_PATTERN.test(id)) {\n throw new SkillsShError(`invalid skills.sh id: ${JSON.stringify(id)}`, 1);\n }\n}\n\n/**\n * Validates `id` and splits it into path segments — the id must never reach `call()` unvalidated.\n * Beyond the charset check, rejects empty, `.` and `..` segments: WHATWG URL parsing normalizes\n * dot-segments on the ASSEMBLED path (after per-segment encoding), so `../..` in an id would\n * otherwise walk the request out of the /api/v1 namespace while still carrying the bearer token.\n */\nfunction validatedIdSegments(id: string): string[] {\n assertValidId(id);\n const segments = id.split('/');\n if (segments.some((s) => s === '' || s === '.' || s === '..')) {\n throw new SkillsShError(`invalid skills.sh id: ${JSON.stringify(id)}`, 1);\n }\n return segments;\n}\n\n/** Builds a same-origin skills.sh API URL from a fixed origin constant, encoding each path segment individually so slashes in ids stay structural rather than escaped. */\nfunction buildUrl(pathSegments: string[], params?: Record<string, string | number>): URL {\n const url = new URL(SKILLS_SH_API_BASE + '/' + pathSegments.map((s) => encodeURIComponent(s)).join('/'), SKILLS_SH_ORIGIN);\n if (params) {\n const search = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) search.set(k, String(v));\n url.search = search.toString();\n }\n return url;\n}\n\n/** Reads a Response body as text, refusing anything over MAX_RESPONSE_BYTES before it's ever handed to JSON.parse. */\nasync function readCappedText(res: Response): Promise<string> {\n const text = await res.text();\n // TextEncoder gives the actual byte length (UTF-8), not just JS string length.\n if (new TextEncoder().encode(text).length > MAX_RESPONSE_BYTES) {\n throw new SkillsShError('skills.sh response exceeded the 2 MB size cap', 1);\n }\n return text;\n}\n\ninterface CallOptions {\n fetchImpl?: FetchFn;\n env?: NodeJS.ProcessEnv;\n}\n\n/**\n * Core authenticated GET against the skills.sh API. Returns `undefined` for a 404\n * (matches skills_sh.py's `return None` for \"not found yet\"). Never echoes the raw\n * response body of a non-2xx status back to the caller — only a truncated, token-free\n * snippet — so credentials or internal detail can't leak through an error message.\n */\nasync function call(pathSegments: string[], params: Record<string, string | number> | undefined, options: CallOptions = {}): Promise<unknown> {\n const env = options.env ?? process.env;\n const token = getToken(env);\n if (!token) {\n throw new SkillsShError(TOKEN_HINT, 3);\n }\n\n const url = buildUrl(pathSegments, params);\n const fetchImpl = options.fetchImpl ?? fetch;\n\n let res: Response;\n try {\n res = await fetchImpl(url, {\n headers: { Authorization: `Bearer ${token}` },\n redirect: 'error',\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new SkillsShError(`skills.sh request failed: ${message}`, 1);\n }\n\n if (res.status === 404) {\n return undefined;\n }\n\n if (!res.ok) {\n // 401 never echoes the response body — an auth-failure body can carry request\n // detail we don't want surfaced (and never the token, but keep the margin wide).\n if (res.status === 401) {\n throw new SkillsShError('skills.sh HTTP 401: unauthorized — the VERCEL_OIDC_TOKEN was rejected (expired or invalid)', 3);\n }\n let snippet = '';\n try {\n snippet = (await readCappedText(res)).slice(0, 300);\n } catch {\n // ignore body-read failures on an already-erroring response\n }\n throw new SkillsShError(`skills.sh HTTP ${res.status}${snippet ? `: ${snippet}` : ''}`, 1);\n }\n\n const text = await readCappedText(res);\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new SkillsShError('skills.sh returned malformed JSON', 1);\n }\n return parsed;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/** `GET /skills/search?q=&limit=` — validates the response has a `data` array before returning it. */\nexport async function search(query: string, limit: number, options: CallOptions = {}): Promise<SkillsShSearchResponse> {\n const data = await call(['skills', 'search'], { q: query, limit }, options);\n if (!isRecord(data) || !Array.isArray(data.data)) {\n throw new SkillsShError('skills.sh search response had an unexpected shape', 1);\n }\n return data as unknown as SkillsShSearchResponse;\n}\n\n/** `GET /skills/audit/{id}` — `undefined` means no partner audits exist yet for this id. */\nexport async function audit(id: string, options: CallOptions = {}): Promise<SkillsShAuditResponse | undefined> {\n const data = await call(['skills', 'audit', ...validatedIdSegments(id)], undefined, options);\n if (data === undefined) return undefined;\n if (!isRecord(data) || !Array.isArray(data.audits)) {\n throw new SkillsShError('skills.sh audit response had an unexpected shape', 1);\n }\n return data as unknown as SkillsShAuditResponse;\n}\n\n/** `GET /skills/{id}` — full detail + file tree, returned raw (only used for `--json`/get output). */\nexport async function get(id: string, options: CallOptions = {}): Promise<unknown> {\n return call(['skills', ...validatedIdSegments(id)], undefined, options);\n}\n\n/** `GET /skills/curated`. */\nexport async function curated(options: CallOptions = {}): Promise<unknown> {\n return call(['skills', 'curated'], undefined, options);\n}\n\n/** Aggregates a `SkillsShAuditResponse` into (verdict, worst risk level, audits) — port of skills_sh.py's `audit_verdict`. */\nexport function auditVerdict(data: SkillsShAuditResponse): { verdict: 'fail' | 'warn' | 'pass' | 'none'; risk: string; audits: SkillsShAuditEntry[] } {\n const audits = data.audits ?? [];\n const statuses = new Set(audits.map((a) => a.status));\n let worst = 'NONE';\n let worstRank = 0;\n for (const a of audits) {\n const rl = a.riskLevel ?? 'NONE';\n const rank = RISK_ORDER[rl] ?? 0;\n if (rank > worstRank) {\n worstRank = rank;\n worst = rl;\n }\n }\n const verdict: 'fail' | 'warn' | 'pass' | 'none' = statuses.has('fail')\n ? 'fail'\n : statuses.has('warn')\n ? 'warn'\n : audits.length > 0\n ? 'pass'\n : 'none';\n return { verdict, risk: worst, audits };\n}\n","import { sanitizeForMarkdown } from '../audit.js';\nimport { UserError } from '../errors.js';\nimport {\n audit as apiAudit,\n auditVerdict,\n curated as apiCurated,\n get as apiGet,\n search as apiSearch,\n SkillsShError,\n type FetchFn,\n type SkillsShSearchItem,\n} from '../gate/skillsShApi.js';\n\nexport interface FindOptions {\n limit?: number;\n json?: boolean;\n audit?: string;\n get?: string;\n curated?: boolean;\n}\n\nexport interface FindDeps {\n fetchImpl?: FetchFn;\n env?: NodeJS.ProcessEnv;\n}\n\nconst INSTALL_HINT_NOTE =\n 'Results are unvetted — `skill-forge add <id>` runs the full quarantine + safety gate; ' +\n '`skill-forge find --audit <id>` shows partner audit verdicts.';\n\nfunction isRecordWithDataArray(v: unknown): v is { data: SkillsShSearchItem[]; count?: number; searchType?: string } {\n return typeof v === 'object' && v !== null && Array.isArray((v as { data?: unknown }).data);\n}\n\n/** Emits exactly one JSON document on stdout — the `--json` contract shared by every mode. */\nfunction printJson(data: unknown): void {\n console.log(JSON.stringify(data, null, 2));\n}\n\nfunction printSearchResults(data: { data: SkillsShSearchItem[]; count?: number; searchType?: string }, query: string): void {\n const items = data.data;\n const count = data.count ?? items.length;\n const searchType = data.searchType ? ` (${sanitizeForMarkdown(data.searchType)})` : '';\n console.log(`skills.sh — ${count} matches for ${JSON.stringify(query)}${searchType}:`);\n for (const item of items) {\n const name = sanitizeForMarkdown(String(item.name ?? item.id));\n const id = sanitizeForMarkdown(String(item.id));\n const installs = item.installs ?? '?';\n console.log(` - ${name} [${id}] ${installs} installs [unvetted]`);\n console.log(` install: skill-forge add ${id}`);\n }\n console.log('');\n console.log(INSTALL_HINT_NOTE);\n}\n\n/**\n * `skill-forge find` — TypeScript port of the plugin's `skills_sh.py`. Exactly one mode\n * per invocation: a search query (default), `--audit <id>`, `--get <id>`, or `--curated`.\n * All requests hit https://skills.sh/api/v1 over HTTPS with a `VERCEL_OIDC_TOKEN` bearer\n * token (never printed); a missing/invalid token fails loud with setup guidance and\n * `process.exitCode = 3` before any request is made. Search/curated/audit results are\n * always labeled unvetted — the deep quarantine + safety gate only runs on `add`.\n */\nexport async function find(query: string | undefined, options: FindOptions = {}, deps: FindDeps = {}): Promise<void> {\n const modes = [query !== undefined, options.audit !== undefined, options.get !== undefined, Boolean(options.curated)];\n const activeCount = modes.filter(Boolean).length;\n if (activeCount === 0) {\n throw new UserError('skill-forge find: provide a search query, or exactly one of --audit <id>, --get <id>, --curated');\n }\n if (activeCount > 1) {\n throw new UserError('skill-forge find: exactly one mode per invocation — a search query, or exactly one of --audit <id>, --get <id>, --curated');\n }\n\n const callOptions = { fetchImpl: deps.fetchImpl, env: deps.env };\n\n try {\n if (query !== undefined) {\n if (query.trim().length < 2) {\n throw new UserError('skill-forge find: query must be at least 2 characters');\n }\n const limit = options.limit ?? 10;\n const data = await apiSearch(query, limit, callOptions);\n if (options.json) {\n printJson(data);\n return;\n }\n printSearchResults(data, query);\n return;\n }\n\n if (options.audit !== undefined) {\n const id = options.audit;\n const data = await apiAudit(id, callOptions);\n if (!data) {\n console.log(`no partner audits yet for ${sanitizeForMarkdown(id)} (generated after first install).`);\n return;\n }\n if (options.json) {\n printJson(data);\n return;\n }\n const { verdict, risk, audits } = auditVerdict(data);\n console.log(\n `skills.sh audit for ${sanitizeForMarkdown(id)}: ${verdict.toUpperCase()} (max risk ${sanitizeForMarkdown(risk)}) — ${audits.length} partner(s)`\n );\n for (const entry of audits) {\n const provider = sanitizeForMarkdown(String(entry.provider ?? 'unknown'));\n const status = sanitizeForMarkdown(String(entry.status ?? 'unknown'));\n const riskLevel = entry.riskLevel ? ` · ${sanitizeForMarkdown(String(entry.riskLevel))}` : '';\n const summary = entry.summary ? ` — ${sanitizeForMarkdown(String(entry.summary))}` : '';\n console.log(` - ${provider}: ${status}${riskLevel}${summary}`);\n }\n return;\n }\n\n if (options.get !== undefined) {\n const data = await apiGet(options.get, callOptions);\n printJson(data);\n return;\n }\n\n // --curated\n const data = await apiCurated(callOptions);\n if (options.json) {\n printJson(data);\n return;\n }\n if (isRecordWithDataArray(data)) {\n printSearchResults(data, '(curated)');\n return;\n }\n printJson(data);\n } catch (err) {\n if (err instanceof SkillsShError) {\n console.error(err.message);\n process.exitCode = err.exitCode;\n return;\n }\n throw err;\n }\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { realpathSync } from 'node:fs';\nimport { join, resolve as resolvePath } from 'node:path';\nimport { sanitizeForMarkdown } from '../audit.js';\nimport { loadConfig, type Config } from '../config.js';\nimport { defaultExec } from '../install/quarantine.js';\nimport type { ExecFn } from '../install/quarantine.js';\nimport { proFeatureStatus } from '../license.js';\nimport { readProvenanceLedger } from '../provenance.js';\nimport { expandHome, isDir } from '../util.js';\nimport { loadSkillMap } from '../gate/skillMap.js';\nimport { computeMapDrift, type MapDriftRow } from '../gate/skillMapDrift.js';\n\n/**\n * `skill-forge watch` (Pro) — TS port of `record_provenance.py --check-drift`. Scans EVERY\n * configured skills root's `SOURCES.md` provenance ledger (src/provenance.ts's\n * `readProvenanceLedger`/`parseProvenanceLedger`) and reports, per tracked (non-REJECT) entry,\n * whether its recorded upstream ref still matches what the source currently resolves to.\n *\n * HARD SECURITY INVARIANT: the ledger's stored `Drift check` command string is\n * attacker-influenceable data (anyone who can write to a skills root's SOURCES.md controls it) —\n * it is NEVER executed, under any circumstances. It is only ever printed, sanitized, as a\n * suggestion for the human/agent to run themselves. The only subprocess this module ever spawns\n * is a fixed, first-party `git ls-remote <url> [ref]` invocation (argv array, no shell), and only\n * for entries whose Source is recognizably a git URL and whose Upstream ref looks like a git ref\n * — see `test/watch.test.ts`'s guard test asserting no spawn happens for anything else.\n */\n\nconst PRO_FEATURE_LABEL = 'provenance drift watch (watch)';\n\nexport type DriftCheckMethod = 'git-ls-remote' | 'manual';\nexport type DriftStatus = 'moved' | 'unchanged' | 'unreachable' | 'not-checked';\n\nexport interface WatchRow {\n /** The configured root (raw, as given in config.skillsRoots) this entry's ledger belongs to. */\n root: string;\n ledgerPath: string;\n name: string;\n verb: string;\n source: string;\n upstreamRef: string;\n checkMethod: DriftCheckMethod;\n driftStatus: DriftStatus;\n detail: string;\n /** Stored drift-check command string, as recorded — DATA ONLY, never executed. See module doc comment. */\n driftCheckCommand: string;\n}\n\nexport interface WatchedRoot {\n root: string;\n realPath: string;\n exists: boolean;\n ledgerPath: string;\n ledgerExists: boolean;\n}\n\nexport interface WatchReport {\n roots: WatchedRoot[];\n rows: WatchRow[];\n /** Ledger parse warnings (malformed blocks skipped) — see provenance.ts's `parseProvenanceLedger`. */\n warnings: string[];\n notices: string[];\n /**\n * Drift results for every `fork-of` edge in the skill map passed via `--skill-map` (Phase 4,\n * `skill-map-graph-substrate.md`) — empty when `--skill-map` wasn't given. See\n * `gate/skillMapDrift.ts`'s module doc comment for the never-execute invariant this shares\n * with the SOURCES.md-ledger drift check above.\n */\n mapDrift: MapDriftRow[];\n}\n\nexport interface WatchOptions {\n json?: boolean;\n /** Skip all network calls — every entry is reported `not-checked` for manual comparison instead. */\n offline?: boolean;\n /** Path to the generated `rhize-plugins` skill map — when given, also runs the fork-of drift check against its edges (Phase 4). */\n skillMap?: string;\n}\n\nexport interface WatchDeps {\n /** Injected subprocess runner for `git ls-remote`. Defaults to a real argv-array spawn (no shell). */\n exec?: ExecFn;\n}\n\n/** True for an `https://`/`git@` URL — the only source shapes `git ls-remote` can be pointed at. */\nfunction isGitUrl(source: string): boolean {\n const s = source.trim();\n return /^https?:\\/\\/\\S+$/i.test(s) || /^git@[\\w.-]+:\\S+$/i.test(s);\n}\n\n/** True when `ref` is a plausible git ref token (commit sha or tag/branch name) — no whitespace or shell metacharacters, not empty, not the ledger's \"n/a\" placeholder. */\n/**\n * Only a commit-sha pin can be drift-compared against `git ls-remote` output — the remote\n * always resolves to a sha, so a tag/version-string pin (e.g. `v1.2.0`, or a frontmatter\n * version recorded by `add`) would compare a name against a sha and report \"moved\" on every\n * run. Non-sha refs route to the manual lane instead of getting a false verdict.\n */\nfunction looksLikeGitRef(ref: string): boolean {\n const r = ref.trim();\n return /^[0-9a-fA-F]{7,40}$/.test(r);\n}\n\nfunction firstNonEmptyLine(text: string): string {\n return text.split('\\n').find((l) => l.trim().length > 0)?.trim() ?? '(no output)';\n}\n\n/**\n * Runs `git ls-remote -- <url> <ref>` (fixed argv, no shell) and compares the resolved sha\n * against the recorded `ref`. This is the ONLY subprocess this module ever spawns, and it never\n * touches the ledger's stored `driftCheck` field.\n */\nasync function checkGitDrift(exec: ExecFn, url: string, ref: string): Promise<{ status: DriftStatus; detail: string }> {\n let result;\n try {\n result = await exec('git', ['ls-remote', '--', url, ref], { cwd: process.cwd() });\n } catch (err) {\n return { status: 'unreachable', detail: `git ls-remote failed to run: ${(err as Error).message}` };\n }\n if (result.code !== 0) {\n return { status: 'unreachable', detail: `git ls-remote exited ${result.code}: ${firstNonEmptyLine(result.stderr || result.stdout)}` };\n }\n // An annotated tag returns two lines (tag object + peeled `^{}` commit) — compare the\n // recorded ref against EVERY returned sha so a peeled-commit pin isn't a false \"moved\".\n const shas = result.stdout\n .split('\\n')\n .map((l) => l.trim())\n .filter((l) => l.length > 0)\n .map((l) => l.split(/\\s+/)[0] ?? '')\n .filter((sha) => sha.length > 0);\n if (shas.length === 0) {\n return { status: 'unreachable', detail: `no ref matching \"${ref}\" found on remote` };\n }\n const recorded = ref.trim().toLowerCase();\n if (shas.some((sha) => sha.toLowerCase() === recorded)) {\n return { status: 'unchanged', detail: `current: ${recorded}` };\n }\n return { status: 'moved', detail: `recorded ${ref}, remote now resolves to ${shas[0]}` };\n}\n\n/**\n * Scans every configured skills root's provenance ledger and classifies each tracked\n * (non-REJECT) entry's drift-check status. Pro-gated BEFORE any file read — a locked license\n * returns an empty report with a notice and touches nothing on disk. `config` is an explicit\n * parameter (rather than this function calling `loadConfig()` itself) so tests can point it at a\n * fixture set of skills roots without touching `~/.skill-forge/config.json` — mirrors\n * `auditConfiguredSet(config, options)`'s shape in src/audit.ts. `watchCommand` below is the CLI\n * entry point that supplies the real, loaded config.\n */\nexport async function runWatch(config: Config, options: WatchOptions = {}, deps: WatchDeps = {}): Promise<WatchReport> {\n const proStatus = proFeatureStatus(PRO_FEATURE_LABEL);\n if (!proStatus.enabled) {\n return { roots: [], rows: [], warnings: [], notices: [proStatus.notice ?? 'Pro feature (watch) locked.'], mapDrift: [] };\n }\n\n const notices: string[] = proStatus.notice ? [proStatus.notice] : [];\n const exec = deps.exec ?? defaultExec;\n\n const roots: WatchedRoot[] = [];\n const rows: WatchRow[] = [];\n const warnings: string[] = [];\n const seenRealPaths = new Set<string>();\n\n for (const root of config.skillsRoots) {\n const resolvedRoot = resolvePath(expandHome(root));\n const exists = isDir(resolvedRoot);\n let realPath = resolvedRoot;\n if (exists) {\n try {\n realPath = realpathSync(resolvedRoot);\n } catch {\n realPath = resolvedRoot;\n }\n }\n if (seenRealPaths.has(realPath)) continue;\n seenRealPaths.add(realPath);\n\n // Per-root failure tolerance (mirrors audit.ts's per-item philosophy): an unreadable\n // ledger in one root becomes a warning, never an abort of the whole drift check.\n let ledger: ReturnType<typeof readProvenanceLedger>;\n try {\n ledger = readProvenanceLedger(resolvedRoot);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const ledgerPath = join(resolvedRoot, 'SOURCES.md');\n roots.push({ root, realPath, exists, ledgerPath, ledgerExists: false });\n warnings.push(`${ledgerPath}: could not read ledger: ${message}`);\n continue;\n }\n roots.push({ root, realPath, exists, ledgerPath: ledger.ledgerPath, ledgerExists: ledger.exists });\n warnings.push(...ledger.warnings.map((w) => `${ledger.ledgerPath}: ${w}`));\n\n for (const entry of ledger.entries) {\n // Mirrors record_provenance.py's check-drift: a rejected source isn't tracked going forward.\n if (entry.verb === 'REJECT') continue;\n\n const rowBase = {\n root,\n ledgerPath: ledger.ledgerPath,\n name: entry.name,\n verb: entry.verb,\n source: entry.source,\n upstreamRef: entry.upstreamRef,\n driftCheckCommand: entry.driftCheck,\n };\n\n if (!(isGitUrl(entry.source) && looksLikeGitRef(entry.upstreamRef))) {\n rows.push({\n ...rowBase,\n checkMethod: 'manual',\n driftStatus: 'not-checked',\n detail: 'source is not a recognized git URL + ref pin — check manually',\n });\n continue;\n }\n\n if (options.offline) {\n rows.push({\n ...rowBase,\n checkMethod: 'git-ls-remote',\n driftStatus: 'not-checked',\n detail: '--offline: network check skipped',\n });\n continue;\n }\n\n const { status, detail } = await checkGitDrift(exec, entry.source, entry.upstreamRef);\n rows.push({ ...rowBase, checkMethod: 'git-ls-remote', driftStatus: status, detail });\n }\n }\n\n let mapDrift: MapDriftRow[] = [];\n if (options.skillMap) {\n try {\n const map = loadSkillMap(options.skillMap);\n mapDrift = await computeMapDrift(\n map,\n options.offline ? { fetchUpstream: async () => undefined } : undefined\n );\n } catch (err) {\n warnings.push(`skill map at ${options.skillMap}: ${(err as Error).message}`);\n }\n }\n\n return { roots, rows, warnings, notices, mapDrift };\n}\n\nfunction printWatchTerminal(report: WatchReport): void {\n console.log('[skill-forge] watch — provenance drift check');\n for (const notice of report.notices) console.log(`[notice] ${notice}`);\n\n if (report.roots.length === 0) {\n console.log('\\nNo configured skills roots.');\n }\n\n for (const root of report.roots) {\n console.log(`\\n${sanitizeForMarkdown(root.root)} (${sanitizeForMarkdown(root.realPath)})`);\n if (!root.exists) {\n console.log(' root does not exist on disk');\n continue;\n }\n if (!root.ledgerExists) {\n console.log(` no ledger at ${sanitizeForMarkdown(root.ledgerPath)}`);\n continue;\n }\n console.log(` ledger: ${sanitizeForMarkdown(root.ledgerPath)}`);\n\n const rowsForRoot = report.rows.filter((r) => r.root === root.root);\n if (rowsForRoot.length === 0) {\n console.log(' (no tracked, non-REJECT sources)');\n continue;\n }\n for (const row of rowsForRoot) {\n console.log(` • ${sanitizeForMarkdown(row.name)} [${sanitizeForMarkdown(row.verb)}] ref=${sanitizeForMarkdown(row.upstreamRef)}`);\n if (row.checkMethod === 'git-ls-remote') {\n console.log(` drift: ${row.driftStatus} — ${sanitizeForMarkdown(row.detail)}`);\n } else {\n console.log(` manual check — stored drift-check command (data, NOT executed): ${sanitizeForMarkdown(row.driftCheckCommand)}`);\n }\n }\n }\n\n if (report.warnings.length > 0) {\n console.log('\\nWarnings:');\n for (const w of report.warnings) console.log(` - ${sanitizeForMarkdown(w)}`);\n }\n\n if (report.mapDrift.length > 0) {\n console.log('\\nSkill-map fork-of drift:');\n for (const row of report.mapDrift) {\n console.log(` • ${sanitizeForMarkdown(row.skill)} <- ${sanitizeForMarkdown(row.upstream)}`);\n console.log(` ${row.status} — ${sanitizeForMarkdown(row.detail)}`);\n }\n }\n\n console.log('\\nCompare each stored ref to current upstream; re-run ingestion on any that moved.');\n}\n\n/**\n * `skill-forge watch` CLI entry point. `--json` prints exactly one JSON document on stdout;\n * notices go to stderr in that mode so stdout stays parseable.\n */\nexport async function watchCommand(options: WatchOptions = {}): Promise<void> {\n const report = await runWatch(loadConfig(), options);\n\n if (options.json) {\n console.log(JSON.stringify(report, null, 2));\n for (const notice of report.notices) console.error(`[skill-forge] ${notice}`);\n return;\n }\n\n printWatchTerminal(report);\n}\n","import { createHash } from 'node:crypto';\nimport { readFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { expandHome } from '../util.js';\nimport { edgesOfType, nodeById, type SkillMap, type SkillMapNode } from './skillMap.js';\n\n/**\n * Skill-map drift checks — the map-graph counterpart of `commands/watch.ts`'s SOURCES.md\n * ledger drift check, driven by the map's `fork-of` edges instead of a per-root SOURCES.md.\n *\n * HARD SECURITY INVARIANT (same rule as watch.ts's module doc comment, and the plan's\n * Phase-4 \"never execute\" rule): nothing derived from the skill map, from a fork-of edge's\n * `driftCheck` metadata, or from fetched upstream content is ever executed as a command,\n * script, or shell string. The ONLY operations this module performs are: (1) read a local\n * file from disk, (2) fetch a remote URL's raw bytes over HTTP(S), (3) hash bytes with\n * sha256, (4) compare hashes/text. See `test/skillMapDrift.test.ts`'s guard test asserting\n * `driftCheck` fields are never read for anything but display.\n */\n\nexport type MapDriftStatus = 'in-sync' | 'drifted' | 'upstream-unreachable' | 'local-missing';\n\nexport interface MapDriftRow {\n skill: string;\n upstream: string;\n status: MapDriftStatus;\n detail: string;\n}\n\nexport interface MapDriftDeps {\n /** Reads a local file's raw text — defaults to a real `fs.readFileSync`. Overridable for tests (fixture \"upstream\" paths, no network). */\n readLocalFile?: (path: string) => string | undefined;\n /** Fetches a remote URL's raw text — defaults to global `fetch`. Overridable for tests. */\n fetchUpstream?: (url: string) => Promise<string | undefined>;\n}\n\nfunction sha256(text: string): string {\n return createHash('sha256').update(text, 'utf-8').digest('hex');\n}\n\nfunction defaultReadLocalFile(path: string): string | undefined {\n const resolved = resolve(expandHome(path));\n if (!existsSync(resolved)) return undefined;\n try {\n return readFileSync(resolved, 'utf-8');\n } catch {\n return undefined;\n }\n}\n\nasync function defaultFetchUpstream(url: string): Promise<string | undefined> {\n try {\n const res = await fetch(url);\n if (!res.ok) return undefined;\n return await res.text();\n } catch {\n return undefined;\n }\n}\n\n/** True for anything that looks like an `http(s)://` URL — routed to `fetchUpstream`; everything else routes to `readLocalFile`. */\nfunction isHttpUrl(value: string): boolean {\n return /^https?:\\/\\//i.test(value.trim());\n}\n\nfunction upstreamLabel(node: SkillMapNode | undefined, edge: { to: string }): string {\n return node?.url ?? node?.path ?? node?.name ?? node?.id ?? edge.to;\n}\n\n/**\n * Computes a drift row for every `fork-of` edge in the map. `from` is expected to be a local\n * `skill` node (must carry `path` + `contentHash`); `to` is expected to be the upstream\n * (`external`) node (must carry `url` or `path`). Tolerant of a schema that doesn't yet match\n * exactly — any edge whose endpoints can't be resolved to usable data yields a row instead of\n * throwing, so one malformed edge never aborts the whole drift run (mirrors `watch.ts`'s\n * per-root failure tolerance).\n */\nexport async function computeMapDrift(map: SkillMap, deps: MapDriftDeps = {}): Promise<MapDriftRow[]> {\n const readLocalFile = deps.readLocalFile ?? defaultReadLocalFile;\n const fetchUpstream = deps.fetchUpstream ?? defaultFetchUpstream;\n\n const rows: MapDriftRow[] = [];\n\n for (const edge of edgesOfType(map, 'fork-of')) {\n const localNode = nodeById(map, edge.from);\n const upstreamNode = nodeById(map, edge.to);\n const skillLabel = localNode?.name ?? localNode?.id ?? edge.from;\n const upstream = upstreamLabel(upstreamNode, edge);\n\n if (!localNode || !localNode.path || !localNode.contentHash) {\n rows.push({\n skill: skillLabel,\n upstream,\n status: 'local-missing',\n detail: !localNode\n ? `fork-of edge \"from\" node not found in map: ${edge.from}`\n : 'local skill node missing \"path\" or \"contentHash\" — cannot compare',\n });\n continue;\n }\n\n const localResolved = resolve(expandHome(localNode.path));\n if (!existsSync(localResolved)) {\n rows.push({ skill: skillLabel, upstream, status: 'local-missing', detail: `local path does not exist: ${localResolved}` });\n continue;\n }\n\n const upstreamLocation = upstreamNode?.url ?? upstreamNode?.path;\n if (!upstreamLocation) {\n rows.push({\n skill: skillLabel,\n upstream,\n status: 'upstream-unreachable',\n detail: 'upstream node has no \"url\" or \"path\" to fetch/read',\n });\n continue;\n }\n\n const upstreamText = isHttpUrl(upstreamLocation)\n ? await fetchUpstream(upstreamLocation)\n : readLocalFile(upstreamLocation);\n\n if (upstreamText === undefined) {\n rows.push({ skill: skillLabel, upstream, status: 'upstream-unreachable', detail: `could not fetch/read upstream: ${upstreamLocation}` });\n continue;\n }\n\n const upstreamHash = sha256(upstreamText);\n if (upstreamHash === localNode.contentHash) {\n rows.push({ skill: skillLabel, upstream, status: 'in-sync', detail: `contentHash matches (${upstreamHash.slice(0, 12)}…)` });\n } else {\n rows.push({\n skill: skillLabel,\n upstream,\n status: 'drifted',\n detail: `recorded ${localNode.contentHash.slice(0, 12)}…, upstream now hashes to ${upstreamHash.slice(0, 12)}…`,\n });\n }\n }\n\n return rows;\n}\n","/**\n * skill-forge — Pro module (source-available, not MIT). See LICENSE-COMMERCIAL.\n */\nimport { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from 'node:fs';\nimport { writeNewFile } from '../util.js';\nimport { dirname, join } from 'node:path';\nimport { auditConfiguredSet, sanitizeForMarkdown, type AuditReport } from '../audit.js';\nimport { summarizeAudit, writeAuditState, type AuditState } from '../auditState.js';\nimport { getHomeDir, loadConfig, type Config } from '../config.js';\nimport { UserError } from '../errors.js';\nimport { readAcceptedFindings } from '../findings.js';\nimport { runHandoff } from '../handoff.js';\nimport { listQuarantineIds } from '../install/quarantine.js';\nimport { proFeatureStatus } from '../license.js';\nimport { buildOrganizeReport, type OrganizeReport } from '../organize.js';\nimport { pendingQueueCount, readQueue, updateQueueEntry, type QueueEntry } from '../queue.js';\nimport { heldCount } from './held.js';\nimport { runWatch, type WatchReport } from './watch.js';\nimport type { Severity } from '../gate/scanShared.js';\n\n/**\n * `skill-forge routine` — the whole maintenance pipeline in one command, shaped for cron.\n *\n * Every other command answers one question; keeping a skill set healthy means running four of\n * them and correlating the output by hand, which is exactly the work nobody does on a schedule.\n * This runs audit + watch + organize against the ENGINES (not the commands), correlates them into\n * one summary and one report file, and exits with a code a scheduler can alert on.\n *\n * **Non-interactive by construction**: never prompts, so it cannot hang a scheduled job. It is\n * also the only command that can be scheduled AND write — so what it may write is enumerated and\n * fenced:\n * - always: the combined report + `audit-state.json` (both under ~/.skill-forge)\n * - `--housekeeping` only: prune expired quarantine sandboxes, close orphaned queue entries\n * - `--handoff` only: launch the configured agent\n * Housekeeping and handoff are OPT-IN. A default `skill-forge routine` in a crontab reports and\n * nothing else — an unattended job that deletes or spends tokens must be something you asked for\n * in the crontab line, not something you discover later.\n *\n * It never installs, promotes, or rejects anything: no candidate enters a skills root without a\n * human/agent decision, and a scheduled job is not that.\n */\n\nconst PRO_FEATURE_LABEL = 'the scheduled maintenance routine';\n\n/** Severity floor for `--fail-on`. `any` counts every finding; `none` never fails. */\nexport type FailOn = 'none' | 'low' | 'medium' | 'high' | 'critical' | 'any';\n\nconst SEVERITY_ORDER: Severity[] = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];\n\nexport interface RoutineOptions {\n json?: boolean;\n /** Skip every network check (passed through to watch). */\n offline?: boolean;\n skipWatch?: boolean;\n skipOrganize?: boolean;\n /** Exit 1 when a finding at or above this severity exists. Default 'none' — report, don't fail. */\n failOn?: FailOn;\n /** Bounded write actions: prune expired quarantine sandboxes, close orphaned queue entries. */\n housekeeping?: boolean;\n /** Age threshold in days for quarantine pruning (default 30). Only meaningful with --housekeeping. */\n pruneDays?: number;\n /** With --housekeeping: list what would be changed, change nothing. */\n dryRun?: boolean;\n /** Hand the written report to the configured agent (curation prompt). */\n handoff?: boolean;\n /** Report path override; defaults to ~/.skill-forge/reports/routine-<timestamp>.md. */\n report?: string;\n}\n\nexport interface HousekeepingAction {\n kind: 'prune-quarantine' | 'close-orphaned-queue-entry';\n target: string;\n reason: string;\n /**\n * Three states, not a boolean: \"not attempted because --dry-run\" and \"attempted and failed\" are\n * operationally very different facts for a cron consumer, and a boolean rendered them both as\n * \"would apply\" — telling an operator nothing was tried when a delete had actually failed.\n */\n outcome: 'applied' | 'would-apply' | 'failed';\n}\n\nexport interface RoutineResult {\n generatedAt: string;\n reportPath: string;\n audit: AuditState;\n watch?: { checked: number; moved: number; unknown: number; notices: string[] };\n organize?: { skills: number; roots: number; notices: string[] };\n pending: { queue: number; quarantine: number };\n housekeeping: HousekeepingAction[];\n /**\n * Store-read notices (v0.12) — e.g. a corrupt/missing accepted-findings store. Deliberately\n * NEVER a `degraded` step: the accepted-findings store fails OPEN (nothing gets suppressed on a\n * read failure — full reporting is already restored), so a corrupt store is cosmetic, not a\n * missed step. Paging a cron on it would train exactly the alert fatigue this field exists to\n * avoid.\n */\n notices: string[];\n /**\n * Steps that were requested but could not complete: a crashed drift/registry check, or a\n * housekeeping action that failed. Without this, `watch: undefined` from a crash is\n * indistinguishable from `--skip-watch`, and a scheduled run whose every housekeeping delete is\n * failing exits 0 forever — invisible to the cron that exists to notice.\n */\n degraded: string[];\n /** Populated when the run exits nonzero; the reason. */\n failure?: string;\n}\n\nfunction parseFailOnFloor(failOn: FailOn): Severity | undefined {\n if (failOn === 'none') return undefined;\n if (failOn === 'any') return 'LOW';\n return failOn.toUpperCase() as Severity;\n}\n\n/** True when the audit found anything at or above the `--fail-on` floor. */\nfunction exceedsFloor(audit: AuditState, failOn: FailOn): string | undefined {\n const floor = parseFailOnFloor(failOn);\n if (!floor) return undefined;\n const floorIndex = SEVERITY_ORDER.indexOf(floor);\n for (let i = SEVERITY_ORDER.length - 1; i >= floorIndex; i--) {\n const severity = SEVERITY_ORDER[i];\n const count = audit.findings[severity] ?? 0;\n if (count > 0) return `${count} ${severity} finding(s) at or above --fail-on ${failOn}`;\n }\n return undefined;\n}\n\nfunction resolveReportPath(override: string | undefined, generatedAt: string): string {\n if (override) return override;\n return join(getHomeDir(), 'reports', `routine-${generatedAt.replace(/[:.]/g, '-')}.md`);\n}\n\n\n/**\n * Housekeeping — deliberately the two most boring, most reversible-in-spirit actions available.\n *\n * Pruning only ever deletes a QUARANTINE sandbox, never anything in a skills root: quarantine is\n * a staging area by definition, and a sandbox nobody has resolved in `pruneDays` is abandoned. It\n * is still a delete, so it is opt-in, age-bounded, `--dry-run`-able, and every path is listed in\n * the report. Closing an orphaned queue entry only touches queue.json, and only when the skill it\n * points at is gone from disk — the decide pass for a skill that no longer exists cannot be done.\n */\nfunction runHousekeeping(config: Config, options: RoutineOptions): HousekeepingAction[] {\n const actions: HousekeepingAction[] = [];\n if (!options.housekeeping) return actions;\n\n const apply = options.dryRun !== true;\n const pruneDays = options.pruneDays ?? 30;\n const cutoff = Date.now() - pruneDays * 86_400_000;\n\n for (const id of listQuarantineIds(config.quarantineDir)) {\n const path = join(config.quarantineDir, id);\n let mtime: number;\n try {\n mtime = statSync(path).mtimeMs;\n } catch {\n continue; // vanished between listing and stat — nothing to prune\n }\n if (mtime >= cutoff) continue;\n const ageDays = Math.floor((Date.now() - mtime) / 86_400_000);\n if (apply) {\n try {\n rmSync(path, { recursive: true, force: true });\n } catch (err) {\n actions.push({ kind: 'prune-quarantine', target: path, reason: `prune failed: ${(err as Error).message}`, outcome: 'failed' });\n continue;\n }\n }\n actions.push({\n kind: 'prune-quarantine',\n target: path,\n reason: `held ${ageDays} days without a decision (threshold ${pruneDays})`,\n outcome: apply ? 'applied' : 'would-apply',\n });\n }\n\n let entries: QueueEntry[] = [];\n try {\n entries = readQueue().entries;\n } catch (err) {\n // Report it rather than returning silently: otherwise \"no orphaned entries closed\" is\n // indistinguishable from \"the orphan pass never ran\".\n actions.push({\n kind: 'close-orphaned-queue-entry',\n target: 'queue.json',\n reason: `queue unreadable, orphan pass skipped: ${(err as Error).message}`,\n outcome: 'failed',\n });\n return actions;\n }\n for (const entry of entries) {\n if (entry.status !== 'pending') continue;\n // Only an entry whose installed skill is GONE is orphaned. A pending entry whose skill is\n // still installed is simply undone work, and closing that would silently discard a decision.\n if (!entry.installedPath || existsSync(entry.installedPath)) continue;\n if (apply) {\n try {\n updateQueueEntry(entry.id, { status: 'dismissed' });\n } catch (err) {\n actions.push({\n kind: 'close-orphaned-queue-entry',\n target: entry.id,\n reason: `close failed: ${(err as Error).message}`,\n outcome: 'failed',\n });\n continue;\n }\n }\n actions.push({\n kind: 'close-orphaned-queue-entry',\n target: entry.id,\n reason: `installed path no longer exists: ${entry.installedPath}`,\n outcome: apply ? 'applied' : 'would-apply',\n });\n }\n\n return actions;\n}\n\nfunction renderRoutineMarkdown(result: RoutineResult, audit: AuditReport, watch?: WatchReport, organize?: OrganizeReport): string {\n const lines: string[] = [\n '# skill-forge routine report',\n '',\n `Generated: ${result.generatedAt}`,\n '',\n '## Summary',\n '',\n `- Skills: ${result.audit.skillCount} across ${audit.summary.rootCount} configured root(s)`,\n `- Findings: ${audit.summary.findingCounts.CRITICAL ?? 0} critical, ${audit.summary.findingCounts.HIGH ?? 0} high, ` +\n `${audit.summary.findingCounts.MEDIUM ?? 0} medium, ${audit.summary.findingCounts.LOW ?? 0} low`,\n `- Accepted: ${audit.summary.acceptedCount} (suppressed from the counts above; see Accepted findings)`,\n `- Overlap clusters: ${result.audit.overlapClusters.length}${result.audit.overlapLocked ? ' (overlap analysis was locked — not \"none found\")' : ''}`,\n `- Pending queue entries: ${result.pending.queue}`,\n `- Held in quarantine: ${result.pending.quarantine}`,\n ];\n\n if (watch) {\n lines.push(`- Drift: ${result.watch?.moved ?? 0} moved, ${result.watch?.unknown ?? 0} unknown, of ${result.watch?.checked ?? 0} tracked`);\n }\n if (organize) {\n lines.push(`- Capability registry: ${result.organize?.skills ?? 0} skill(s) across ${result.organize?.roots ?? 0} root(s)`);\n }\n\n if (result.notices.length > 0) {\n lines.push('', '## Notices', '');\n for (const item of result.notices) lines.push(`- ${sanitizeForMarkdown(item)}`);\n }\n\n if (result.degraded.length > 0) {\n lines.push('', '## Incomplete steps', '');\n for (const item of result.degraded) lines.push(`- ${sanitizeForMarkdown(item)}`);\n }\n\n if (result.housekeeping.length > 0) {\n lines.push('', '## Housekeeping', '');\n for (const action of result.housekeeping) {\n const label = action.outcome === 'applied' ? 'APPLIED' : action.outcome === 'failed' ? 'FAILED' : 'WOULD APPLY';\n lines.push(\n `- ${label} ${action.kind}: ` +\n `${sanitizeForMarkdown(action.target)} — ${sanitizeForMarkdown(action.reason)}`\n );\n }\n }\n\n if (audit.hygiene.length > 0) {\n lines.push('', '## Findings', '');\n for (const finding of audit.hygiene) {\n const fp = finding.fingerprint ? finding.fingerprint.slice(0, 12) : 'unknown';\n lines.push(\n `- **${finding.severity}** \\`${sanitizeForMarkdown(finding.rule)}\\` — ` +\n `${sanitizeForMarkdown(finding.target)}: ${sanitizeForMarkdown(finding.detail)} (fp ${fp})`\n );\n }\n }\n\n if (audit.accepted.length > 0) {\n lines.push('', '## Accepted findings', '');\n for (const entry of audit.accepted) {\n lines.push(\n `- **${entry.finding.severity}** \\`${sanitizeForMarkdown(entry.finding.rule)}\\` — ` +\n `${sanitizeForMarkdown(entry.finding.target)}: ${sanitizeForMarkdown(entry.reason)} (accepted ${sanitizeForMarkdown(entry.acceptedAt.slice(0, 10))})`\n );\n }\n }\n\n if (watch && watch.rows.some((r) => r.driftStatus === 'moved')) {\n lines.push('', '## Upstreams that moved', '');\n for (const row of watch.rows.filter((r) => r.driftStatus === 'moved')) {\n lines.push(`- ${sanitizeForMarkdown(row.name)} (${sanitizeForMarkdown(row.source)}) — ${sanitizeForMarkdown(row.detail)}`);\n }\n }\n\n lines.push('', '## Next steps', '');\n if (result.pending.queue > 0) lines.push('- `skill-forge ingest` — pending decide-pass entries');\n if (result.pending.quarantine > 0) lines.push('- `skill-forge promote <id>` / `skill-forge reject <id>` — held candidates');\n if (result.audit.overlapClusters.length > 0) lines.push('- `skill-forge audit --handoff` — consolidate the overlap clusters above');\n lines.push('');\n\n return lines.join('\\n');\n}\n\nexport async function routineCommand(options: RoutineOptions = {}): Promise<void> {\n const pro = proFeatureStatus(PRO_FEATURE_LABEL);\n const log = options.json ? console.error : console.log;\n if (pro.notice) log(`[skill-forge] ${pro.notice}`);\n if (!pro.enabled) return;\n\n if (options.pruneDays !== undefined && (!Number.isFinite(options.pruneDays) || options.pruneDays < 1)) {\n throw new UserError(`--prune-days must be a positive number (got \"${options.pruneDays}\")`);\n }\n if (options.dryRun && !options.housekeeping) {\n throw new UserError('--dry-run only applies to --housekeeping; without it `routine` already writes nothing but its own report');\n }\n\n const config = loadConfig();\n const generatedAt = new Date().toISOString();\n\n // v0.12: load human-accepted findings and pass them through — the engine stays pure (only takes\n // the already-loaded records). A corrupt/missing store fails OPEN (see src/findings.ts's module\n // doc): nothing is suppressed, and the read failure surfaces as a NOTICE, never as `degraded` —\n // full reporting is already restored, so it's cosmetic, not a missed step (see RoutineResult's\n // `notices` doc comment).\n const { records: accepted, notices: storeNotices } = readAcceptedFindings();\n for (const notice of storeNotices) log(`[skill-forge] ${notice}`);\n\n const audit = await auditConfiguredSet(config, { accepted });\n const auditState = summarizeAudit(audit);\n\n const degraded: string[] = [];\n\n let watch: WatchReport | undefined;\n if (!options.skipWatch) {\n try {\n watch = await runWatch(config, { offline: options.offline, json: true });\n } catch (err) {\n degraded.push(`drift check failed: ${(err as Error).message}`);\n log(`[skill-forge] drift check failed: ${(err as Error).message}`);\n }\n }\n\n let organize: OrganizeReport | undefined;\n if (!options.skipOrganize) {\n try {\n organize = buildOrganizeReport(config);\n } catch (err) {\n degraded.push(`capability registry failed: ${(err as Error).message}`);\n log(`[skill-forge] capability registry failed: ${(err as Error).message}`);\n }\n }\n\n const pending = pendingQueueCount();\n // A corrupt queue is a degraded run, not \"0 pending\": the queue is the record of promoted\n // skills still owed a verb, so a scheduled run must not report an empty backlog for it.\n if (!pending.readable) degraded.push('queue.json is unreadable — pending count is not trustworthy');\n\n const housekeeping = runHousekeeping(config, options);\n for (const action of housekeeping) {\n if (action.outcome === 'failed') degraded.push(`${action.kind} failed for ${action.target}: ${action.reason}`);\n }\n\n // Counted AFTER housekeeping so the report reflects the state the run leaves behind. Uses the\n // shared counter so an unreadable quarantine dir reports 0 instead of aborting a scheduled run.\n const quarantine = heldCount(config.quarantineDir);\n if (!quarantine.readable) degraded.push('quarantine dir could not be listed — held count is not trustworthy');\n\n const result: RoutineResult = {\n generatedAt,\n reportPath: resolveReportPath(options.report, generatedAt),\n audit: auditState,\n watch: watch\n ? {\n checked: watch.rows.length,\n moved: watch.rows.filter((r) => r.driftStatus === 'moved').length,\n // 'unreachable' and 'not-checked' both mean \"we do not know\" — only 'unchanged' is a clean bill.\n unknown: watch.rows.filter((r) => r.driftStatus === 'unreachable' || r.driftStatus === 'not-checked').length,\n notices: watch.notices,\n }\n : undefined,\n organize: organize\n ? { skills: organize.registry.count, roots: organize.registry.skillsRoots.length, notices: organize.notices }\n : undefined,\n pending: { queue: pending.count, quarantine: quarantine.count },\n housekeeping,\n notices: storeNotices,\n degraded,\n };\n\n // A step the operator ASKED for that could not run is always a nonzero exit, independent of\n // --fail-on: --fail-on grades what the audit FOUND, whereas a degraded run means the routine\n // did not actually do what was scheduled. Silently exiting 0 there defeats the point of cron.\n const failure = degraded.length > 0\n ? `${degraded.length} step(s) did not complete: ${degraded.join('; ')}`\n : exceedsFloor(auditState, options.failOn ?? 'none');\n if (failure) result.failure = failure;\n\n writeNewFile(result.reportPath, renderRoutineMarkdown(result, audit, watch, organize), '--report');\n try {\n // Reuses the summary computed above — recomputing it would be free to write but invites the\n // report and the persisted state drifting apart.\n writeAuditState({ ...auditState, reportPath: result.reportPath });\n } catch (err) {\n console.error(`[skill-forge] routine report written, but recording audit state failed: ${(err as Error).message}`);\n }\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log('[skill-forge] routine');\n console.log(` skills: ${auditState.skillCount}`);\n console.log(\n ` findings: ${auditState.findings.CRITICAL ?? 0} critical / ${auditState.findings.HIGH ?? 0} high / ` +\n `${auditState.findings.MEDIUM ?? 0} medium / ${auditState.findings.LOW ?? 0} low`\n );\n console.log(` accepted: ${auditState.acceptedCount ?? 0} (suppressed from counts; see report)`);\n if (result.watch) console.log(` drift: ${result.watch.moved} moved of ${result.watch.checked} tracked`);\n if (result.organize) console.log(` registry: ${result.organize.skills} skill(s)`);\n console.log(` pending: ${result.pending.queue} queue, ${result.pending.quarantine} quarantine`);\n for (const action of housekeeping) {\n const label = action.outcome === 'applied' ? 'housekeeping' : action.outcome === 'failed' ? 'FAILED' : 'would do';\n console.log(` ${label}: ${action.kind} ${action.target}${action.outcome === 'failed' ? ` — ${action.reason}` : ''}`);\n }\n for (const item of result.degraded) console.log(` INCOMPLETE: ${item}`);\n console.log(` report -> ${result.reportPath}`);\n }\n\n if (options.handoff) {\n runHandoff(config, result.reportPath, {\n promptAsset: 'curation-prompt.md',\n log: options.json ? console.error : console.log,\n });\n }\n\n if (failure) {\n console.error(`[skill-forge] routine: ${failure}`);\n process.exitCode = 1;\n }\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { realpathSync } from 'node:fs';\nimport { dirname, isAbsolute, relative, resolve as resolvePath } from 'node:path';\nimport { loadConfig, type Config } from '../config.js';\nimport { UserError } from '../errors.js';\nimport { runHandoff } from '../handoff.js';\nimport { proFeatureStatus } from '../license.js';\nimport { getQueuePath, readQueue, updateQueueEntry, type QueueEntry, type QueueStatus } from '../queue.js';\n\n/**\n * `skill-forge ingest` (Pro) — the queue-drain UX for pending ingestions (no `<source>` form).\n * Direct single-source ingestion is NOT this command — use `skill-forge add <source> --ingest`,\n * which runs the full quarantine/gate pipeline before optionally handing off.\n *\n * `--list` is read-only (no handoff, no writes). The no-arg form validates every pending entry's\n * recorded path before handing off ONCE to the configured agent with the bundled\n * `assets/ingest-prompt.md` and the queue file path — see src/handoff.ts. `queueClose` below is\n * the thin `skill-forge queue close <id> --status ...` wiring so an agent never hand-edits\n * queue.json directly.\n */\n\nconst PRO_FEATURE_LABEL = 'the pending-ingestion queue';\nconst VALID_STATUSES: QueueStatus[] = ['ingested', 'dismissed'];\n\nexport interface IngestOptions {\n list?: boolean;\n json?: boolean;\n}\n\nexport interface QueueCloseOptions {\n status?: string;\n}\n\ninterface ListRow {\n id: string;\n source: string;\n artifactType: 'skill' | 'mcp';\n safetyVerdict: QueueEntry['gate']['safetyVerdict'];\n createdAt: string;\n}\n\ninterface ExcludedEntry {\n entry: QueueEntry;\n reason: string;\n}\n\nexport interface ValidationResult {\n valid: QueueEntry[];\n excluded: ExcludedEntry[];\n}\n\nfunction requireProFeature(): void {\n const status = proFeatureStatus(PRO_FEATURE_LABEL);\n if (!status.enabled) {\n throw new UserError(\n status.notice ?? 'ingest is a Pro feature — set SKILL_FORGE_LICENSE or config.licenseKey to a valid license to unlock.'\n );\n }\n if (status.notice) console.error(`[skill-forge] ${status.notice}`);\n}\n\n/** `entr` + `y`/`ies` for `count`, e.g. `pluralizeEntries(1)` -> `'entry'`, `pluralizeEntries(2)` -> `'entries'`. */\nfunction pluralizeEntries(count: number): string {\n return `entr${count === 1 ? 'y' : 'ies'}`;\n}\n\nfunction toListRow(entry: QueueEntry): ListRow {\n return {\n id: entry.id,\n source: entry.source,\n artifactType: entry.artifactType ?? 'skill',\n safetyVerdict: entry.gate.safetyVerdict,\n createdAt: entry.createdAt,\n };\n}\n\nfunction printPendingList(pending: QueueEntry[], json?: boolean): void {\n if (json) {\n console.log(JSON.stringify({ entries: pending.map(toListRow) }, null, 2));\n return;\n }\n if (pending.length === 0) {\n console.log('[skill-forge] no pending queue entries.');\n return;\n }\n console.log(`[skill-forge] ${pending.length} pending ${pluralizeEntries(pending.length)}:`);\n for (const entry of pending) {\n const row = toListRow(entry);\n console.log(` ${row.id} ${row.source} (${row.artifactType}) verdict=${row.safetyVerdict} created=${row.createdAt}`);\n }\n}\n\n/** `realpathSync`, swallowing any failure (missing path, broken symlink, permission error) to null. */\nfunction realpathOrNull(p: string): string | null {\n try {\n return realpathSync(p);\n } catch {\n return null;\n }\n}\n\n/** True when `candidate` is `root` or a descendant of it, comparing already-canonicalized (realpath'd) paths. */\nfunction isUnderRoot(candidate: string, root: string): boolean {\n const rel = relative(root, candidate);\n return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));\n}\n\n/**\n * Validates that every pending entry's effective path (`installedPath` once promoted, else\n * `quarantinePath`) canonicalizes under one of the configured roots: the quarantine dir, a\n * configured skills root, or a configured MCP target file's directory. `queue.json` is a plain,\n * user-writable file — nothing stops a hand-edited or malicious entry from pointing anywhere on\n * disk, so this check runs before the handoff and every failure is reported. NOTE: the handoff\n * passes the queue FILE path, so excluded entries still exist inside it — the ingest prompt\n * instructs the agent to re-apply the same containment check per entry, and the terminal output\n * tells the user how to dismiss a failed entry. Entries that fail (path\n * missing, broken/cyclic symlink, or a realpath that escapes every configured root — e.g. a\n * symlink planted inside quarantineDir pointing outside it) are excluded with a reason rather\n * than blocking the rest of the drain.\n */\nexport function validatePendingEntries(entries: QueueEntry[], config: Config): ValidationResult {\n const quarantineRoot = realpathOrNull(resolvePath(config.quarantineDir));\n const skillsRoots = config.skillsRoots.map((r) => realpathOrNull(resolvePath(r))).filter((r): r is string => r !== null);\n const mcpTargetDirs = (config.mcpTargets ?? [])\n .map((f) => realpathOrNull(dirname(resolvePath(f))))\n .filter((r): r is string => r !== null);\n const allowedRoots = [quarantineRoot, ...skillsRoots, ...mcpTargetDirs].filter((r): r is string => r !== null);\n\n const valid: QueueEntry[] = [];\n const excluded: ExcludedEntry[] = [];\n\n for (const entry of entries) {\n const effectivePath = entry.installedPath ?? entry.quarantinePath;\n if (!effectivePath) {\n excluded.push({ entry, reason: 'no installedPath or quarantinePath recorded on the entry' });\n continue;\n }\n const real = realpathOrNull(resolvePath(effectivePath));\n if (!real) {\n excluded.push({ entry, reason: `path does not exist or could not be resolved: ${effectivePath}` });\n continue;\n }\n if (allowedRoots.length === 0 || !allowedRoots.some((root) => isUnderRoot(real, root))) {\n excluded.push({ entry, reason: `path escapes every configured root (quarantineDir/skillsRoots/mcpTargets): ${effectivePath}` });\n continue;\n }\n valid.push(entry);\n }\n\n return { valid, excluded };\n}\n\n/**\n * `skill-forge ingest [--list] [--json]` — see module doc comment. `--list` is read-only and\n * skipped from the Pro gate's write path but still Pro-gated (the queue itself is a Pro\n * artifact); the no-arg form gates first, then reads the queue, validates pending entries, and\n * hands off once via `runHandoff` with the queue file path as the handoff target.\n */\nexport async function ingest(options: IngestOptions = {}): Promise<void> {\n requireProFeature();\n\n const queue = readQueue();\n const pending = queue.entries.filter((e) => e.status === 'pending');\n\n if (options.list) {\n printPendingList(pending, options.json);\n return;\n }\n\n if (pending.length === 0) {\n console.log('[skill-forge] no pending queue entries to ingest.');\n return;\n }\n\n const config = loadConfig();\n const { valid, excluded } = validatePendingEntries(pending, config);\n\n if (excluded.length > 0) {\n console.error(`[skill-forge] excluded ${excluded.length} pending ${pluralizeEntries(excluded.length)} that failed path validation:`);\n for (const { entry, reason } of excluded) {\n console.error(` - ${entry.id} (${entry.source}): ${reason}`);\n }\n console.error(\n '[skill-forge] excluded entries stay in the queue — review each, then dismiss with: skill-forge queue close <id> --status dismissed'\n );\n }\n\n if (valid.length === 0) {\n console.log('[skill-forge] no pending entries passed path validation — nothing to hand off.');\n return;\n }\n\n console.log(`[skill-forge] ${valid.length} pending ${pluralizeEntries(valid.length)} ready for ingest:`);\n for (const entry of valid) {\n console.log(` - ${entry.id} ${entry.source} (${entry.artifactType ?? 'skill'})`);\n }\n\n runHandoff(config, getQueuePath(), { promptAsset: 'ingest-prompt.md' });\n}\n\n/**\n * `skill-forge queue close <id> --status ingested|dismissed` (Pro) — thin wiring over\n * `queue.ts`'s existing `updateQueueEntry` so an agent never hand-edits `queue.json` directly.\n * Refuses an unknown `id` or a status outside `ingested`/`dismissed` (never `pending` — closing\n * doesn't reopen an entry, and there's no \"close to pending\" use case).\n */\nexport async function queueClose(id: string, options: QueueCloseOptions = {}): Promise<void> {\n requireProFeature();\n\n const status = options.status;\n if (!status || !VALID_STATUSES.includes(status as QueueStatus)) {\n throw new UserError(`--status must be one of: ${VALID_STATUSES.join(', ')} (got ${status ? JSON.stringify(status) : 'nothing'})`);\n }\n\n // updateQueueEntry throws a plain Error on an unknown id — rewrap as UserError so a\n // typo'd id gets the standard `skill-forge: <message>` output, not \"unexpected error\".\n let updated;\n try {\n updated = updateQueueEntry(id, { status: status as QueueStatus });\n } catch (err) {\n throw new UserError(err instanceof Error ? err.message : String(err));\n }\n console.log(`[skill-forge] queue entry ${updated.id} closed -> status: ${updated.status}`);\n}\n","import { createHash, randomUUID } from 'node:crypto';\nimport { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs';\nimport { basename, dirname, isAbsolute, join, relative, resolve as resolvePath, sep } from 'node:path';\nimport { createInterface, type Interface } from 'node:readline/promises';\nimport { sanitizeForMarkdown } from '../audit.js';\nimport { loadConfig, type Config } from '../config.js';\nimport { UserError } from '../errors.js';\nimport { assertPlainSkillName, gatherRefineContext, inventoryAllScopes, resolveScopes, type ScopeOverrideInventory, type ScopeResolution } from '../refine/context.js';\nimport { runHandoff } from '../handoff.js';\nimport { proFeatureStatus } from '../license.js';\nimport {\n defaultRandomHex,\n generateRefinementId,\n isOverrideType,\n isPatchAction,\n isPatternStatus,\n isRefinementCategory,\n isRefinementStatus,\n patternCount,\n type CaptureScope,\n type OverrideType,\n type PatchAction,\n type Pattern,\n type Refinement,\n type RefinementCategory,\n} from '../refine/models.js';\nimport { applyPatchBlocks, previewDiff, renderConfigOverride, renderExtendFile, type ParsedPatchBlock } from '../refine/patch.js';\nimport { appendProvenance } from '../provenance.js';\nimport {\n createBackup,\n getBackupsDir,\n isValidBackupId,\n markGeneralized,\n readBackupManifest,\n readPatternsStore,\n readRefinementsStore,\n recordRefinement,\n writePatternsStore,\n type BackupManifest,\n type RecordRefinementResult,\n} from '../refine/store.js';\nimport { expandHome } from '../util.js';\n\n/**\n * `skill-forge refine` (Pro, thin MIT CLI wiring) — capture, apply, and\n * generalize improvements to installed skills from real usage feedback. See\n * `.claude/plans/refinement-merge.md` for the full design; this file adapts\n * commander's options shape into calls against `src/refine/models.ts`\n * (types/ids), `src/refine/store.ts` (persistence), `src/refine/patch.ts`\n * (pure patch engine), and `src/refine/context.ts` (scope resolution).\n * `src/cli.ts` (owned by a different lane) wires these exported functions to\n * commander subcommands: `capture` (the default action), `list`,\n * `patterns`, `promote <pattern-id>`, `which <skill>`, `rollback <backup-id>`.\n *\n * Amendment 1 (capture never mutates a base SKILL.md): `refineCaptureCommand`\n * writes ONLY override artifacts — `SKILL.patch.md` / `SKILL.extend.md` /\n * `skill-config.json`, or a verbatim override-file copy for `full`/`hook`/\n * `script` — at the chosen PROJECT scope (`local`/`shared`, never `user`).\n * `refinePromoteCommand` is the only function that ever writes to a\n * user-scope base `SKILL.md`, and only via `applyPatchBlocks` (patch.ts),\n * never a raw string edit.\n */\n\nconst PRO_FEATURE_LABEL = 'skill refinement';\nconst REFINE_PROMPT_ASSET = 'refine-prompt.md';\n\nfunction requireProFeature(): void {\n const status = proFeatureStatus(PRO_FEATURE_LABEL);\n if (!status.enabled) {\n throw new UserError(status.notice ?? 'refine is a Pro feature — set SKILL_FORGE_LICENSE or config.licenseKey to a valid license to unlock.');\n }\n if (status.notice) console.error(`[skill-forge] ${status.notice}`);\n}\n\nfunction isInteractive(json: boolean | undefined): boolean {\n return !json && Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);\n}\n\n/** Prints `payload` as a single JSON document when `json` is true; otherwise runs `renderText`. Centralizes the `--json`-vs-terminal branch repeated across every refine subcommand. */\nfunction emit(json: boolean | undefined, payload: unknown, renderText: () => void): void {\n if (json) {\n console.log(JSON.stringify(payload, null, 2));\n } else {\n renderText();\n }\n}\n\n/** Asks `question` and returns true on an explicit y/yes answer (default no). Mirrors `src/commands/audit.ts`'s confirm idiom. */\nasync function confirmYesNo(rl: Interface, question: string): Promise<boolean> {\n const answer = (await rl.question(question)).trim().toLowerCase();\n return answer === 'y' || answer === 'yes';\n}\n\n/**\n * Resolves whether a write may proceed: `--yes` always wins; otherwise an\n * interactive TTY prompt is required (Amendment \"explicit confirmation\n * before any write ... unless --yes\"). A non-interactive caller without\n * `--yes` gets a `UserError` rather than a silent write or a silent skip.\n */\nasync function confirmWrite(yes: boolean | undefined, json: boolean | undefined, prompt: string): Promise<boolean> {\n if (yes) return true;\n if (!isInteractive(json)) {\n throw new UserError('refusing to write without confirmation — pass --yes, or run interactively.');\n }\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n try {\n return await confirmYesNo(rl, prompt);\n } finally {\n rl.close();\n }\n}\n\nfunction resolveConfiguredRoot(root: string): string {\n return resolvePath(expandHome(root));\n}\n\nfunction isUnderRoot(candidate: string, root: string): boolean {\n const rel = relative(root, candidate);\n return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));\n}\n\n/**\n * Strips C0/C1 control characters (including ANSI escapes) from a diff before it ever reaches\n * `console.log` at a confirm-prompt site — a captured refinement's patch content, or an\n * installed skill's own SKILL.md, is untrusted text that could otherwise smuggle terminal\n * control sequences into the confirmation preview. Applies `audit.ts`'s `sanitizeForMarkdown`\n * per line (not to the whole multi-line string at once) so the diff's own `\\n` line breaks\n * survive — `patch.ts`'s `previewDiff` stays a pure, unsanitized function; sanitization is a\n * command-layer concern.\n */\nfunction sanitizeDiffForConsole(diff: string): string {\n return diff\n .split('\\n')\n .map((line) => sanitizeForMarkdown(line))\n .join('\\n');\n}\n\n// ---------------------------------------------------------------------------\n// capture\n// ---------------------------------------------------------------------------\n\nexport interface RefineCaptureOptions {\n skill?: string;\n category?: string;\n target?: string;\n overrideType?: string;\n action?: string;\n marker?: string;\n content?: string;\n contentFile?: string;\n expected?: string;\n actual?: string;\n example?: string;\n outcome?: string;\n rootCause?: string;\n patternId?: string;\n scope?: string;\n dryRun?: boolean;\n json?: boolean;\n yes?: boolean;\n handoff?: boolean;\n}\n\ninterface ValidatedCaptureOptions {\n skill: string;\n category: RefinementCategory;\n target: string;\n overrideType: OverrideType;\n action?: PatchAction;\n marker?: string;\n expected: string;\n actual: string;\n example?: string;\n outcome?: string;\n rootCause?: string;\n patternId?: string;\n scope: CaptureScope;\n dryRun: boolean;\n json: boolean;\n yes: boolean;\n handoff: boolean;\n}\n\nfunction resolveContentOption(options: { content?: string; contentFile?: string }): string | undefined {\n if (options.content !== undefined) return options.content;\n if (options.contentFile) return readFileSync(resolvePath(options.contentFile), 'utf-8');\n return undefined;\n}\n\nfunction validateCaptureOptions(options: RefineCaptureOptions): ValidatedCaptureOptions {\n if (!options.skill) throw new UserError('--skill is required');\n assertPlainSkillName(options.skill);\n if (!options.category || !isRefinementCategory(options.category)) {\n throw new UserError(`--category is required and must be one of: trigger, content, hook, tool, pattern, config, new`);\n }\n if (!options.target) throw new UserError('--target is required (heading-path selector or a short description for whole-file override types)');\n if (!options.overrideType || !isOverrideType(options.overrideType)) {\n throw new UserError('--override-type is required and must be one of: patch, extend, config, full, hook, script, new');\n }\n if (!options.expected) throw new UserError('--expected is required');\n if (!options.actual) throw new UserError('--actual is required');\n\n const scopeRaw = options.scope ?? 'local';\n if (scopeRaw !== 'local' && scopeRaw !== 'shared') {\n throw new UserError('--scope must be \"local\" or \"shared\"');\n }\n\n let action: PatchAction | undefined;\n if (options.overrideType === 'patch') {\n if (!options.action || !isPatchAction(options.action)) {\n throw new UserError(\n '--action is required for --override-type patch (one of: append, prepend, replace-section, insert-after, insert-before, delete-section)'\n );\n }\n action = options.action;\n }\n\n return {\n skill: options.skill,\n category: options.category,\n target: options.target,\n overrideType: options.overrideType,\n action,\n marker: options.marker,\n expected: options.expected,\n actual: options.actual,\n example: options.example,\n outcome: options.outcome,\n rootCause: options.rootCause,\n patternId: options.patternId,\n scope: scopeRaw,\n dryRun: options.dryRun ?? false,\n json: options.json ?? false,\n yes: options.yes ?? false,\n handoff: options.handoff ?? false,\n };\n}\n\n/** Renders `<!-- ACTION: <action> \"<marker>\" -->` / `<!-- TARGET: ... -->` text matching `patch.ts`'s `parsePatchFile` grammar exactly. */\nfunction renderPatchActionBlock(params: { action: PatchAction; marker?: string; target: string; content: string }): string {\n const lines: string[] = [];\n lines.push(params.marker ? `<!-- ACTION: ${params.action} \"${params.marker}\" -->` : `<!-- ACTION: ${params.action} -->`);\n lines.push(`<!-- TARGET: ${params.target} -->`);\n lines.push(params.content);\n return lines.join('\\n');\n}\n\ninterface RenderedArtifact {\n /** Path relative to the scope directory. */\n relPath: string;\n content: string;\n}\n\n/** Default relative filename per whole-file override type; overridable via `--marker`. */\nfunction defaultOverrideFilename(overrideType: 'full' | 'hook' | 'script'): string {\n switch (overrideType) {\n case 'full':\n return 'SKILL.md';\n case 'hook':\n return 'hooks/refinement.sh';\n case 'script':\n return 'scripts/refinement.sh';\n default: {\n const exhaustive: never = overrideType;\n throw new Error(`internal error: defaultOverrideFilename called for non-whole-file override type \"${exhaustive as string}\"`);\n }\n }\n}\n\n/**\n * Renders the override artifact content for every one of the seven override\n * types (plan Amendment 2). `existingContent` is the current on-disk content\n * at the destination path (empty string if none) — `patch`/`extend`/`new`\n * accumulate onto it rather than overwriting.\n */\nfunction renderCaptureArtifact(\n opts: ValidatedCaptureOptions,\n content: string | undefined,\n refinementId: string,\n generatedAt: string,\n existingContent: string\n): RenderedArtifact {\n switch (opts.overrideType) {\n case 'patch': {\n if (!content) throw new UserError('--content or --content-file is required for --override-type patch');\n const block = renderPatchActionBlock({ action: opts.action!, marker: opts.marker, target: opts.target, content });\n const preamble = `# SKILL.patch.md\\n# Patches for: ${opts.skill}\\n`;\n const base = existingContent.trim().length > 0 ? existingContent.trimEnd() : preamble.trimEnd();\n return { relPath: 'SKILL.patch.md', content: `${base}\\n\\n${block}\\n` };\n }\n case 'extend':\n case 'new': {\n if (!content) throw new UserError(`--content or --content-file is required for --override-type ${opts.overrideType}`);\n const rendered = renderExtendFile({ skillName: opts.skill, refinementId, generatedAt, targetSection: opts.target, content });\n const combined = existingContent.trim().length > 0 ? `${existingContent.trimEnd()}\\n\\n${rendered}` : rendered;\n return { relPath: 'SKILL.extend.md', content: combined };\n }\n case 'config': {\n if (!content) throw new UserError('--content or --content-file (JSON object) is required for --override-type config');\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch (err) {\n throw new UserError(`--content for --override-type config must be valid JSON: ${(err as Error).message}`);\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new UserError('--content for --override-type config must be a JSON object');\n }\n const rendered = renderConfigOverride({\n skillName: opts.skill,\n refinementId,\n generatedAt,\n config: parsed as Record<string, unknown>,\n });\n return { relPath: 'skill-config.json', content: rendered };\n }\n case 'full':\n case 'hook':\n case 'script': {\n if (!content) throw new UserError(`--content or --content-file is required for --override-type ${opts.overrideType}`);\n return { relPath: opts.marker && opts.marker.trim().length > 0 ? opts.marker.trim() : defaultOverrideFilename(opts.overrideType), content };\n }\n default: {\n const exhaustive: never = opts.overrideType;\n throw new UserError(`unknown override type \"${exhaustive as string}\"`);\n }\n }\n}\n\n/** Refuses a symlinked scope directory, then creates it (mkdir -p) and confirms the realpath has no symlink components AND stays contained under the project's own cwd. */\nfunction ensureScopeDirSafe(scopeDir: string, resolvedCwd: string): string {\n let lst;\n try {\n lst = lstatSync(scopeDir);\n } catch {\n lst = undefined;\n }\n if (lst?.isSymbolicLink()) {\n throw new UserError(`refusing to write under ${scopeDir}: scope directory is a symlink`);\n }\n mkdirSync(scopeDir, { recursive: true });\n const real = resolveConfiguredRoot(scopeDir);\n const lexical = resolvePath(scopeDir);\n // Best-effort: if the directory tree itself contains a symlinked component, realpath and the\n // lexical resolution diverge — refuse rather than silently writing through it.\n let realResolved: string;\n try {\n realResolved = realpathSync(scopeDir);\n } catch {\n realResolved = real;\n }\n if (realResolved !== lexical) {\n throw new UserError(`refusing to write under ${scopeDir}: path contains a symlink`);\n }\n if (!isUnderRoot(realResolved, resolveConfiguredRoot(resolvedCwd))) {\n throw new UserError(`refusing to write under ${scopeDir}: resolves outside the project root ${resolvedCwd}`);\n }\n return realResolved;\n}\n\n/** Refuses a symlinked destination file, containment-checks the resolved parent dir under the scope root, and writes atomically-enough for a single small file (direct write, mode 0600). */\nfunction writeOverrideFile(scopeDir: string, relPath: string, content: string, resolvedCwd: string): string {\n const realScopeDir = ensureScopeDirSafe(scopeDir, resolvedCwd);\n const destPath = join(scopeDir, relPath);\n const destDir = dirname(destPath);\n mkdirSync(destDir, { recursive: true });\n\n const realDestDir = realpathSync(destDir);\n if (!isUnderRoot(realDestDir, realScopeDir)) {\n throw new UserError(`refusing to write ${destPath}: resolves outside scope directory ${scopeDir}`);\n }\n\n let destLst;\n try {\n destLst = lstatSync(destPath);\n } catch {\n destLst = undefined;\n }\n if (destLst?.isSymbolicLink()) {\n throw new UserError(`refusing to write ${destPath}: destination is a symlink`);\n }\n\n writeFileSync(destPath, content, { mode: 0o600 });\n chmodSync(destPath, 0o600);\n return destPath;\n}\n\nexport async function refineCaptureCommand(rawOptions: RefineCaptureOptions = {}): Promise<void> {\n requireProFeature();\n const opts = validateCaptureOptions(rawOptions);\n\n const config = loadConfig();\n const cwd = process.cwd();\n const scopes = resolveScopes(config, cwd, opts.skill);\n const scopeDir = opts.scope === 'local' ? scopes.local.dir : scopes.shared.dir;\n\n const content = resolveContentOption(rawOptions);\n\n // Pre-generate the refinement id so it can be embedded in the rendered artifact (extend/config\n // renderers reference it) AND be the exact id recordRefinement writes — passed through below as\n // an explicit `id`, NOT as a constant `randomHex` closure. A constant closure would feed\n // `generateRefinementId`'s (and, worse, the unrelated `generatePatternId`'s) collision-retry\n // do-while loop a value that never changes, turning any id collision into an infinite hang\n // (merge-gate IMPORTANT 3). Safe to precompute here because nothing else touches the store\n // between this read and the recordRefinement call below (single-threaded CLI).\n const now = new Date();\n const existingIds = new Set(readRefinementsStore().records.map((r) => r.id));\n const refinementId = generateRefinementId(existingIds, now, defaultRandomHex);\n\n const preview = renderCaptureArtifactPreview(opts, content, refinementId, now.toISOString(), scopeDir);\n\n if (opts.dryRun) {\n emit(opts.json, { dryRun: true, path: preview.destPath, diff: preview.diff }, () => {\n console.log(`[skill-forge] refine capture — ${opts.overrideType} override for \"${sanitizeForMarkdown(opts.skill)}\" at ${opts.scope} scope`);\n console.log(sanitizeDiffForConsole(preview.diff));\n });\n return;\n }\n\n if (!opts.json) {\n console.log(`[skill-forge] refine capture — ${opts.overrideType} override for \"${sanitizeForMarkdown(opts.skill)}\" at ${opts.scope} scope`);\n console.log(sanitizeDiffForConsole(preview.diff));\n }\n\n const proceed = await confirmWrite(opts.yes, opts.json, `\\nWrite ${preview.destPath}? [y/N] `);\n if (!proceed) {\n emit(opts.json, { written: false, cancelled: true, path: preview.destPath }, () => {\n console.log('[skill-forge] capture cancelled — nothing written.');\n });\n return;\n }\n\n const writtenPath = writeOverrideFile(scopeDir, preview.relPath, preview.content, cwd);\n\n let result: RecordRefinementResult;\n try {\n result = recordRefinement(\n {\n skill: opts.skill,\n category: opts.category,\n overrideType: opts.overrideType,\n targetSection: opts.target,\n expectedBehavior: opts.expected,\n actualBehavior: opts.actual,\n example: opts.example,\n desiredOutcome: opts.outcome,\n rootCause: opts.rootCause,\n project: cwd,\n projectRoot: cwd,\n patchAction: opts.action,\n patchMarker: opts.marker,\n patchContent: content,\n status: 'applied',\n scope: opts.scope,\n patternId: opts.patternId,\n },\n { now, id: refinementId }\n );\n } catch (err) {\n throw new UserError(`wrote the override artifact at ${writtenPath}, but recording the refinement in the store failed: ${(err as Error).message}`);\n }\n\n emit(\n opts.json,\n { written: true, path: writtenPath, refinementId: result.refinement.id, patternId: result.pattern.id, patternBecameReady: result.patternBecameReady },\n () => {\n console.log(`[skill-forge] wrote ${writtenPath}`);\n console.log(`[skill-forge] recorded refinement ${result.refinement.id} -> pattern ${result.pattern.id} (status: ${result.pattern.status})`);\n if (result.patternBecameReady) {\n console.log(`[skill-forge] pattern ${result.pattern.id} is now READY (seen in a second project) — consider \\`skill-forge refine promote ${result.pattern.id}\\``);\n }\n }\n );\n\n if (opts.handoff) {\n runHandoff(config, writtenPath, { promptAsset: REFINE_PROMPT_ASSET, log: opts.json ? console.error : console.log });\n }\n}\n\n/** Composes the rendered artifact + diff preview against current on-disk content, without writing anything. */\nfunction renderCaptureArtifactPreview(\n opts: ValidatedCaptureOptions,\n content: string | undefined,\n refinementId: string,\n generatedAt: string,\n scopeDir: string\n): { relPath: string; content: string; destPath: string; diff: string } {\n // Peek at existing content for accumulation (patch/extend/new) without creating any directory.\n const probeRelPath = opts.overrideType === 'patch' ? 'SKILL.patch.md' : opts.overrideType === 'extend' || opts.overrideType === 'new' ? 'SKILL.extend.md' : undefined;\n const probePath = probeRelPath ? join(scopeDir, probeRelPath) : undefined;\n const existingContent = probePath && existsSync(probePath) ? readFileSync(probePath, 'utf-8') : '';\n\n const rendered = renderCaptureArtifact(opts, content, refinementId, generatedAt, existingContent);\n const relPath = rendered.relPath;\n const destPath = join(scopeDir, relPath);\n const before = existsSync(destPath) ? readFileSync(destPath, 'utf-8') : '';\n const diff = previewDiff(before, rendered.content, destPath);\n\n return { relPath, content: rendered.content, destPath, diff };\n}\n\n// ---------------------------------------------------------------------------\n// list / patterns\n// ---------------------------------------------------------------------------\n\nexport interface RefineListOptions {\n status?: string;\n skill?: string;\n project?: string;\n json?: boolean;\n}\n\nexport async function refineListCommand(options: RefineListOptions = {}): Promise<void> {\n requireProFeature();\n const { records } = readRefinementsStore();\n let rows: Refinement[] = records;\n if (options.skill) rows = rows.filter((r) => r.skill === options.skill);\n if (options.project) rows = rows.filter((r) => r.project === options.project || r.projectRoot === resolvePath(options.project!));\n if (options.status) {\n if (!isRefinementStatus(options.status)) {\n throw new UserError('--status must be one of: pending, applied, generalized');\n }\n rows = rows.filter((r) => r.status === options.status);\n }\n const filtered = Boolean(options.skill || options.project || options.status);\n\n emit(options.json, { records: rows }, () => {\n if (rows.length === 0) {\n if (filtered && records.length > 0) {\n console.log(`[skill-forge] no refinements match the given filter(s) (${records.length} total recorded).`);\n } else {\n console.log('[skill-forge] no refinements recorded.');\n }\n return;\n }\n console.log(`[skill-forge] ${rows.length} refinement(s):`);\n for (const r of rows) {\n console.log(\n ` ${r.id} ${sanitizeForMarkdown(r.skill)} [${r.category}/${r.overrideType}] status=${r.status} pattern=${r.patternId ?? '(none)'} ${r.createdAt}`\n );\n }\n });\n}\n\nexport interface RefinePatternsOptions {\n status?: string;\n skill?: string;\n project?: string;\n json?: boolean;\n}\n\nexport async function refinePatternsCommand(options: RefinePatternsOptions = {}): Promise<void> {\n requireProFeature();\n const { records } = readPatternsStore();\n let rows: Pattern[] = records;\n if (options.status) {\n if (!isPatternStatus(options.status)) {\n throw new UserError('--status must be one of: tracking, ready, generalized, dismissed');\n }\n rows = rows.filter((p) => p.status === options.status);\n }\n if (options.skill) rows = rows.filter((p) => p.affectedSkills.includes(options.skill!));\n if (options.project) {\n const resolvedProject = resolvePath(options.project);\n rows = rows.filter((p) => p.occurrences.some((o) => o.projectRealpath === resolvedProject));\n }\n const filtered = Boolean(options.skill || options.project || options.status);\n\n emit(options.json, { records: rows.map((p) => ({ ...p, count: patternCount(p) })) }, () => {\n if (rows.length === 0) {\n if (filtered && records.length > 0) {\n console.log(`[skill-forge] no patterns match the given filter(s) (${records.length} total recorded).`);\n } else {\n console.log('[skill-forge] no patterns recorded.');\n }\n return;\n }\n console.log(`[skill-forge] ${rows.length} pattern(s):`);\n for (const p of rows) {\n console.log(\n ` ${p.id} ${sanitizeForMarkdown(p.name)} status=${p.status} count=${patternCount(p)} skills=${p.affectedSkills.map(sanitizeForMarkdown).join(', ')}`\n );\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// which\n// ---------------------------------------------------------------------------\n\nexport interface RefineWhichOptions {\n userRoot?: string;\n json?: boolean;\n}\n\nfunction renderInventoryLine(row: ScopeOverrideInventory): string {\n const present = [\n row.skillMd ? 'SKILL.md' : undefined,\n row.patchMd ? 'SKILL.patch.md' : undefined,\n row.extendMd ? 'SKILL.extend.md' : undefined,\n row.configJson ? 'skill-config.json' : undefined,\n row.hooksDir ? 'hooks/' : undefined,\n row.scriptsDir ? 'scripts/' : undefined,\n ].filter((v): v is string => Boolean(v));\n return ` [${row.scope}] ${row.dir}${row.dirExists ? '' : ' (missing)'} — ${present.length > 0 ? present.join(', ') : '(no override files)'}`;\n}\n\nexport async function refineWhichCommand(skill: string, options: RefineWhichOptions = {}): Promise<void> {\n requireProFeature();\n if (!skill) throw new UserError('a skill name is required: skill-forge refine which <skill>');\n\n const config = loadConfig();\n const cwd = process.cwd();\n const scopes = resolveScopes(config, cwd, skill, options.userRoot);\n const inventory = inventoryAllScopes(scopes);\n\n emit(options.json, { scopes, inventory }, () => {\n console.log(`[skill-forge] refine which \"${sanitizeForMarkdown(skill)}\" — resolution order (highest precedence first):`);\n for (const row of inventory) console.log(renderInventoryLine(row));\n if (!scopes.user.resolved) {\n console.log(\n ` [user] ambiguous — candidates: ${scopes.user.candidates.map((c) => c.root).join(', ') || '(none configured)'} (pass --user-root to disambiguate)`\n );\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// promote\n// ---------------------------------------------------------------------------\n\nexport interface RefinePromoteOptions {\n force?: boolean;\n dryRun?: boolean;\n json?: boolean;\n yes?: boolean;\n userRoot?: string;\n}\n\ninterface PostWriteSidecar {\n schemaVersion: 1;\n files: { path: string; sha256: string }[];\n}\n\nfunction writePostWriteSidecar(backupId: string, files: { path: string; sha256: string }[]): void {\n const sidecar: PostWriteSidecar = { schemaVersion: 1, files };\n const dir = join(getBackupsDir(), backupId);\n mkdirSync(dir, { recursive: true });\n writeFileSync(join(dir, 'post.json'), JSON.stringify(sidecar, null, 2) + '\\n', { mode: 0o600 });\n}\n\ninterface PostWriteSidecarResult {\n hashes: Map<string, string>;\n /** `missing`/`unreadable`/`corrupt` all mean rollback cannot verify the current on-disk state\n * matches what was promoted — the caller (`refineRollbackCommand`) must refuse without\n * `--force` rather than silently treating \"no data\" the same as \"no mismatch\". `hashes` is\n * always empty for every non-`ok` status (never partially populated), so a `--force` restore\n * under corruption skips hash verification uniformly for every file rather than for some. */\n status: 'ok' | 'missing' | 'unreadable' | 'corrupt';\n}\n\nfunction readPostWriteSidecar(backupId: string): PostWriteSidecarResult {\n const path = join(getBackupsDir(), backupId, 'post.json');\n if (!existsSync(path)) return { hashes: new Map(), status: 'missing' };\n\n // Split from JSON.parse/population below: a real read error (permissions, I/O) is a distinct\n // condition from a readable-but-malformed file, and gets its own status/message rather than\n // being mislabeled 'corrupt'.\n let raw: string;\n try {\n raw = readFileSync(path, 'utf-8');\n } catch {\n return { hashes: new Map(), status: 'unreadable' };\n }\n\n const hashes = new Map<string, string>();\n try {\n const parsed = JSON.parse(raw) as PostWriteSidecar;\n for (const f of parsed.files ?? []) {\n if (typeof f !== 'object' || f === null || typeof f.path !== 'string' || typeof f.sha256 !== 'string') {\n throw new Error('malformed post.json files entry');\n }\n hashes.set(f.path, f.sha256);\n }\n } catch {\n // Reset rather than returning whatever the loop partially populated before the throw — a\n // files array with one bad entry among otherwise-good ones must not leave hashes half full.\n return { hashes: new Map(), status: 'corrupt' };\n }\n return { hashes, status: 'ok' };\n}\n\nexport async function refinePromoteCommand(patternId: string, options: RefinePromoteOptions = {}): Promise<void> {\n requireProFeature();\n if (!patternId) throw new UserError('a pattern id is required: skill-forge refine promote <pattern-id>');\n\n const patternsStore = readPatternsStore();\n const pattern = patternsStore.records.find((p) => p.id === patternId);\n if (!pattern) throw new UserError(`no pattern with id: ${patternId}`);\n\n if (pattern.status === 'generalized' && !options.force) {\n throw new UserError(`pattern ${patternId} is already generalized (backup ${pattern.generalizedBackupId ?? '(unknown)'}) — pass --force to re-promote.`);\n }\n if (pattern.status !== 'ready' && pattern.status !== 'generalized' && !options.force) {\n throw new UserError(`pattern ${patternId} is not ready (status: ${pattern.status}) — pass --force to promote anyway.`);\n }\n\n const skill = pattern.affectedSkills[0];\n if (!skill) throw new UserError(`pattern ${patternId} has no affected skills recorded`);\n // Defense-in-depth: a poisoned/hand-edited patterns.json could carry a traversal skill name —\n // resolveScopes() below guards this too, but checking it here first gives a clearer error tied\n // to the pattern rather than a generic scope-resolution failure.\n assertPlainSkillName(skill);\n\n const config = loadConfig();\n const cwd = process.cwd();\n const scopes = resolveScopes(config, cwd, skill, options.userRoot);\n if (!scopes.user.resolved) {\n throw new UserError(\n `cannot resolve a single user scope for skill \"${skill}\" — candidates: ${scopes.user.candidates.map((c) => c.root).join(', ') || '(none configured)'}. Pass --user-root to disambiguate.`\n );\n }\n const skillDir = scopes.user.dir;\n\n // Containment check (mirrors writeOverrideFile's isUnderRoot check): the resolved skill\n // directory must actually live under the resolved user-scope root before any read/write.\n const resolvedUserRoot = resolveConfiguredRoot(scopes.user.root);\n let realUserRoot: string;\n try {\n realUserRoot = realpathSync(resolvedUserRoot);\n } catch {\n realUserRoot = resolvedUserRoot;\n }\n let realSkillDir: string;\n try {\n realSkillDir = realpathSync(skillDir);\n } catch {\n realSkillDir = resolvePath(skillDir);\n }\n if (!isUnderRoot(realSkillDir, realUserRoot)) {\n throw new UserError(`refusing to promote: resolved skill directory ${skillDir} is outside the configured user scope root ${scopes.user.root}`);\n }\n\n const skillMdPath = join(skillDir, 'SKILL.md');\n if (!existsSync(skillMdPath)) {\n throw new UserError(`user-scope SKILL.md not found at ${skillMdPath} — refusing to promote against a missing base skill.`);\n }\n\n const refinementsStore = readRefinementsStore();\n const patternRefinements = refinementsStore.records.filter((r) => pattern.refinementIds.includes(r.id));\n const representative = patternRefinements[patternRefinements.length - 1];\n if (!representative) throw new UserError(`pattern ${patternId} has no recorded refinements to promote`);\n\n let block: ParsedPatchBlock;\n if (representative.overrideType === 'patch' && representative.patchAction && representative.patchContent) {\n block = { action: representative.patchAction, marker: representative.patchMarker, target: representative.targetSection, content: representative.patchContent };\n } else if ((representative.overrideType === 'extend' || representative.overrideType === 'new') && representative.patchContent) {\n block = { action: 'append', target: representative.targetSection, content: representative.patchContent };\n } else {\n throw new UserError(\n `pattern ${patternId}'s representative refinement is a '${representative.overrideType}' override — promote currently only materializes patch/extend/new-type patterns against the user-scope SKILL.md; other override types must be merged manually.`\n );\n }\n\n const before = readFileSync(skillMdPath, 'utf-8');\n const after = applyPatchBlocks(before, [block]);\n const diff = previewDiff(before, after, skillMdPath);\n\n if (options.dryRun) {\n emit(options.json, { dryRun: true, path: skillMdPath, diff }, () => {\n console.log(`[skill-forge] refine promote ${patternId} -> ${skillMdPath}`);\n console.log(sanitizeDiffForConsole(diff));\n });\n return;\n }\n\n if (!options.json) {\n console.log(`[skill-forge] refine promote ${patternId} -> ${skillMdPath}`);\n console.log(sanitizeDiffForConsole(diff));\n }\n\n const proceed = await confirmWrite(options.yes, options.json, `\\nPromote pattern ${patternId} into ${skillMdPath}? [y/N] `);\n if (!proceed) {\n emit(options.json, { promoted: false, cancelled: true }, () => {\n console.log('[skill-forge] promote cancelled — nothing written.');\n });\n return;\n }\n\n const backup: BackupManifest = createBackup([skillMdPath]);\n\n const tempPath = `${skillMdPath}.tmp-${randomUUID()}`;\n try {\n writeFileSync(tempPath, after, 'utf-8');\n renameSync(tempPath, skillMdPath);\n } catch (err) {\n rmSync(tempPath, { force: true });\n throw err;\n }\n\n try {\n const postSha256 = createHash('sha256').update(readFileSync(skillMdPath)).digest('hex');\n writePostWriteSidecar(backup.id, [{ path: skillMdPath, sha256: postSha256 }]);\n markGeneralized(patternId, backup.id);\n const skillsRoot = resolveConfiguredRoot(scopes.user.root);\n appendProvenance(\n {\n name: skill,\n source: `skill-forge refine (${patternId})`,\n verb: 'DEFER',\n target: skillMdPath,\n took: `applied a ${block.action} patch materializing pattern ${patternId}`,\n notes: `generalized from ${patternId} via skill-forge refine`,\n },\n skillsRoot\n );\n } catch (err) {\n throw new UserError(\n `the user-scope skill was updated at ${skillMdPath} (backup ${backup.id}), but recording post-write verification and/or pattern/provenance state failed: ${(err as Error).message}`\n );\n }\n\n emit(options.json, { promoted: true, path: skillMdPath, backupId: backup.id }, () => {\n console.log(`[skill-forge] promoted pattern ${patternId} -> ${skillMdPath} (backup: ${backup.id})`);\n });\n}\n\n// ---------------------------------------------------------------------------\n// rollback\n// ---------------------------------------------------------------------------\n\nexport interface RefineRollbackOptions {\n force?: boolean;\n json?: boolean;\n yes?: boolean;\n}\n\n/**\n * Containment-checks a candidate backup dir under the real backups root before it's ever used —\n * `backupId` has already passed {@link isValidBackupId}'s strict format check (which alone\n * already excludes `/`, `..`, etc.), but this realpath check is defense-in-depth against the\n * backups directory itself resolving somewhere unexpected (e.g. a symlinked SKILL_FORGE_HOME).\n * Tolerates a not-yet-existing backup dir — falls through so {@link readBackupManifest}'s own\n * \"no backup with id\" error fires with its normal message.\n */\nfunction assertBackupDirContained(backupId: string): void {\n const backupsDir = getBackupsDir();\n if (!existsSync(backupsDir)) return;\n const realBackupsDir = realpathSync(backupsDir);\n const backupDir = join(backupsDir, backupId);\n if (!existsSync(backupDir)) return;\n const realBackupDir = realpathSync(backupDir);\n if (!isUnderRoot(realBackupDir, realBackupsDir)) {\n throw new UserError(`refusing rollback: backup id \"${backupId}\" resolves outside the backups directory`);\n }\n}\n\n/**\n * Resolves the realpath of the deepest EXISTING ancestor of `path`, then reattaches the\n * (lexically resolved, not-yet-existing) remainder — giving a best-effort \"where would this\n * path actually land\" answer even when `path` itself doesn't exist yet (e.g. a restore target\n * that was deleted, or a tombstone). If `path` itself exists (regular file OR symlink),\n * `realpathSync` resolves it fully, which is intentional here: a symlink leaf's realpath is its\n * OUTSIDE target, so the containment check below correctly rejects it as a second, independent\n * layer of defense behind the explicit `lstatSync`-based symlink refusal in\n * {@link assertNoSymlinkLeaves}.\n */\nfunction resolveTargetRealPath(path: string): string {\n let lexicalAncestor = resolvePath(path);\n while (true) {\n try {\n const real = realpathSync(lexicalAncestor);\n const remainder = relative(lexicalAncestor, resolvePath(path));\n return remainder === '' ? real : join(real, remainder);\n } catch {\n const parent = dirname(lexicalAncestor);\n if (parent === lexicalAncestor) return resolvePath(path);\n lexicalAncestor = parent;\n }\n }\n}\n\n/**\n * Refuses a rollback whose manifest references any file path outside the currently configured\n * `skillsRoots` — a manifest is only ever written by `createBackup` against paths this CLI itself\n * chose, but a hand-edited or otherwise tampered `manifest.json` could point anywhere on disk.\n * Aborts the whole rollback (restoring nothing) the moment one offending path is found.\n *\n * Containment is checked against the fully-resolved leaf via {@link resolveTargetRealPath} (the\n * deepest existing ancestor's realpath + the remaining lexical path), not just `dirname`'s\n * realpath — a manifest path whose PARENT directory legitimately realpaths inside a configured\n * root, but whose LEAF is itself a symlink pointing outside every root, is caught here too. This\n * is defense-in-depth: {@link assertNoSymlinkLeaves}'s explicit `lstatSync` check is the primary\n * gate for a symlink leaf (refused outright with a clearer error), not this containment check.\n */\nfunction assertManifestPathsWithinRoots(manifest: BackupManifest, config: Config): void {\n if (config.skillsRoots.length === 0) {\n throw new UserError(`refusing rollback ${manifest.id}: no skillsRoots are configured — cannot verify the backup's file paths are within a trusted skills root.`);\n }\n const roots = config.skillsRoots.map((root) => {\n const resolved = resolveConfiguredRoot(root);\n try {\n return realpathSync(resolved);\n } catch {\n return resolved;\n }\n });\n\n for (const file of manifest.files) {\n const resolvedTarget = resolveTargetRealPath(file.path);\n const withinAnyRoot = roots.some((root) => isUnderRoot(resolvedTarget, root));\n if (!withinAnyRoot) {\n throw new UserError(`refusing rollback ${manifest.id}: backup manifest references a path outside configured skills roots — ${file.path}`);\n }\n }\n}\n\n/**\n * Refuses the whole rollback (before any restore/delete is attempted) if any manifest file path\n * currently exists on disk as a symlink. A manifest is only ever written by `createBackup`\n * against paths this CLI itself chose as real files/tombstones, so a symlink now sitting at one\n * of those paths means something else replaced it after the backup was taken — following it\n * (write) or blindly unlinking it (delete) would act on attacker-controlled state. Mirrors the\n * exact lstat-symlink-refusal pattern `writeOverrideFile`/`ensureScopeDirSafe` already use on the\n * capture path. Applied uniformly to both restore (`existed: true`) and tombstone/delete\n * (`existed: false`) entries — deleting an unexpected symlink at a tombstone path is anomalous\n * and should abort rather than silently unlink it.\n */\nfunction assertNoSymlinkLeaves(manifest: BackupManifest): void {\n for (const file of manifest.files) {\n let lst;\n try {\n lst = lstatSync(file.path);\n } catch {\n lst = undefined;\n }\n if (lst?.isSymbolicLink()) {\n throw new UserError(`refusing rollback ${manifest.id}: backup manifest path is currently a symlink — refusing to follow or overwrite it: ${file.path}`);\n }\n }\n}\n\n/**\n * Finds the configured skillsRoot that lexically contains `filePath` (string comparison against\n * `resolveConfiguredRoot(root)`, no filesystem access) and returns both that lexical root and its\n * realpath (falling back to the lexical form if the root itself doesn't exist). Lexical, not\n * filesystem-resolved, containment is deliberate here: {@link walkRealComponents} is the function\n * responsible for safely resolving everything *below* the root, one component at a time — using a\n * filesystem-resolving lookup (like {@link resolveTargetRealPath}) to pick the root in the first\n * place would reintroduce the exact TOCTOU this module exists to close.\n */\nfunction findLexicalRoot(filePath: string, config: Config): { rootLexical: string; rootReal: string } {\n const lexicalTarget = resolvePath(filePath);\n for (const root of config.skillsRoots) {\n const rootLexical = resolveConfiguredRoot(root);\n const rel = relative(rootLexical, lexicalTarget);\n if (rel !== '' && (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel))) continue;\n let rootReal: string;\n try {\n rootReal = realpathSync(rootLexical);\n } catch {\n rootReal = rootLexical;\n }\n return { rootLexical, rootReal };\n }\n throw new UserError(`refusing to operate on ${filePath}: does not resolve lexically under any configured skills root`);\n}\n\ninterface ComponentWalkResult {\n /** Real, symlink-free parent directory of the leaf — undefined iff an intermediate component was missing and `createMissing` was false. */\n parentReal: string | undefined;\n leafComponent: string;\n rootReal: string;\n}\n\n/**\n * The authoritative write-time gate behind {@link safeRestoreWrite} and {@link safeDeleteTombstone}.\n * Walks from the containing skillsRoot's verified realpath down through `filePath`'s intermediate\n * directory components ONE AT A TIME, `lstatSync`-checking each immediately before creating or\n * descending into it — never a single filesystem call (like `realpathSync` or `mkdirSync(...,\n * {recursive: true})`) that could resolve or traverse through a symlink planted at any level after\n * `assertManifestPathsWithinRoots`'s preflight ran (e.g. during the confirm-prompt's await). Aborts\n * with `UserError` the instant a component is found to be a symlink, or exists but isn't a\n * directory. A missing intermediate component is not itself an error: with `createMissing: true`\n * (restore) it is created as a fresh real directory and the walk continues; with `createMissing:\n * false` (delete) the walk stops and `parentReal` comes back `undefined`, telling the caller there\n * is nothing real at that path to delete.\n */\nfunction walkRealComponents(filePath: string, config: Config, createMissing: boolean): ComponentWalkResult {\n const { rootLexical, rootReal } = findLexicalRoot(filePath, config);\n const relPath = relative(rootLexical, resolvePath(filePath));\n const components = relPath.split(sep).filter((c) => c.length > 0);\n if (components.length === 0) {\n throw new UserError(`refusing to operate on ${filePath}: resolves to the skills root itself`);\n }\n const leafComponent = components[components.length - 1];\n const dirComponents = components.slice(0, -1);\n\n let current: string | undefined = rootReal;\n for (const component of dirComponents) {\n if (current === undefined) break;\n const next = join(current, component);\n let lst;\n try {\n lst = lstatSync(next);\n } catch {\n lst = undefined;\n }\n if (lst === undefined) {\n if (!createMissing) {\n current = undefined;\n continue;\n }\n mkdirSync(next);\n current = next;\n continue;\n }\n if (lst.isSymbolicLink()) {\n throw new UserError(`refusing to operate on ${filePath}: intermediate path component is a symlink — ${next}`);\n }\n if (!lst.isDirectory()) {\n throw new UserError(`refusing to operate on ${filePath}: intermediate path component is not a directory — ${next}`);\n }\n current = next;\n }\n\n return { parentReal: current, leafComponent, rootReal };\n}\n\n/**\n * Restores a single manifest entry. This is the sole place a rollback restore ever touches disk:\n * it resolves the real, symlink-free parent directory via {@link walkRealComponents} (creating any\n * missing intermediate directories one level at a time), refuses a leaf that is now a symlink,\n * re-asserts containment under the root as a final belt-and-suspenders check, then writes through a\n * temp file in that verified-real parent followed by an atomic rename — mirroring the\n * tempPath/renameSync + rmSync-on-failure pattern `refinePromoteCommand`'s SKILL.md write already\n * uses. There is no `await` anywhere between the component walk and the rename.\n *\n * Known residual limitation: like any path-string-based (non-fd-relative) filesystem check, this\n * cannot fully close a concurrent-attacker sub-millisecond race in which an already-verified\n * ancestor directory is swapped for a symlink in the gap between its `lstatSync` and the final\n * `renameSync`. Node's `fs` API exposes no `openat`/`O_NOFOLLOW`-relative primitive to eliminate\n * it. This requires an attacker with concurrent write access to the same skills root the process\n * is operating on — who already has comparable ability to tamper with the target files directly.\n */\nfunction safeRestoreWrite(filePath: string, contentBuffer: Buffer, mode: number, config: Config): void {\n const { parentReal, leafComponent, rootReal } = walkRealComponents(filePath, config, true);\n if (parentReal === undefined) {\n throw new UserError(`refusing to restore ${filePath}: could not establish a real parent directory`);\n }\n const finalPath = join(parentReal, leafComponent);\n if (!isUnderRoot(finalPath, rootReal)) {\n throw new UserError(`refusing to restore ${filePath}: resolves outside root ${rootReal}`);\n }\n\n let leafLst;\n try {\n leafLst = lstatSync(finalPath);\n } catch {\n leafLst = undefined;\n }\n if (leafLst?.isSymbolicLink()) {\n throw new UserError(`refusing to restore ${filePath}: it is now a symlink — refusing to follow or overwrite it.`);\n }\n\n const tempPath = `${finalPath}.tmp-${randomUUID()}`;\n try {\n writeFileSync(tempPath, contentBuffer);\n chmodSync(tempPath, mode);\n renameSync(tempPath, finalPath);\n } catch (err) {\n rmSync(tempPath, { force: true });\n throw err;\n }\n}\n\n/**\n * Deletes a single tombstone manifest entry (a file that didn't exist when the backup was taken).\n * Uses the same {@link walkRealComponents} component-by-component symlink refusal as\n * {@link safeRestoreWrite} — without it, a planted symlinked intermediate directory would let\n * `rmSync` traverse through it and delete a file outside every skillsRoot. A missing intermediate\n * component (or a missing leaf) means there is genuinely nothing to delete — not an error.\n */\nfunction safeDeleteTombstone(filePath: string, config: Config): void {\n const { parentReal, leafComponent, rootReal } = walkRealComponents(filePath, config, false);\n if (parentReal === undefined) return;\n const finalPath = join(parentReal, leafComponent);\n if (!isUnderRoot(finalPath, rootReal)) {\n throw new UserError(`refusing to delete ${filePath}: resolves outside root ${rootReal}`);\n }\n\n let leafLst;\n try {\n leafLst = lstatSync(finalPath);\n } catch {\n leafLst = undefined;\n }\n if (leafLst === undefined) return;\n if (leafLst.isSymbolicLink()) {\n throw new UserError(`refusing to delete ${filePath}: it is now a symlink — refusing to follow or overwrite it.`);\n }\n rmSync(finalPath, { force: true });\n}\n\nexport async function refineRollbackCommand(backupId: string, options: RefineRollbackOptions = {}): Promise<void> {\n requireProFeature();\n if (!backupId) throw new UserError('a backup id is required: skill-forge refine rollback <backup-id>');\n if (!isValidBackupId(backupId)) {\n throw new UserError(`invalid backup id \"${backupId}\" — expected the format produced by \\`skill-forge refine promote\\` (e.g. 20260720T142305Z-a1b2)`);\n }\n assertBackupDirContained(backupId);\n\n const manifest = readBackupManifest(backupId);\n\n const config = loadConfig();\n assertManifestPathsWithinRoots(manifest, config);\n assertNoSymlinkLeaves(manifest);\n\n const warnings: string[] = [];\n const sidecar = readPostWriteSidecar(backupId);\n if (sidecar.status !== 'ok') {\n const reason = sidecar.status;\n if (!options.force) {\n throw new UserError(\n `refusing rollback ${backupId}: post-write verification data (post.json) is ${reason} — cannot confirm the current file state matches what was promoted. Pass --force to override.`\n );\n }\n warnings.push(`post-write verification data for backup ${backupId} is ${reason} — restoring without hash verification (--force).`);\n }\n\n const mismatches: string[] = [];\n for (const file of manifest.files) {\n const expected = sidecar.hashes.get(file.path);\n if (expected === undefined) continue;\n const currentSha256 = existsSync(file.path) ? createHash('sha256').update(readFileSync(file.path)).digest('hex') : null;\n if (currentSha256 !== expected) mismatches.push(file.path);\n }\n if (mismatches.length > 0 && !options.force) {\n throw new UserError(\n `refusing rollback ${backupId}: current file(s) differ from the state recorded right after promote — ${mismatches.join(', ')} (pass --force to override)`\n );\n }\n\n const fileList = manifest.files.map((file) => ` ${file.existed ? 'restore' : 'delete'} ${file.path}`).join('\\n');\n if (!options.json) {\n console.log(`[skill-forge] refine rollback ${backupId} — restoring ${manifest.files.length} file(s):`);\n console.log(fileList);\n for (const w of warnings) console.log(`[skill-forge] warning: ${w}`);\n }\n\n const proceed = await confirmWrite(options.yes, options.json, `\\nRoll back backup ${backupId}? Affected files:\\n${fileList}\\n[y/N] `);\n if (!proceed) {\n emit(options.json, { rolledBack: false, cancelled: true, backupId }, () => {\n console.log('[skill-forge] rollback cancelled — nothing restored.');\n });\n return;\n }\n\n const restored: string[] = [];\n for (const file of manifest.files) {\n try {\n // safeRestoreWrite/safeDeleteTombstone are the authoritative write-time gate: they walk\n // every intermediate path component and lstat the leaf immediately before touching disk,\n // with no intervening await, closing the TOCTOU window between the preflight checks above\n // (assertManifestPathsWithinRoots, assertNoSymlinkLeaves) and this loop — e.g. a confirm\n // prompt that waited on user input in between.\n if (!file.existed) {\n safeDeleteTombstone(file.path, config);\n } else {\n safeRestoreWrite(file.path, Buffer.from(file.contentBase64 ?? '', 'base64'), file.mode ?? 0o644, config);\n }\n restored.push(file.path);\n } catch (err) {\n const remaining = manifest.files.slice(restored.length + 1).map((f) => f.path);\n throw new UserError(\n `rollback ${backupId} failed partway through: restored ${restored.length} of ${manifest.files.length} file(s) (${restored.join(', ') || '(none)'}), then failed on ${file.path}: ${(err as Error).message}. NOT restored: ${[file.path, ...remaining].join(', ')}.`\n );\n }\n }\n\n const patternsStore = readPatternsStore();\n const pattern = patternsStore.records.find((p) => p.generalizedBackupId === backupId);\n if (pattern) {\n pattern.status = 'ready';\n pattern.generalizedAt = undefined;\n pattern.generalizedBackupId = undefined;\n writePatternsStore(patternsStore);\n }\n\n emit(options.json, { rolledBack: true, backupId, patternReset: pattern?.id, warnings }, () => {\n console.log(`[skill-forge] rollback complete for ${backupId}${pattern ? ` (pattern ${pattern.id} reset to ready)` : ''}`);\n for (const w of warnings) console.log(`[skill-forge] warning: ${w}`);\n });\n}\n\n// Re-exported for src/cli.ts wiring convenience and for tests that want the full context bundle\n// without re-deriving it (e.g. a future `refine context` debugging subcommand).\nexport { gatherRefineContext, resolveScopes, inventoryAllScopes };\nexport type { Config, ScopeResolution };\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { existsSync, realpathSync, statSync } from 'node:fs';\nimport { isAbsolute, join, relative, resolve as resolvePath } from 'node:path';\nimport type { Config } from '../config.js';\nimport { UserError } from '../errors.js';\nimport { defaultExec, type ExecFn } from '../install/quarantine.js';\nimport { expandHome } from '../util.js';\nimport { fingerprintKey, type OverrideType, type PatchAction, type Pattern, type Refinement } from './models.js';\nimport { readPatternsStore, readRefinementsStore } from './store.js';\n\n/**\n * Portable context gathering for `skill-forge refine` (v0.10): override-precedence scope\n * resolution, an override-file inventory per scope, git status/log of the target skill dir,\n * and similar-past-refinement lookup from the user-scope store. Deliberately out of scope\n * (see `.claude/plans/refinement-merge.md`'s \"What is deliberately NOT ported\" section):\n * syncing to a personal MCP server and session-history harvesting tied to a specific agent's\n * tooling — neither is portable to a plain npm CLI. Nothing here executes anything from a\n * target skill; the only subprocess ever spawned is a fixed `git -C <dir> ...`\n * (argv array, no shell), mirroring `src/commands/watch.ts`'s exec\n * injection discipline.\n */\n\nexport type RefineScope = 'local' | 'shared' | 'user';\n\nexport interface ResolvedProjectScopeDir {\n scope: 'local' | 'shared';\n /** `<cwd>/.claude/skills/<skill>` (local) or `<cwd>/skills/<skill>` (shared). */\n dir: string;\n exists: boolean;\n}\n\nexport interface UserScopeCandidate {\n /** The configured root exactly as given in `config.skillsRoots`. */\n root: string;\n /** `<root>/<skill>` — the candidate base skill directory. */\n dir: string;\n}\n\n/**\n * Resolution of the USER scope for one skill, per plan Amendment 6's\n * explicit order: `--user-root` flag -> the configured root that already\n * (realpath-)contains the target skill -> the sole configured root outside\n * the project -> ambiguous (caller must ask interactively or throw a\n * `UserError` listing candidates — this module never guesses).\n */\nexport type UserScopeResolution =\n | { resolved: true; dir: string; root: string; reason: 'flag' | 'contains-skill' | 'sole-external-root' }\n | { resolved: false; candidates: UserScopeCandidate[] };\n\nexport interface ScopeResolution {\n local: ResolvedProjectScopeDir;\n shared: ResolvedProjectScopeDir;\n user: UserScopeResolution;\n}\n\nfunction resolveConfiguredRoot(root: string): string {\n return resolvePath(expandHome(root));\n}\n\n/**\n * Rejects a skill name that isn't a plain directory basename — no path\n * separators, and not a `.`/`..` dot-segment — BEFORE it's ever joined into\n * a filesystem path. Every place a skill name (CLI `--skill` input, or one\n * read back out of the refinements/patterns store, e.g. `pattern.affectedSkills[0]`)\n * is turned into a directory must call this first; `resolveScopes` below is\n * the single choke point every refine subcommand routes through, so guarding\n * it here also protects a poisoned store entry, not just new captures.\n */\nexport function assertPlainSkillName(name: string): void {\n if (!name || name.trim().length === 0) {\n throw new UserError('skill name must not be empty');\n }\n if (name.includes('/') || name.includes('\\\\') || name === '.' || name === '..') {\n throw new UserError(`invalid skill name \"${name}\" — must be a plain directory name, not a path`);\n }\n}\n\nfunction realpathOrNull(p: string): string | null {\n try {\n return realpathSync(p);\n } catch {\n return null;\n }\n}\n\nfunction isUnderRoot(candidate: string, root: string): boolean {\n const rel = relative(root, candidate);\n return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));\n}\n\n/**\n * Resolves the three override-precedence scopes for `skill` relative to\n * `cwd`: PROJECT LOCAL (`<cwd>/.claude/skills/<skill>`), PROJECT SHARED\n * (`<cwd>/skills/<skill>`), and USER SCOPE per Amendment 6's explicit order.\n * `userRootFlag`, when given, always wins for the user scope regardless of\n * whether it actually contains the skill yet (an agent/user may be\n * bootstrapping a brand-new user-scope skill). Never silently guesses\n * between multiple equally-plausible candidate roots — see\n * `UserScopeResolution`'s `resolved: false` branch.\n */\nexport function resolveScopes(config: Config, cwd: string, skill: string, userRootFlag?: string): ScopeResolution {\n assertPlainSkillName(skill);\n const resolvedCwd = resolvePath(cwd);\n const localDir = join(resolvedCwd, '.claude', 'skills', skill);\n const sharedDir = join(resolvedCwd, 'skills', skill);\n\n const local: ResolvedProjectScopeDir = { scope: 'local', dir: localDir, exists: existsSync(localDir) };\n const shared: ResolvedProjectScopeDir = { scope: 'shared', dir: sharedDir, exists: existsSync(sharedDir) };\n\n const user = resolveUserScope(config, resolvedCwd, skill, userRootFlag);\n\n return { local, shared, user };\n}\n\nfunction resolveUserScope(config: Config, resolvedCwd: string, skill: string, userRootFlag?: string): UserScopeResolution {\n if (userRootFlag) {\n const root = resolvePath(userRootFlag);\n return { resolved: true, dir: join(root, skill), root: userRootFlag, reason: 'flag' };\n }\n\n const cwdReal = realpathOrNull(resolvedCwd) ?? resolvedCwd;\n const candidates: UserScopeCandidate[] = config.skillsRoots.map((root) => ({ root, dir: join(resolveConfiguredRoot(root), skill) }));\n\n // Rank 1: a configured root that already (realpath-)contains the target skill.\n for (const candidate of candidates) {\n const skillReal = realpathOrNull(candidate.dir);\n if (skillReal) {\n return { resolved: true, dir: candidate.dir, root: candidate.root, reason: 'contains-skill' };\n }\n }\n\n // Rank 2: the sole configured root that lies outside the current project.\n const externalCandidates = candidates.filter((candidate) => {\n const rootReal = realpathOrNull(resolveConfiguredRoot(candidate.root)) ?? resolveConfiguredRoot(candidate.root);\n return !isUnderRoot(rootReal, cwdReal) && !isUnderRoot(cwdReal, rootReal);\n });\n if (externalCandidates.length === 1) {\n return { resolved: true, dir: externalCandidates[0].dir, root: externalCandidates[0].root, reason: 'sole-external-root' };\n }\n\n return { resolved: false, candidates: externalCandidates.length > 0 ? externalCandidates : candidates };\n}\n\n// ---------------------------------------------------------------------------\n// Override-file inventory\n// ---------------------------------------------------------------------------\n\nexport interface ScopeOverrideInventory {\n scope: RefineScope;\n dir: string;\n dirExists: boolean;\n skillMd: boolean;\n patchMd: boolean;\n extendMd: boolean;\n configJson: boolean;\n hooksDir: boolean;\n scriptsDir: boolean;\n}\n\nfunction isFile(p: string): boolean {\n try {\n return statSync(p).isFile();\n } catch {\n return false;\n }\n}\n\nfunction isDirectory(p: string): boolean {\n try {\n return statSync(p).isDirectory();\n } catch {\n return false;\n }\n}\n\n/** Inventories which override artifacts exist at a single scope's base directory — the \"why didn't my patch take effect\" primitive `refine which` is built on. */\nexport function inventoryScopeDir(scope: RefineScope, dir: string): ScopeOverrideInventory {\n return {\n scope,\n dir,\n dirExists: isDirectory(dir),\n skillMd: isFile(join(dir, 'SKILL.md')),\n patchMd: isFile(join(dir, 'SKILL.patch.md')),\n extendMd: isFile(join(dir, 'SKILL.extend.md')),\n configJson: isFile(join(dir, 'skill-config.json')),\n hooksDir: isDirectory(join(dir, 'hooks')),\n scriptsDir: isDirectory(join(dir, 'scripts')),\n };\n}\n\n/** Inventories override artifacts across all three resolved scopes, in precedence order (local, shared, user). The user scope is only inventoried when resolved to a single directory. */\nexport function inventoryAllScopes(resolution: ScopeResolution): ScopeOverrideInventory[] {\n const rows = [inventoryScopeDir('local', resolution.local.dir), inventoryScopeDir('shared', resolution.shared.dir)];\n if (resolution.user.resolved) {\n rows.push(inventoryScopeDir('user', resolution.user.dir));\n }\n return rows;\n}\n\n// ---------------------------------------------------------------------------\n// Git status/log of the target skill dir — injectable ExecFn, argv-array only\n// ---------------------------------------------------------------------------\n\nexport interface GitContext {\n isRepo: boolean;\n /** `git status --porcelain` output, only present when `isRepo` is true. */\n status?: string;\n /** `git log -5 --oneline` output, only present when `isRepo` is true. */\n log?: string;\n /** Present when the dir isn't a git repo (or git isn't runnable) — never thrown, always tolerated. */\n detail?: string;\n}\n\nexport interface GitContextDeps {\n /** Injectable subprocess runner, mirroring `src/commands/watch.ts`'s `ExecFn` injection. Defaults to a real argv-array spawn (no shell). */\n exec?: ExecFn;\n}\n\nfunction firstLine(text: string): string {\n return text.split('\\n').find((l) => l.trim().length > 0)?.trim() ?? '(no output)';\n}\n\n/**\n * Runs `git -C <dir> status --porcelain` and `git -C <dir> log -5 --oneline`\n * (fixed argv, no shell) against `skillDir`, tolerant of `dir` not being a\n * git repo at all (or git being unavailable) — never throws. This is the\n * ONLY subprocess this module ever spawns.\n */\nexport async function gatherGitContext(skillDir: string, deps: GitContextDeps = {}): Promise<GitContext> {\n const exec = deps.exec ?? defaultExec;\n\n let statusResult;\n try {\n statusResult = await exec('git', ['-C', skillDir, 'status', '--porcelain'], { cwd: skillDir });\n } catch (err) {\n return { isRepo: false, detail: `git not runnable: ${(err as Error).message}` };\n }\n if (statusResult.code !== 0) {\n return { isRepo: false, detail: firstLine(statusResult.stderr || statusResult.stdout || 'not a git repository') };\n }\n\n let logResult;\n try {\n logResult = await exec('git', ['-C', skillDir, 'log', '-5', '--oneline'], { cwd: skillDir });\n } catch (err) {\n return { isRepo: true, status: statusResult.stdout, detail: `git log failed: ${(err as Error).message}` };\n }\n\n return {\n isRepo: true,\n status: statusResult.stdout,\n log: logResult.code === 0 ? logResult.stdout : undefined,\n detail: logResult.code === 0 ? undefined : firstLine(logResult.stderr || logResult.stdout),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Similar-refinement lookup (by skill, then fingerprintKey match)\n// ---------------------------------------------------------------------------\n\nexport interface SimilarRefinementMatch {\n refinement: Refinement;\n pattern?: Pattern;\n /** `'fingerprint'` when the match shares an exact deterministic join key with the query (see `fingerprintKey`); `'skill'` when it's merely the same skill. */\n matchedBy: 'fingerprint' | 'skill';\n}\n\nexport interface FindSimilarRefinementsOptions {\n targetSection?: string;\n overrideType?: OverrideType;\n patchAction?: PatchAction;\n}\n\n/**\n * Looks up past refinements against `skill` from the user-scope store, most\n * relevant first: an exact `fingerprintKey` match (same skill + target\n * section + override type + patch action) sorts ahead of every other\n * same-skill refinement. Returns every same-skill refinement — callers slice\n * for display. Read-only; never mutates the store.\n */\nexport function findSimilarRefinements(skill: string, options: FindSimilarRefinementsOptions = {}): SimilarRefinementMatch[] {\n const { records: refinements } = readRefinementsStore();\n const { records: patterns } = readPatternsStore();\n const patternById = new Map(patterns.map((p) => [p.id, p] as const));\n\n const bySkill = refinements.filter((r) => r.skill === skill);\n\n const fingerprint =\n options.targetSection !== undefined && options.overrideType !== undefined\n ? fingerprintKey(skill, options.targetSection, options.overrideType, options.patchAction)\n : undefined;\n\n const matches: SimilarRefinementMatch[] = bySkill.map((refinement) => {\n const pattern = refinement.patternId ? patternById.get(refinement.patternId) : undefined;\n const matchedBy: SimilarRefinementMatch['matchedBy'] = fingerprint && pattern?.fingerprint === fingerprint ? 'fingerprint' : 'skill';\n return { refinement, pattern, matchedBy };\n });\n\n return matches.sort((a, b) => {\n if (a.matchedBy === b.matchedBy) return b.refinement.createdAt.localeCompare(a.refinement.createdAt);\n return a.matchedBy === 'fingerprint' ? -1 : 1;\n });\n}\n\n// ---------------------------------------------------------------------------\n// Top-level bundle\n// ---------------------------------------------------------------------------\n\nexport interface RefineContext {\n scopes: ScopeResolution;\n overrideInventory: ScopeOverrideInventory[];\n git: GitContext;\n similarRefinements: SimilarRefinementMatch[];\n}\n\nexport interface GatherRefineContextOptions {\n userRoot?: string;\n targetSection?: string;\n overrideType?: OverrideType;\n patchAction?: PatchAction;\n}\n\n/**\n * Composes the full portable context bundle for capturing a refinement\n * against `skill`: scope resolution, override-file inventory across every\n * resolved scope, git status/log of the resolved USER-scope skill dir (the\n * base skill a promote would eventually mutate — project-scope override\n * dirs rarely have their own git history), and similar past refinements.\n * This is the CLI's analog of `gather_context.py`'s portable subset — see\n * this module's doc comment for what was deliberately dropped.\n */\nexport async function gatherRefineContext(\n config: Config,\n cwd: string,\n skill: string,\n options: GatherRefineContextOptions = {},\n deps: GitContextDeps = {}\n): Promise<RefineContext> {\n const scopes = resolveScopes(config, cwd, skill, options.userRoot);\n const overrideInventory = inventoryAllScopes(scopes);\n const gitTargetDir = scopes.user.resolved ? scopes.user.dir : cwd;\n const git = await gatherGitContext(gitTargetDir, deps);\n const similarRefinements = findSimilarRefinements(skill, {\n targetSection: options.targetSection,\n overrideType: options.overrideType,\n patchAction: options.patchAction,\n });\n\n return { scopes, overrideInventory, git, similarRefinements };\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { randomBytes } from 'node:crypto';\n\n/**\n * Data model for `skill-forge refine` (v0.10): record types, JSON-store semantics, and the\n * pattern fingerprint/similarity criteria. See `.claude/plans/refinement-merge.md` for the\n * full design (Amendments 3 and 7 in particular shape `Pattern`/`Refinement` here).\n *\n * Notable design choices:\n * - `PatternStatus` values are `tracking|ready|generalized|dismissed` (the\n * plan's Goal section).\n * - `Pattern.count` does not exist as a stored field — `patternCount()`\n * derives it from unique occurrence project realpaths, so it can never\n * drift from the occurrences it's counting (Amendment 3).\n * - IDs get a 4-hex random suffix instead of a same-day sequence counter\n * (Amendment 7), avoiding a directory-listing scan to allocate an id.\n */\n\nexport type RefinementCategory = 'trigger' | 'content' | 'hook' | 'tool' | 'pattern' | 'config' | 'new';\n\nexport const REFINEMENT_CATEGORIES: readonly RefinementCategory[] = ['trigger', 'content', 'hook', 'tool', 'pattern', 'config', 'new'];\n\n/** All seven override types (plan Amendment 2). patch/extend/config are rendered; full/hook/script are verbatim override-file writes; new creates an extension section. */\nexport type OverrideType = 'patch' | 'extend' | 'config' | 'full' | 'hook' | 'script' | 'new';\n\nexport const OVERRIDE_TYPES: readonly OverrideType[] = ['patch', 'extend', 'config', 'full', 'hook', 'script', 'new'];\n\n/** The six section-patch actions. Owned here (not patch.ts) so both models.ts and patch.ts can import the same type without a circular dependency. */\nexport type PatchAction = 'append' | 'prepend' | 'replace-section' | 'insert-after' | 'insert-before' | 'delete-section';\n\nexport const PATCH_ACTIONS: readonly PatchAction[] = ['append', 'prepend', 'replace-section', 'insert-after', 'insert-before', 'delete-section'];\n\n/** tracking: count(unique projects) < 2. ready: >= 2 (Amendment 3's \"second, different project\" rule). generalized: promoted to user scope. dismissed: manually rejected. */\nexport type PatternStatus = 'tracking' | 'ready' | 'generalized' | 'dismissed';\n\nexport const PATTERN_STATUSES: readonly PatternStatus[] = ['tracking', 'ready', 'generalized', 'dismissed'];\n\nexport type GeneralizationPotential = 'high' | 'medium' | 'low';\n\n/** Project scope a capture writes its override artifacts to (plan's override-precedence section). */\nexport type CaptureScope = 'local' | 'shared';\n\n/** Lifecycle status of a single Refinement record. */\nexport type RefinementStatus = 'pending' | 'applied' | 'generalized';\n\nexport function isRefinementCategory(value: string): value is RefinementCategory {\n return (REFINEMENT_CATEGORIES as readonly string[]).includes(value);\n}\n\nexport function isOverrideType(value: string): value is OverrideType {\n return (OVERRIDE_TYPES as readonly string[]).includes(value);\n}\n\nexport function isPatchAction(value: string): value is PatchAction {\n return (PATCH_ACTIONS as readonly string[]).includes(value);\n}\n\nexport function isPatternStatus(value: string): value is PatternStatus {\n return (PATTERN_STATUSES as readonly string[]).includes(value);\n}\n\n/** All three refinement lifecycle statuses. */\nexport const REFINEMENT_STATUSES: readonly RefinementStatus[] = ['pending', 'applied', 'generalized'];\n\nexport function isRefinementStatus(value: string): value is RefinementStatus {\n return (REFINEMENT_STATUSES as readonly string[]).includes(value);\n}\n\n/**\n * A single captured/applied refinement against an installed skill. `id` is\n * `REF-YYYYMMDD-xxxx` (see {@link generateRefinementId}). `patchAction`/\n * `patchMarker`/`patchContent` are only meaningful when `overrideType`\n * is `'patch'`.\n */\nexport interface Refinement {\n id: string;\n skill: string;\n category: RefinementCategory;\n overrideType: OverrideType;\n /** Heading-path selector, e.g. `\"Hooks > duplicate-check\"` (plan Amendment 4). */\n targetSection: string;\n\n expectedBehavior: string;\n actualBehavior: string;\n example?: string;\n desiredOutcome?: string;\n\n rootCause?: string;\n confidence?: number;\n\n /** Human-readable project name/label (may differ from projectRoot's basename). */\n project: string;\n /** Realpath of the project root this refinement was captured in — the unit the second-project rule counts by. */\n projectRoot: string;\n\n patchAction?: PatchAction;\n patchMarker?: string;\n patchContent?: string;\n\n /** The pattern this refinement was joined into (auto-matched or explicit `--pattern-id`). */\n patternId?: string;\n generalizationPotential?: GeneralizationPotential;\n\n scope?: CaptureScope;\n status: RefinementStatus;\n\n createdAt: string;\n appliedAt?: string;\n}\n\n/** One occurrence of a pattern: a refinement captured against it in a given project, at a given time. */\nexport interface PatternOccurrence {\n refinementId: string;\n /** Realpath of the project root the occurrence was captured in — see {@link patternCount}. */\n projectRealpath: string;\n timestamp: string;\n}\n\n/**\n * A refinement pattern tracked across projects. `id` is `PAT-xxxx` (see\n * {@link generatePatternId}). There is deliberately no stored `count` field\n * (Amendment 3) — always derive it with {@link patternCount}.\n */\nexport interface Pattern {\n id: string;\n name: string;\n description: string;\n affectedSkills: string[];\n refinementIds: string[];\n occurrences: PatternOccurrence[];\n status: PatternStatus;\n /** file/artifact path -> proposed change content. `proposedChanges.content` holds the representative sample used for content-similarity joins (see {@link contentSimilarity}). */\n proposedChanges: Record<string, string>;\n /** Deterministic fingerprint of skill+target+overrideType+action (see {@link fingerprintKey}) for exact-match joins. */\n fingerprint: string;\n\n firstSeen: string;\n lastSeen: string;\n generalizedAt?: string;\n /** Backup id created by the promote that generalized this pattern; needed for `--rollback`. */\n generalizedBackupId?: string;\n dismissedReason?: string;\n}\n\n/** Counts unique project realpaths across a pattern's occurrences — the value `count` would have held, always in sync with `occurrences`. */\nexport function patternCount(pattern: Pattern): number {\n return new Set(pattern.occurrences.map((o) => o.projectRealpath)).size;\n}\n\n/** Injectable random-hex source (4 hex chars) so tests can produce deterministic ids. */\nexport type RandomHex = () => string;\n\n/** Default `RandomHex` source (4 hex chars from crypto). Shared by store.ts's id/backup-id generation. */\nexport function defaultRandomHex(): string {\n return randomBytes(2).toString('hex');\n}\n\nfunction formatYYYYMMDD(date: Date): string {\n const yyyy = date.getUTCFullYear().toString().padStart(4, '0');\n const mm = (date.getUTCMonth() + 1).toString().padStart(2, '0');\n const dd = date.getUTCDate().toString().padStart(2, '0');\n return `${yyyy}${mm}${dd}`;\n}\n\n/** Generates `REF-YYYYMMDD-xxxx`, retrying the random suffix on collision against `existingIds`. */\nexport function generateRefinementId(\n existingIds: ReadonlySet<string> = new Set(),\n now: Date = new Date(),\n randomHex: RandomHex = defaultRandomHex\n): string {\n const datePart = formatYYYYMMDD(now);\n let id: string;\n do {\n id = `REF-${datePart}-${randomHex()}`;\n } while (existingIds.has(id));\n return id;\n}\n\n/** Generates `PAT-xxxx`, retrying the random suffix on collision against `existingIds`. */\nexport function generatePatternId(existingIds: ReadonlySet<string> = new Set(), randomHex: RandomHex = defaultRandomHex): string {\n let id: string;\n do {\n id = `PAT-${randomHex()}`;\n } while (existingIds.has(id));\n return id;\n}\n\n/**\n * Deterministic join key: same skill + target section + override type +\n * patch action always fingerprint identically, regardless of the exact\n * wording of the expected/actual behavior text. Case-insensitive,\n * whitespace-normalized (matches the selector-matching rules patch.ts uses\n * for target sections — plan Amendment 4).\n */\nexport function fingerprintKey(skill: string, target: string, overrideType: OverrideType, action?: PatchAction | null): string {\n const normalize = (s: string): string => s.trim().toLowerCase().replace(/\\s+/g, ' ');\n return [normalize(skill), normalize(target), overrideType, action ?? ''].join('::');\n}\n\n/**\n * Token-Jaccard content similarity: lowercase, whitespace-split into\n * a token set per side, then intersection-over-union. Pure content-shape\n * signal — callers combine it with {@link fingerprintKey} rather than using\n * it alone. A join threshold of `> 0.7` is the caller's decision (see\n * `store.ts`'s `SIMILARITY_JOIN_THRESHOLD`), not baked in here.\n */\nexport function contentSimilarity(a: string, b: string): number {\n const tokenize = (s: string): Set<string> => new Set(s.toLowerCase().split(/\\s+/).filter(Boolean));\n const tokensA = tokenize(a);\n const tokensB = tokenize(b);\n if (tokensA.size === 0 || tokensB.size === 0) return 0;\n\n let intersectionSize = 0;\n for (const token of tokensA) {\n if (tokensB.has(token)) intersectionSize++;\n }\n const unionSize = tokensA.size + tokensB.size - intersectionSize;\n return unionSize === 0 ? 0 : intersectionSize / unionSize;\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { createHash, randomUUID } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, realpathSync } from 'node:fs';\nimport { basename, dirname, join, resolve as resolvePath } from 'node:path';\nimport { getHomeDir } from '../config.js';\nimport { UserError } from '../errors.js';\nimport { writeJsonAtomic } from '../util.js';\nimport {\n contentSimilarity,\n defaultRandomHex,\n fingerprintKey,\n generatePatternId,\n generateRefinementId,\n patternCount,\n type CaptureScope,\n type GeneralizationPotential,\n type OverrideType,\n type PatchAction,\n type Pattern,\n type RandomHex,\n type Refinement,\n type RefinementCategory,\n type RefinementStatus,\n} from './models.js';\n\n/**\n * User-scope store for `skill-forge refine` (v0.10) — `~/.skill-forge/refinements/`\n * (honors SKILL_FORGE_HOME via `getHomeDir()`, same as `queue.ts`). Two\n * versioned JSON envelopes — `refinements.json` and `patterns.json` — plus\n * per-promotion backup manifests are the store. `refine list`/`refine patterns` (a\n * different lane's `src/commands/refine.ts`) render the human view from\n * these files; there is no other canonical store.\n *\n * Writes are atomic (temp file + rename within the same directory, same\n * idiom as `queue.ts`'s `writeQueue`) and mode 0600. There is no\n * cross-process lock — this is a single-user CLI, and atomic rename is the\n * consistency boundary (plan Amendment 7).\n */\n\nexport interface RefinementStore {\n schemaVersion: 1;\n records: Refinement[];\n}\n\nexport interface PatternStore {\n schemaVersion: 1;\n records: Pattern[];\n}\n\n/** Content-similarity join threshold (plan Amendment 3): a refinement's patch content joins an existing pattern when {@link contentSimilarity} exceeds this. */\nexport const SIMILARITY_JOIN_THRESHOLD = 0.7;\n\n/** unique-project count a pattern needs to flip from `tracking` to `ready` (the \"second, different project\" rule). */\nconst READY_THRESHOLD = 2;\n\nexport function getRefinementsDir(): string {\n return join(getHomeDir(), 'refinements');\n}\n\nexport function getRefinementsPath(): string {\n return join(getRefinementsDir(), 'refinements.json');\n}\n\nexport function getPatternsPath(): string {\n return join(getRefinementsDir(), 'patterns.json');\n}\n\nexport function getBackupsDir(): string {\n return join(getRefinementsDir(), 'backups');\n}\n\n\n/**\n * Reads and JSON-parses `path`, labeling both failure modes with `label` in\n * a `UserError`: unreadable (permissions, etc.) and malformed (not valid\n * JSON). Shared by {@link readStore} and {@link readBackupManifest} — the\n * only difference between those two callers is what a *missing* file means.\n */\nfunction readJsonFile(path: string, label: string): unknown {\n let raw: string;\n try {\n raw = readFileSync(path, 'utf-8');\n } catch (err) {\n throw new UserError(`unable to read ${label} at ${path}: ${(err as Error).message}`);\n }\n\n try {\n return JSON.parse(raw);\n } catch {\n throw new UserError(`malformed ${label} at ${path}: not valid JSON`);\n }\n}\n\n/**\n * Tolerant, typed envelope load: a missing file is an empty store (first\n * run); a present-but-unparseable or wrong-shaped file is a loud\n * `UserError` naming the file, never a silent reset to empty (which would\n * discard real records).\n */\nfunction readStore<T>(path: string, label: string, validateRecord?: (record: unknown, index: number) => void): { schemaVersion: 1; records: T[] } {\n if (!existsSync(path)) {\n return { schemaVersion: 1, records: [] };\n }\n\n const parsed = readJsonFile(path, label);\n if (typeof parsed !== 'object' || parsed === null || !Array.isArray((parsed as { records?: unknown }).records)) {\n throw new UserError(`malformed ${label} at ${path}: expected an object with a \"records\" array`);\n }\n\n const records = (parsed as { records: unknown[] }).records;\n if (validateRecord) {\n records.forEach((record, index) => validateRecord(record, index));\n }\n\n return { schemaVersion: 1, records: records as T[] };\n}\n\n/**\n * Minimal shape check for a `refinements.json` record — fails loud, naming the file, instead of\n * surfacing a later `undefined`-property TypeError somewhere downstream. Also enforces the\n * `overrideType`-conditional coupling of `patchAction`/`patchMarker`/`patchContent` (merge-gate\n * CONTRACT-HARDENING C1/C2): those fields are only meaningful — and only allowed to be present —\n * when `overrideType` is `'patch'`, matching `schemas/refinement.schema.json`'s `if`/`then`.\n */\nfunction validateRefinementRecordShape(record: unknown, index: number, path: string): void {\n if (typeof record !== 'object' || record === null) {\n throw new UserError(`malformed refinements.json at ${path}: record[${index}] is not an object`);\n }\n const r = record as Record<string, unknown>;\n if (typeof r.id !== 'string' || r.id.length === 0) {\n throw new UserError(`malformed refinements.json at ${path}: record[${index}] is missing a valid \"id\"`);\n }\n if (typeof r.createdAt !== 'string' || r.createdAt.length === 0) {\n throw new UserError(`malformed refinements.json at ${path}: record \"${r.id}\" is missing a valid \"createdAt\"`);\n }\n if (typeof r.status !== 'string' || r.status.length === 0) {\n throw new UserError(`malformed refinements.json at ${path}: record \"${r.id}\" is missing a valid \"status\"`);\n }\n if (typeof r.skill !== 'string' || r.skill.length === 0) {\n throw new UserError(`malformed refinements.json at ${path}: record \"${r.id}\" is missing a valid \"skill\"`);\n }\n\n // NOTE (deliberate narrowing of merge-gate CONTRACT-HARDENING C1/C2's literal wording): the\n // finding as written would also require patchMarker/patchContent to be ABSENT whenever\n // overrideType !== 'patch'. That's contradicted by primary evidence in this same lane's\n // src/commands/refine.ts — `refineCaptureCommand` passes `patchContent: content` for every one\n // of the seven override types (renderCaptureArtifact requires non-empty content for all of\n // them, not just 'patch'), and passes `patchMarker: opts.marker` unconditionally too (reused as\n // the destination filename override for full/hook/script). Enforcing absence here would throw\n // immediately after any legitimate extend/new/config/full/hook/script capture. So: patchAction\n // is strictly coupled to overrideType === 'patch' (verified safe — validateCaptureOptions only\n // ever sets `action` for that override type); patchMarker's presence is required only when a\n // patch action that needs a marker is used (matches patch.ts's requireMarker); patchContent is\n // required whenever overrideType === 'patch' (the only case store.ts itself depends on the\n // field for, via content-similarity joins) but is not restricted for the other six types.\n const patchActionsRequiringMarker = new Set(['insert-after', 'insert-before', 'replace-section', 'delete-section']);\n if (r.overrideType === 'patch') {\n if (typeof r.patchAction !== 'string' || r.patchAction.length === 0) {\n throw new UserError(`malformed refinements.json at ${path}: record \"${r.id}\" has overrideType \"patch\" but is missing \"patchAction\"`);\n }\n if (patchActionsRequiringMarker.has(r.patchAction) && (typeof r.patchMarker !== 'string' || r.patchMarker.length === 0)) {\n throw new UserError(\n `malformed refinements.json at ${path}: record \"${r.id}\" has patchAction \"${r.patchAction}\" but is missing \"patchMarker\"`\n );\n }\n if (typeof r.patchContent !== 'string' || r.patchContent.length === 0) {\n throw new UserError(`malformed refinements.json at ${path}: record \"${r.id}\" has overrideType \"patch\" but is missing \"patchContent\"`);\n }\n } else if (r.patchAction !== undefined) {\n throw new UserError(\n `malformed refinements.json at ${path}: record \"${r.id}\" has overrideType \"${String(r.overrideType)}\" but carries \"patchAction\" — only valid when overrideType is \"patch\"`\n );\n }\n}\n\n/**\n * Minimal shape check for a `patterns.json` record — same rationale as\n * {@link validateRefinementRecordShape}. Also enforces the `status`-conditional lifecycle\n * coupling (merge-gate CONTRACT-HARDENING I1): `dismissed` requires `dismissedReason`,\n * `generalized` requires `generalizedAt`/`generalizedBackupId`, matching\n * `schemas/pattern.schema.json`'s `if`/`then`.\n */\nfunction validatePatternRecordShape(record: unknown, index: number, path: string): void {\n if (typeof record !== 'object' || record === null) {\n throw new UserError(`malformed patterns.json at ${path}: record[${index}] is not an object`);\n }\n const p = record as Record<string, unknown>;\n if (typeof p.id !== 'string' || p.id.length === 0) {\n throw new UserError(`malformed patterns.json at ${path}: record[${index}] is missing a valid \"id\"`);\n }\n if (typeof p.status !== 'string' || p.status.length === 0) {\n throw new UserError(`malformed patterns.json at ${path}: record \"${p.id}\" is missing a valid \"status\"`);\n }\n if (!Array.isArray(p.occurrences)) {\n throw new UserError(`malformed patterns.json at ${path}: record \"${p.id}\" is missing a valid \"occurrences\" array`);\n }\n\n if (p.status === 'dismissed' && (typeof p.dismissedReason !== 'string' || p.dismissedReason.length === 0)) {\n throw new UserError(`malformed patterns.json at ${path}: record \"${p.id}\" has status \"dismissed\" but is missing \"dismissedReason\"`);\n }\n if (p.status === 'generalized') {\n if (typeof p.generalizedAt !== 'string' || p.generalizedAt.length === 0) {\n throw new UserError(`malformed patterns.json at ${path}: record \"${p.id}\" has status \"generalized\" but is missing \"generalizedAt\"`);\n }\n if (typeof p.generalizedBackupId !== 'string' || p.generalizedBackupId.length === 0) {\n throw new UserError(`malformed patterns.json at ${path}: record \"${p.id}\" has status \"generalized\" but is missing \"generalizedBackupId\"`);\n }\n }\n}\n\nexport function readRefinementsStore(): RefinementStore {\n const path = getRefinementsPath();\n return readStore<Refinement>(path, 'refinements.json', (record, index) => validateRefinementRecordShape(record, index, path));\n}\n\nexport function writeRefinementsStore(store: RefinementStore): void {\n writeJsonAtomic(getRefinementsPath(), store);\n}\n\nexport function readPatternsStore(): PatternStore {\n const path = getPatternsPath();\n return readStore<Pattern>(path, 'patterns.json', (record, index) => validatePatternRecordShape(record, index, path));\n}\n\nexport function writePatternsStore(store: PatternStore): void {\n writeJsonAtomic(getPatternsPath(), store);\n}\n\n/** Resolves a project root to its realpath (the identity the second-project rule counts by); falls back to the resolved-but-unresolved path if the directory doesn't exist (e.g. it was removed since capture). */\nfunction resolveProjectRealpath(projectRoot: string): string {\n const resolved = resolvePath(projectRoot);\n try {\n return realpathSync(resolved);\n } catch {\n return resolved;\n }\n}\n\nfunction generatePatternName(refinement: Refinement): string {\n const parts = refinement.targetSection\n .replace(/\\//g, ' ')\n .replace(/-/g, ' ')\n .split(/\\s+/)\n .filter(Boolean);\n const lastThree = parts.slice(-3);\n if (lastThree.length === 0) return refinement.targetSection || `pattern-${refinement.skill}`;\n return lastThree.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');\n}\n\nfunction generatePatternDescription(refinement: Refinement): string {\n const category = refinement.category.charAt(0).toUpperCase() + refinement.category.slice(1);\n const cause = refinement.rootCause || refinement.expectedBehavior;\n return `${category} modification for ${refinement.skill}: ${cause}`;\n}\n\n/**\n * Finds or creates the pattern this refinement belongs to, mutating\n * `records` in place (pushing a new pattern, or mutating the matched one)\n * and returning it. Join order: explicit `patternId` override, then exact\n * `fingerprintKey` match, then content-similarity `> SIMILARITY_JOIN_THRESHOLD`\n * against the pattern's stored sample content — all skipped for\n * `generalized`/`dismissed` patterns (a settled pattern doesn't silently\n * reabsorb new occurrences).\n */\nfunction upsertPattern(\n records: Pattern[],\n refinement: Refinement,\n projectRealpath: string,\n nowIso: string,\n randomHex: RandomHex | undefined,\n explicitPatternId: string | undefined\n): { pattern: Pattern; becameReady: boolean } {\n let pattern: Pattern | undefined;\n\n if (explicitPatternId) {\n pattern = records.find((p) => p.id === explicitPatternId);\n if (!pattern) {\n throw new UserError(`no pattern with id: ${explicitPatternId}`);\n }\n if (pattern.status === 'dismissed' || pattern.status === 'generalized') {\n throw new UserError(`cannot join pattern ${explicitPatternId}: it is ${pattern.status} and no longer accepts new occurrences`);\n }\n } else {\n const joinable = records.filter((p) => p.status !== 'generalized' && p.status !== 'dismissed');\n const fingerprint = fingerprintKey(refinement.skill, refinement.targetSection, refinement.overrideType, refinement.patchAction);\n pattern = joinable.find((p) => p.fingerprint === fingerprint);\n\n if (!pattern && refinement.patchContent) {\n pattern = joinable.find((p) => {\n const sample = p.proposedChanges.content;\n return sample ? contentSimilarity(refinement.patchContent!, sample) > SIMILARITY_JOIN_THRESHOLD : false;\n });\n }\n }\n\n const wasTracking = pattern?.status === 'tracking';\n\n if (!pattern) {\n const created: Pattern = {\n id: generatePatternId(new Set(records.map((p) => p.id)), randomHex),\n name: generatePatternName(refinement),\n description: generatePatternDescription(refinement),\n affectedSkills: [refinement.skill],\n refinementIds: [refinement.id],\n occurrences: [{ refinementId: refinement.id, projectRealpath, timestamp: nowIso }],\n status: 'tracking',\n proposedChanges: refinement.patchContent ? { content: refinement.patchContent } : {},\n fingerprint: fingerprintKey(refinement.skill, refinement.targetSection, refinement.overrideType, refinement.patchAction),\n firstSeen: nowIso,\n lastSeen: nowIso,\n };\n records.push(created);\n return { pattern: created, becameReady: false };\n }\n\n if (!pattern.affectedSkills.includes(refinement.skill)) pattern.affectedSkills.push(refinement.skill);\n if (!pattern.refinementIds.includes(refinement.id)) pattern.refinementIds.push(refinement.id);\n pattern.occurrences.push({ refinementId: refinement.id, projectRealpath, timestamp: nowIso });\n pattern.lastSeen = nowIso;\n\n const becameReady = wasTracking && patternCount(pattern) >= READY_THRESHOLD;\n if (becameReady) pattern.status = 'ready';\n\n return { pattern, becameReady };\n}\n\nexport interface RecordRefinementInput {\n skill: string;\n category: RefinementCategory;\n overrideType: OverrideType;\n targetSection: string;\n expectedBehavior: string;\n actualBehavior: string;\n example?: string;\n desiredOutcome?: string;\n rootCause?: string;\n confidence?: number;\n project: string;\n /** Any path to the project root; resolved to a realpath for pattern-occurrence identity. */\n projectRoot: string;\n patchAction?: PatchAction;\n patchMarker?: string;\n patchContent?: string;\n generalizationPotential?: GeneralizationPotential;\n status?: RefinementStatus;\n scope?: CaptureScope;\n /** Explicit join override (plan Amendment 3's `--pattern-id`): must name an existing, non-dismissed/generalized pattern or a `UserError` is thrown. */\n patternId?: string;\n}\n\nexport interface RecordRefinementDeps {\n now?: Date;\n randomHex?: RandomHex;\n /**\n * Pre-minted refinement id to use verbatim instead of generating one — lets a caller (e.g.\n * `refineCaptureCommand`) embed the exact id it already committed to in a rendered artifact,\n * without smuggling a constant `randomHex` closure into {@link generateRefinementId}'s\n * collision-retry loop (a constant closure turns any id collision into an infinite loop, and\n * previously also neutered {@link generatePatternId}'s independent retry — see merge-gate\n * IMPORTANT 3). When omitted, `randomHex` (or the real crypto-random default) is used as\n * before.\n */\n id?: string;\n}\n\nexport interface RecordRefinementResult {\n refinement: Refinement;\n pattern: Pattern;\n /** True only when this call is the one that flipped the pattern tracking -> ready (Amendment 3's second-project rule). */\n patternBecameReady: boolean;\n}\n\n/**\n * Records a captured refinement: allocates its id, resolves the pattern it\n * belongs to (creating one if none matches), appends the refinement and\n * writes both stores atomically. A same-project repeat against an existing\n * `tracking` pattern does NOT flip it to `ready` — only a second, distinct\n * project's realpath does (Amendment 3).\n */\nexport function recordRefinement(input: RecordRefinementInput, deps: RecordRefinementDeps = {}): RecordRefinementResult {\n const now = deps.now ?? new Date();\n const nowIso = now.toISOString();\n\n const refinementsStore = readRefinementsStore();\n const patternsStore = readPatternsStore();\n\n const existingRefIds = new Set(refinementsStore.records.map((r) => r.id));\n const refinementId = deps.id ?? generateRefinementId(existingRefIds, now, deps.randomHex);\n const projectRealpath = resolveProjectRealpath(input.projectRoot);\n\n const refinement: Refinement = {\n id: refinementId,\n skill: input.skill,\n category: input.category,\n overrideType: input.overrideType,\n targetSection: input.targetSection,\n expectedBehavior: input.expectedBehavior,\n actualBehavior: input.actualBehavior,\n example: input.example,\n desiredOutcome: input.desiredOutcome,\n rootCause: input.rootCause,\n confidence: input.confidence,\n project: input.project,\n projectRoot: projectRealpath,\n patchAction: input.patchAction,\n patchMarker: input.patchMarker,\n patchContent: input.patchContent,\n generalizationPotential: input.generalizationPotential,\n scope: input.scope,\n status: input.status ?? 'applied',\n createdAt: nowIso,\n };\n\n const { pattern, becameReady } = upsertPattern(patternsStore.records, refinement, projectRealpath, nowIso, deps.randomHex, input.patternId);\n refinement.patternId = pattern.id;\n\n refinementsStore.records.push(refinement);\n\n writeRefinementsStore(refinementsStore);\n try {\n writePatternsStore(patternsStore);\n } catch (err) {\n throw new UserError(\n `refinements.json was updated with refinement ${refinement.id}, but patterns.json failed to update: ${(err as Error).message}`\n );\n }\n\n return { refinement, pattern, patternBecameReady: becameReady };\n}\n\n/** Marks a pattern `dismissed` (won't generalize) with a reason. Throws `UserError` if `id` isn't found. */\nexport function dismissPattern(id: string, reason: string): Pattern {\n const store = readPatternsStore();\n const pattern = store.records.find((p) => p.id === id);\n if (!pattern) {\n throw new UserError(`no pattern with id: ${id}`);\n }\n pattern.status = 'dismissed';\n pattern.dismissedReason = reason;\n pattern.lastSeen = new Date().toISOString();\n writePatternsStore(store);\n return pattern;\n}\n\n/** Marks a pattern `generalized`, recording the backup id created by the promote that applied it (needed for `--rollback`). Throws `UserError` if `id` isn't found. */\nexport function markGeneralized(id: string, backupId: string): Pattern {\n const store = readPatternsStore();\n const pattern = store.records.find((p) => p.id === id);\n if (!pattern) {\n throw new UserError(`no pattern with id: ${id}`);\n }\n const nowIso = new Date().toISOString();\n pattern.status = 'generalized';\n pattern.generalizedAt = nowIso;\n pattern.generalizedBackupId = backupId;\n pattern.lastSeen = nowIso;\n writePatternsStore(store);\n return pattern;\n}\n\nexport interface BackupManifestEntry {\n path: string;\n /** False = a tombstone: the file did not exist when the backup was taken (so restoring it means deleting it). */\n existed: boolean;\n sha256: string | null;\n /** POSIX permission bits (e.g. 0o644); null for a tombstone. */\n mode: number | null;\n /** Base64-encoded original bytes; null for a tombstone. */\n contentBase64: string | null;\n}\n\nexport interface BackupManifest {\n id: string;\n createdAt: string;\n files: BackupManifestEntry[];\n}\n\nexport interface CreateBackupDeps {\n now?: Date;\n randomHex?: RandomHex;\n}\n\nfunction formatBackupTimestamp(date: Date): string {\n return date.toISOString().replace(/[-:]/g, '').replace(/\\.\\d{3}Z$/, 'Z');\n}\n\n/**\n * Matches exactly what {@link formatBackupTimestamp} + a 4-hex `randomHex()` suffix produces\n * (`YYYYMMDDTHHMMSSZ-xxxx`) — nothing else is a valid backup id. `readBackupManifest` rejects\n * anything else before it's ever joined into a path, closing off `../`-style traversal via a\n * user-supplied `<backup-id>` positional.\n */\nconst BACKUP_ID_PATTERN = /^\\d{8}T\\d{6}Z-[0-9a-f]{4}$/;\n\nexport function isValidBackupId(id: string): boolean {\n return BACKUP_ID_PATTERN.test(id);\n}\n\n/**\n * Backs up the current on-disk state of `filePaths` (pre-promotion) into\n * `backups/<id>/manifest.json`: per-file sha256 + original bytes (base64) +\n * mode, or a tombstone entry when the file doesn't exist yet (plan\n * Amendment 5). The manifest alone is sufficient to restore every file —\n * `--rollback` reads it back with {@link readBackupManifest}.\n */\nexport function createBackup(filePaths: readonly string[], deps: CreateBackupDeps = {}): BackupManifest {\n const now = deps.now ?? new Date();\n const randomHex = deps.randomHex ?? defaultRandomHex;\n const id = `${formatBackupTimestamp(now)}-${randomHex()}`;\n\n const files: BackupManifestEntry[] = filePaths.map((path) => {\n if (!existsSync(path)) {\n return { path, existed: false, sha256: null, mode: null, contentBase64: null };\n }\n const bytes = readFileSync(path);\n const mode = statSync(path).mode & 0o777;\n const sha256 = createHash('sha256').update(bytes).digest('hex');\n return { path, existed: true, sha256, mode, contentBase64: bytes.toString('base64') };\n });\n\n const manifest: BackupManifest = { id, createdAt: now.toISOString(), files };\n writeJsonAtomic(join(getBackupsDir(), id, 'manifest.json'), manifest);\n\n return manifest;\n}\n\n/**\n * Validates the shape of a parsed `manifest.json` before it is ever trusted by a rollback restore\n * loop (merge-gate IMPORTANT 4): the top-level envelope (`id`/`createdAt`/`files` array) plus\n * every `files[]` entry — `path`/`existed` always required, and `sha256`/`mode`/`contentBase64`\n * required (non-null) exactly when `existed` is `true` (a tombstone carries `null` for all three,\n * matching {@link createBackup}'s own tombstone branch). Throws `UserError` naming `path` on any\n * violation, BEFORE `refineRollbackCommand`'s restore loop touches disk — closing the gap where a\n * malformed manifest previously surfaced as a raw `TypeError` mid-restore, after some files may\n * already have been overwritten.\n */\nfunction validateBackupManifestShape(value: unknown, path: string): asserts value is BackupManifest {\n if (typeof value !== 'object' || value === null) {\n throw new UserError(`malformed backup manifest at ${path}: not an object`);\n }\n const m = value as Record<string, unknown>;\n if (typeof m.id !== 'string' || m.id.length === 0) {\n throw new UserError(`malformed backup manifest at ${path}: missing a valid \"id\"`);\n }\n if (typeof m.createdAt !== 'string' || m.createdAt.length === 0) {\n throw new UserError(`malformed backup manifest at ${path}: missing a valid \"createdAt\"`);\n }\n if (!Array.isArray(m.files)) {\n throw new UserError(`malformed backup manifest at ${path}: missing a valid \"files\" array`);\n }\n\n m.files.forEach((entry, index) => {\n if (typeof entry !== 'object' || entry === null) {\n throw new UserError(`malformed backup manifest at ${path}: files[${index}] is not an object`);\n }\n const f = entry as Record<string, unknown>;\n if (typeof f.path !== 'string' || f.path.length === 0) {\n throw new UserError(`malformed backup manifest at ${path}: files[${index}] is missing a valid \"path\"`);\n }\n if (typeof f.existed !== 'boolean') {\n throw new UserError(`malformed backup manifest at ${path}: files[${index}] (\"${f.path}\") is missing a valid \"existed\" (must be boolean)`);\n }\n if (f.existed) {\n if (typeof f.sha256 !== 'string' || f.sha256.length === 0) {\n throw new UserError(`malformed backup manifest at ${path}: files[${index}] (\"${f.path}\") has existed:true but is missing \"sha256\"`);\n }\n if (typeof f.mode !== 'number') {\n throw new UserError(`malformed backup manifest at ${path}: files[${index}] (\"${f.path}\") has existed:true but is missing a numeric \"mode\"`);\n }\n if (typeof f.contentBase64 !== 'string') {\n throw new UserError(`malformed backup manifest at ${path}: files[${index}] (\"${f.path}\") has existed:true but is missing \"contentBase64\"`);\n }\n } else {\n if (f.sha256 !== null || f.mode !== null || f.contentBase64 !== null) {\n throw new UserError(`malformed backup manifest at ${path}: files[${index}] (\"${f.path}\") has existed:false (a tombstone) but sha256/mode/contentBase64 are not all null`);\n }\n }\n });\n}\n\n/** Reads back a backup manifest written by {@link createBackup}. Throws `UserError` if the backup id is unknown or its manifest is unreadable/malformed/wrong-shaped. */\nexport function readBackupManifest(id: string): BackupManifest {\n if (!isValidBackupId(id)) {\n throw new UserError(`invalid backup id \"${id}\" — expected the format produced by \\`skill-forge refine promote\\` (e.g. 20260720T142305Z-a1b2)`);\n }\n\n const manifestPath = join(getBackupsDir(), id, 'manifest.json');\n if (!existsSync(manifestPath)) {\n throw new UserError(`no backup with id: ${id}`);\n }\n\n const parsed = readJsonFile(manifestPath, 'backup manifest');\n validateBackupManifestShape(parsed, manifestPath);\n return parsed;\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { UserError } from '../errors.js';\nimport { isPatchAction, type PatchAction } from './models.js';\n\n/**\n * The `skill-forge refine` patch engine — parses and applies section-patch action blocks\n * against a `SKILL.md`'s section tree. See `.claude/plans/refinement-merge.md` (Amendment 4)\n * for the selector rules this implements.\n *\n * Pure functions only: every export here takes strings/structures in and\n * returns strings/structures out. NO filesystem access, NO subprocesses —\n * the command layer (`src/commands/refine.ts`, not yet built) owns all I/O.\n * That keeps this module trivially unit-testable and keeps its security\n * review surface to \"does the string transformation do what it claims\",\n * with no execution or write-path risk to reason about here at all.\n *\n * ## Patch file format\n *\n * A `SKILL.patch.md` (or `SKILL.extend.md`) is a sequence of accumulated\n * action blocks:\n *\n * ```\n * <!-- ACTION: insert-after \"Only check paths\" -->\n * <!-- TARGET: Hooks > duplicate-check -->\n * # Exclude test directories\n * if echo \"$FILE_PATH\" | grep -qE \"(__tests__|fixtures)\"; then\n * exit 0\n * fi\n * ```\n *\n * `ACTION` is required and optionally carries a quoted marker argument\n * (required for `replace-section` / `insert-after` / `insert-before` /\n * `delete-section`, ignored for `append` / `prepend`). `TARGET` is optional\n * — a heading-path selector (`>`-separated, case-insensitive,\n * whitespace-normalized) identifying which section of the target `SKILL.md`\n * the block applies to; omitted means the whole document body. Any text\n * before the first `ACTION` comment (a `# SKILL.patch.md` / `# Patches\n * for: ...` preamble) is ignored. `ACTION`/`TARGET`\n * comments and `#`-heading lines inside fenced code blocks are never\n * treated as directives/headings — a patch block's own example content can\n * safely contain literal `<!-- ACTION -->` text or `#` lines inside a\n * fence without being misparsed.\n *\n * ## Selector semantics\n *\n * - Section targets are heading-path selectors resolved against a heading\n * tree built from the target `SKILL.md`'s markdown headings (`#`..`######`).\n * - `insert-after` / `insert-before` markers are substring matches (after\n * whitespace normalization) against any line within the resolved target\n * section's full body (including nested subsections).\n * - `replace-section` / `delete-section` markers are substring matches\n * against a *direct child heading* of the resolved target section — i.e.\n * they operate on a literal markdown subsection, not an arbitrary\n * in-content comment marker. This is a narrow, unambiguous definition.\n * - Every selector — section path or marker — fails loud (`UserError`\n * listing the candidates) on zero or more-than-one matches. Never fuzzy,\n * never \"use the first match\".\n *\n * YAML frontmatter (a leading `---` ... `---` block) is never scanned for\n * headings and is carried through every transformation byte-identical.\n */\n\nexport interface ParsedPatchBlock {\n action: PatchAction;\n /** Marker argument from `<!-- ACTION: <action> \"<marker>\" -->`, when present. */\n marker?: string;\n /** Heading-path selector from `<!-- TARGET: ... -->`, when present. Omitted = whole document body. */\n target?: string;\n /** Block body, trailing/leading blank lines trimmed. */\n content: string;\n}\n\nconst FENCE_RE = /^\\s*(```|~~~)/;\nconst HEADING_RE = /^(#{1,6})\\s+(.*)$/;\nconst ACTION_RE = /^<!--\\s*ACTION:\\s*([a-zA-Z-]+)(?:\\s+\"([^\"]*)\")?\\s*-->\\s*$/;\nconst TARGET_RE = /^<!--\\s*TARGET:\\s*(.+?)\\s*-->\\s*$/;\n\nconst ACTIONS_REQUIRING_MARKER: ReadonlySet<string> = new Set(['replace-section', 'insert-after', 'insert-before', 'delete-section']);\n\n/**\n * Parses a `SKILL.patch.md`/`SKILL.extend.md` source into its accumulated\n * action blocks, in file order. Throws `UserError` on an unrecognized\n * action, a missing required marker, or if the file contains no ACTION\n * blocks at all.\n */\nexport function parsePatchFile(source: string): ParsedPatchBlock[] {\n const lines = source.split('\\n');\n const blocks: ParsedPatchBlock[] = [];\n let inFence = false;\n let current: { action: PatchAction; marker?: string; target?: string; contentLines: string[] } | null = null;\n\n const finalizeCurrent = (): void => {\n if (!current) return;\n const contentLines = current.contentLines;\n while (contentLines.length > 0 && contentLines[0].trim() === '') contentLines.shift();\n while (contentLines.length > 0 && contentLines[contentLines.length - 1].trim() === '') contentLines.pop();\n blocks.push({\n action: current.action,\n marker: current.marker,\n target: current.target,\n content: contentLines.join('\\n'),\n });\n current = null;\n };\n\n let i = 0;\n while (i < lines.length) {\n const line = lines[i];\n\n if (FENCE_RE.test(line)) {\n inFence = !inFence;\n if (current) current.contentLines.push(line);\n i++;\n continue;\n }\n\n if (!inFence) {\n const trimmed = line.trim();\n const actionMatch = ACTION_RE.exec(trimmed);\n if (actionMatch) {\n finalizeCurrent();\n const action = actionMatch[1];\n if (!isPatchAction(action)) {\n throw new UserError(`Unknown patch action \"${action}\" (valid: append, prepend, replace-section, insert-after, insert-before, delete-section)`);\n }\n const marker = actionMatch[2];\n if (ACTIONS_REQUIRING_MARKER.has(action) && (!marker || marker.trim().length === 0)) {\n throw new UserError(`Action \"${action}\" requires a quoted marker argument, e.g. <!-- ACTION: ${action} \"marker text\" -->`);\n }\n current = { action, marker: marker || undefined, contentLines: [] };\n i++;\n\n // Optional TARGET metadata line, allowed after any leading blank lines.\n let peek = i;\n while (peek < lines.length && lines[peek].trim() === '') peek++;\n if (peek < lines.length) {\n const targetMatch = TARGET_RE.exec(lines[peek].trim());\n if (targetMatch) {\n current.target = targetMatch[1];\n i = peek + 1;\n }\n }\n continue;\n }\n }\n\n if (current) current.contentLines.push(line);\n i++;\n }\n finalizeCurrent();\n\n if (blocks.length === 0) {\n throw new UserError('No ACTION blocks found in patch source');\n }\n return blocks;\n}\n\ninterface HeadingNode {\n level: number;\n heading: string;\n /** Absolute line index of the heading line itself (root uses bodyOffset - 1, never dereferenced). */\n headingLine: number;\n /** Absolute line index (inclusive) where this section's body starts. */\n bodyStart: number;\n /** Absolute line index (exclusive) where this section's body ends. */\n bodyEnd: number;\n children: HeadingNode[];\n}\n\nfunction frontmatterOffset(lines: readonly string[]): number {\n if (lines[0] !== '---') return 0;\n for (let i = 1; i < lines.length; i++) {\n if (lines[i] === '---') return i + 1;\n }\n return 0; // unterminated frontmatter fence: treat as no frontmatter\n}\n\nfunction buildHeadingTree(lines: readonly string[], bodyOffset: number): HeadingNode {\n const flat: Array<{ level: number; text: string; line: number }> = [];\n let inFence = false;\n for (let i = bodyOffset; i < lines.length; i++) {\n const line = lines[i];\n if (FENCE_RE.test(line)) {\n inFence = !inFence;\n continue;\n }\n if (inFence) continue;\n const m = HEADING_RE.exec(line);\n if (m) flat.push({ level: m[1].length, text: m[2].trim(), line: i });\n }\n\n const root: HeadingNode = { level: 0, heading: '', headingLine: bodyOffset - 1, bodyStart: bodyOffset, bodyEnd: lines.length, children: [] };\n const stack: HeadingNode[] = [root];\n for (const h of flat) {\n while (stack.length > 1 && stack[stack.length - 1].level >= h.level) {\n const done = stack.pop();\n if (done) done.bodyEnd = h.line;\n }\n const node: HeadingNode = { level: h.level, heading: h.text, headingLine: h.line, bodyStart: h.line + 1, bodyEnd: lines.length, children: [] };\n stack[stack.length - 1].children.push(node);\n stack.push(node);\n }\n return root;\n}\n\nfunction normalizeText(s: string): string {\n return s.trim().replace(/\\s+/g, ' ');\n}\n\nfunction splitHeadingPath(target: string): string[] {\n return target\n .split('>')\n .map((segment) => segment.trim())\n .filter((segment) => segment.length > 0);\n}\n\nfunction collectDescendants(node: HeadingNode, acc: HeadingNode[]): void {\n acc.push(node);\n for (const child of node.children) collectDescendants(child, acc);\n}\n\n/**\n * Resolves a `>`-separated heading-path selector against a heading tree.\n * The first segment matches a heading anywhere in the document (so a\n * top-level `## Triggers` selector works whether or not the document has a\n * wrapping `# Title`); every subsequent segment matches a *direct child* of\n * the previous match, i.e. `>` means \"nested under\", not \"anywhere below\".\n * Fails loud on 0 or >1 matches.\n */\nfunction resolveHeadingPath(root: HeadingNode, segments: readonly string[]): HeadingNode {\n if (segments.length === 0) return root;\n\n const allNodes: HeadingNode[] = [];\n collectDescendants(root, allNodes);\n const headingNodes = allNodes.filter((n) => n.level > 0);\n\n const firstNorm = normalizeText(segments[0]);\n let candidates = headingNodes.filter((n) => normalizeText(n.heading) === firstNorm);\n if (candidates.length === 0) {\n const available = headingNodes.map((n) => n.heading);\n throw new UserError(`No section matching \"${segments[0]}\" found (available headings: ${available.length > 0 ? available.join(', ') : 'none'})`);\n }\n\n for (let i = 1; i < segments.length; i++) {\n const segment = segments[i];\n const norm = normalizeText(segment);\n const next: HeadingNode[] = [];\n for (const candidate of candidates) {\n for (const child of candidate.children) {\n if (normalizeText(child.heading) === norm) next.push(child);\n }\n }\n if (next.length === 0) {\n const available = [...new Set(candidates.flatMap((c) => c.children.map((child) => child.heading)))];\n throw new UserError(`No section matching \"${segment}\" found (available headings: ${available.length > 0 ? available.join(', ') : 'none'})`);\n }\n candidates = next;\n }\n\n if (candidates.length > 1) {\n throw new UserError(`Target selector \"${segments.join(' > ')}\" matched ${candidates.length} sections; be more specific`);\n }\n return candidates[0];\n}\n\nfunction findMarkerLine(lines: readonly string[], start: number, end: number, marker: string): number {\n const norm = normalizeText(marker);\n const matches: number[] = [];\n for (let i = start; i < end; i++) {\n if (normalizeText(lines[i]).includes(norm)) matches.push(i);\n }\n if (matches.length === 0) {\n throw new UserError(`No line matching marker \"${marker}\" found in target section`);\n }\n if (matches.length > 1) {\n const preview = matches.map((i) => `line ${i + 1}: \"${lines[i].trim()}\"`).join('; ');\n throw new UserError(`Marker \"${marker}\" matched ${matches.length} lines, expected exactly 1 (${preview})`);\n }\n return matches[0];\n}\n\nfunction findChildByMarker(target: HeadingNode, marker: string): HeadingNode {\n const norm = normalizeText(marker);\n const matches = target.children.filter((c) => normalizeText(c.heading).includes(norm));\n if (matches.length === 0) {\n const available = target.children.map((c) => c.heading).join(', ') || 'none';\n throw new UserError(`No subsection matching \"${marker}\" found under target section (available: ${available})`);\n }\n if (matches.length > 1) {\n throw new UserError(`Marker \"${marker}\" matched ${matches.length} subsections: ${matches.map((c) => c.heading).join(', ')}`);\n }\n return matches[0];\n}\n\nfunction requireMarker(block: ParsedPatchBlock): string {\n if (!block.marker || block.marker.trim().length === 0) {\n throw new UserError(`Action \"${block.action}\" requires a marker`);\n }\n return block.marker;\n}\n\nfunction applyOneAction(lines: string[], target: HeadingNode, block: ParsedPatchBlock): void {\n const contentLines = block.content.length === 0 ? [''] : block.content.split('\\n');\n\n switch (block.action) {\n case 'append': {\n lines.splice(target.bodyEnd, 0, ...contentLines);\n return;\n }\n case 'prepend': {\n lines.splice(target.bodyStart, 0, ...contentLines);\n return;\n }\n case 'insert-after':\n case 'insert-before': {\n const idx = findMarkerLine(lines, target.bodyStart, target.bodyEnd, requireMarker(block));\n lines.splice(block.action === 'insert-after' ? idx + 1 : idx, 0, ...contentLines);\n return;\n }\n case 'replace-section':\n case 'delete-section': {\n const child = findChildByMarker(target, requireMarker(block));\n const replacement = block.action === 'replace-section' ? contentLines : [];\n lines.splice(child.headingLine, child.bodyEnd - child.headingLine, ...replacement);\n return;\n }\n default: {\n const exhaustive: never = block.action;\n throw new UserError(`Unknown patch action \"${exhaustive as string}\"`);\n }\n }\n}\n\n/**\n * Applies accumulated patch blocks (parsed via {@link parsePatchFile}) to a\n * `SKILL.md` document's text, in block order. The heading tree is rebuilt\n * before each block so line-number shifts from earlier blocks never go\n * stale. YAML frontmatter is never touched. Trailing newline structure is\n * preserved automatically (round-trips through `split('\\n')`/`join('\\n')`).\n */\nexport function applyPatchBlocks(skillMd: string, blocks: readonly ParsedPatchBlock[]): string {\n const lines = skillMd.split('\\n');\n for (const block of blocks) {\n const bodyOffset = frontmatterOffset(lines);\n const root = buildHeadingTree(lines, bodyOffset);\n const segments = block.target ? splitHeadingPath(block.target) : [];\n const target = resolveHeadingPath(root, segments);\n applyOneAction(lines, target, block);\n }\n return lines.join('\\n');\n}\n\nexport interface RenderMeta {\n skillName: string;\n refinementId: string;\n /** ISO timestamp, caller-supplied — keeps rendering deterministic/testable (no internal `Date.now()`). */\n generatedAt: string;\n}\n\n/** Renders a `SKILL.extend.md` additive-extension file (always a single `append` block). */\nexport function renderExtendFile(meta: RenderMeta & { targetSection: string; content: string }): string {\n return [\n '# SKILL.extend.md',\n `# Extensions for: ${meta.skillName}`,\n `# Generated: ${meta.generatedAt}`,\n `# Refinement: ${meta.refinementId}`,\n '',\n `## EXTEND: ${meta.targetSection}`,\n '<!-- ACTION: append -->',\n meta.content,\n '',\n ].join('\\n');\n}\n\n/** Renders a `skill-config.json` config-override artifact. */\nexport function renderConfigOverride(meta: RenderMeta & { config: Record<string, unknown> }): string {\n const payload = {\n skill: meta.skillName,\n version: '1.0.0',\n generated: meta.generatedAt,\n config: meta.config,\n };\n return `${JSON.stringify(payload, null, 2)}\\n`;\n}\n\n/**\n * `full` / `hook` / `script` override types are whole-file overrides:\n * verbatim content, no transformation at all. This function exists so\n * call sites have one uniform rendering entry point per override type\n * (mirrors `renderExtendFile`/`renderConfigOverride`) rather than special-\n * casing \"just use the content\" three different ways.\n */\nexport function renderOverridePassthrough(content: string): string {\n return content;\n}\n\ntype DiffOpType = 'context' | 'add' | 'remove';\ninterface DiffOp {\n type: DiffOpType;\n line: string;\n}\n\n/** Line-level LCS diff. O(n*m) time/space — fine for skill-sized documents, not intended for huge files. */\nfunction computeLcsDiff(a: readonly string[], b: readonly string[]): DiffOp[] {\n const n = a.length;\n const m = b.length;\n const dp: number[][] = Array.from({ length: n + 1 }, () => new Array<number>(m + 1).fill(0));\n for (let i = n - 1; i >= 0; i--) {\n for (let j = m - 1; j >= 0; j--) {\n dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);\n }\n }\n\n const ops: DiffOp[] = [];\n let i = 0;\n let j = 0;\n while (i < n && j < m) {\n if (a[i] === b[j]) {\n ops.push({ type: 'context', line: a[i] });\n i++;\n j++;\n } else if (dp[i + 1][j] >= dp[i][j + 1]) {\n ops.push({ type: 'remove', line: a[i] });\n i++;\n } else {\n ops.push({ type: 'add', line: b[j] });\n j++;\n }\n }\n while (i < n) {\n ops.push({ type: 'remove', line: a[i] });\n i++;\n }\n while (j < m) {\n ops.push({ type: 'add', line: b[j] });\n j++;\n }\n return ops;\n}\n\n/**\n * A simple, deterministic unified-diff-style preview of `before` -> `after`\n * for `path`, stdlib-only. No hunk headers/context windowing — every line\n * is shown, prefixed ` `/`+`/`-`. Good enough for a human confirmation\n * prompt before a write; not a drop-in replacement for `diff -u`.\n */\nexport function previewDiff(before: string, after: string, path: string): string {\n const a = before.split('\\n');\n const b = after.split('\\n');\n const ops = computeLcsDiff(a, b);\n const lines = [`--- ${path}`, `+++ ${path}`];\n for (const op of ops) {\n const prefix = op.type === 'add' ? '+' : op.type === 'remove' ? '-' : ' ';\n lines.push(`${prefix}${op.line}`);\n }\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["join","readFileSync","join","readFileSync","existsSync","mkdirSync","statSync","writeFileSync","homedir","join","homedir","join","statSync","mkdirSync","writeFileSync","existsSync","randomUUID","existsSync","mkdirSync","readdirSync","basename","join","SKIP_DIRS","randomUUID","join","mkdirSync","existsSync","basename","readdirSync","walk","reject","cpSync","existsSync","mkdirSync","renameSync","rmSync","basename","join","readFileSync","readdirSync","join","SKIP_DIRS","walk","join","basename","existsSync","mkdirSync","rmSync","renameSync","cpSync","existsSync","mkdirSync","readFileSync","writeFileSync","dirname","existsSync","readFileSync","writeFileSync","mkdirSync","dirname","readFileSync","dirname","join","pkg","existsSync","readFileSync","statSync","writeFileSync","join","readdirSync","existsSync","readFileSync","basename","dirname","join","existsSync","lstatSync","readFileSync","readdirSync","realpathSync","renameSync","rmSync","statSync","writeFileSync","basename","delimiter","extname","join","resolvePath","statSync","delimiter","join","statSync","delimiter","join","statSync","basename","extname","basename","extname","statSync","existsSync","readFileSync","readdirSync","basename","dirname","join","resolve","sep","readFileSync","basename","dirname","existsSync","readdirSync","join","resolve","createHash","existsSync","readFileSync","join","createHash","join","existsSync","readFileSync","status","resolvePath","statSync","readdirSync","join","existsSync","realpathSync","readFileSync","basename","extname","find","delimiter","which","lstatSync","writeFileSync","renameSync","rmSync","join","finding","dirname","basename","existsSync","readFileSync","join","existsSync","mkdtempSync","readFileSync","readdirSync","rmSync","statSync","basename","dirname","extname","join","resolve","existsSync","readFileSync","extname","join","readFileSync","pkg","join","existsSync","extname","resolve","existsSync","statSync","join","MCP_SDK_DEP","MCP_KEYWORD","MCP_BIN_PREFIX","readFileSync","pkg","basename","extname","mkdtempSync","dirname","rmSync","readdirSync","m","existsSync","readFileSync","resolve","resolve","existsSync","readFileSync","round3","score","readFileSync","resolve","resolve","readFileSync","STOPWORDS","tokenize","intersectSize","unionSize","round3","score","finding","readdirSync","join","randomUUID","existsSync","mkdirSync","readFileSync","renameSync","rmSync","writeFileSync","join","join","existsSync","readFileSync","queue","mkdirSync","randomUUID","writeFileSync","renameSync","rmSync","existsSync","dirname","join","spawn","existsSync","homedir","createInterface","statSync","homedir","join","resolve","statSync","resolve","join","homedir","existsSync","join","resolvePath","createInterface","resolvePath","join","existsSync","createInterface","homedir","createInterface","existsSync","join","join","join","existsSync","existsSync","realpathSync","basename","join","resolvePath","sep","PRO_FEATURE_LABEL","basename","join","existsSync","realpathSync","resolvePath","sep","existsSync","createInterface","createInterface","createInterface","createHash","cpSync","existsSync","lstatSync","mkdtempSync","readFileSync","rmSync","tmpdir","basename","dirname","join","resolvePath","sep","PRO_FEATURE_LABEL","resolvePath","join","existsSync","sep","mkdtempSync","tmpdir","cpSync","rmSync","dirname","basename","createInterface","readFileSync","lstatSync","createHash","existsSync","existsSync","list","finding","resolvePath","existsSync","readFileSync","readdirSync","realpathSync","statSync","basename","join","resolvePath","PRO_FEATURE_LABEL","listSubdirs","readdirSync","join","statSync","findSkillDirsUnderRoot","existsSync","resolvePath","readFileSync","realpathSync","basename","resolvePath","search","data","status","realpathSync","join","resolvePath","createHash","readFileSync","existsSync","resolve","createHash","resolve","existsSync","readFileSync","PRO_FEATURE_LABEL","resolvePath","realpathSync","join","status","existsSync","rmSync","statSync","join","PRO_FEATURE_LABEL","audit","resolveReportPath","join","statSync","rmSync","existsSync","finding","quarantine","realpathSync","dirname","isAbsolute","relative","resolvePath","PRO_FEATURE_LABEL","status","realpathSync","relative","isAbsolute","resolvePath","dirname","queue","createHash","randomUUID","existsSync","lstatSync","mkdirSync","readFileSync","realpathSync","renameSync","rmSync","writeFileSync","dirname","isAbsolute","join","relative","resolvePath","sep","createInterface","existsSync","realpathSync","statSync","isAbsolute","join","relative","resolvePath","tokenize","unionSize","createHash","existsSync","readFileSync","statSync","realpathSync","join","resolvePath","join","readFileSync","existsSync","resolvePath","realpathSync","existsSync","readFileSync","statSync","sha256","createHash","join","resolveConfiguredRoot","resolvePath","realpathOrNull","realpathSync","isUnderRoot","relative","isAbsolute","join","existsSync","isFile","statSync","i","j","PRO_FEATURE_LABEL","requireProFeature","status","createInterface","resolveConfiguredRoot","resolvePath","isUnderRoot","relative","isAbsolute","readFileSync","lstatSync","mkdirSync","realpathSync","join","dirname","writeFileSync","existsSync","randomUUID","renameSync","rmSync","createHash","sep","require"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/commands/add.ts","../src/config.ts","../src/errors.ts","../src/gate/scanShared.ts","../src/preferences.ts","../src/install/resolve.ts","../src/util.ts","../src/install/quarantine.ts","../src/install/promote.ts","../src/install/fingerprint.ts","../src/install/promoteMcp.ts","../src/install/mcpCandidate.ts","../src/provenance.ts","../src/commands/gatePipeline.ts","../src/auditState.ts","../src/audit.ts","../src/gate/safety.ts","../src/gate/mcpSafety.ts","../src/gate/overlap.ts","../src/findings.ts","../src/license.ts","../src/gate/profile.ts","../src/gate/mcpCapabilities.ts","../src/gate/mcpOverlap.ts","../src/gate/skillMap.ts","../src/gate/mapOverlap.ts","../src/report/render.ts","../src/queue.ts","../src/handoff.ts","../src/commands/init.ts","../src/agents.ts","../src/commands/audit.ts","../src/decision.ts","../src/commands/scan.ts","../src/commands/list.ts","../src/commands/guide.ts","../src/commands/held.ts","../src/commands/config.ts","../src/commands/status.ts","../src/evolve.ts","../src/commands/evolve.ts","../src/commands/finding.ts","../src/commands/organize.ts","../src/organize.ts","../src/gate/skillsShApi.ts","../src/commands/find.ts","../src/commands/watch.ts","../src/gate/skillMapDrift.ts","../src/commands/routine.ts","../src/commands/ingest.ts","../src/commands/refine.ts","../src/refine/context.ts","../src/refine/models.ts","../src/refine/store.ts","../src/refine/patch.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { createRequire } from 'node:module';\nimport { add } from './commands/add.js';\nimport { scan } from './commands/scan.js';\nimport { list } from './commands/list.js';\nimport { guide } from './commands/guide.js';\nimport { configGet, configList, configPropose, configReview, configSet, configUnset } from './commands/config.js';\nimport { promoteHeld, rejectHeld } from './commands/held.js';\nimport { status } from './commands/status.js';\nimport { init } from './commands/init.js';\nimport { evolveCommand } from './commands/evolve.js';\nimport { auditCommand } from './commands/audit.js';\nimport { findingAcceptCommand, findingListCommand, findingRevokeCommand } from './commands/finding.js';\nimport { organizeCommand } from './commands/organize.js';\nimport { find } from './commands/find.js';\nimport { watchCommand } from './commands/watch.js';\nimport { routineCommand, type FailOn } from './commands/routine.js';\nimport { ingest, queueClose } from './commands/ingest.js';\nimport {\n refineCaptureCommand,\n refineListCommand,\n refinePatternsCommand,\n refinePromoteCommand,\n refineRollbackCommand,\n refineWhichCommand,\n} from './commands/refine.js';\nimport { UserError } from './errors.js';\nimport type { ArtifactType } from './install/resolve.js';\n\nconst require = createRequire(import.meta.url);\nconst pkg = require('../package.json') as { version: string };\n\n/** Commander option parser for `--artifact` — throws UserError (caught below) on anything but skill|mcp. */\nfunction parseArtifact(value: string): ArtifactType {\n if (value !== 'skill' && value !== 'mcp') {\n throw new UserError(`--artifact must be \"skill\" or \"mcp\" (got \"${value}\")`);\n }\n return value;\n}\n\n/** Commander option parser for `--lookback-hours` — throws UserError (caught below) on a non-finite, empty, or negative number. */\nfunction parseLookbackHours(value: string): number {\n const n = Number(value);\n if (value.trim() === '' || !Number.isFinite(n) || n < 0) {\n throw new UserError(`--lookback-hours must be a non-negative number (got \"${value}\")`);\n }\n return n;\n}\n\n/** Commander option parser for `find --limit` — throws UserError (caught below) on a non-positive-integer value. */\nfunction parseLimit(value: string): number {\n const n = Number(value);\n if (value.trim() === '' || !Number.isInteger(n) || n < 1 || n > 50) {\n throw new UserError(`--limit must be an integer between 1 and 50 (got \"${value}\")`);\n }\n return n;\n}\n\n/** Commander option parser for `routine --fail-on` — throws UserError (caught below) on an unknown level. */\nfunction parseFailOn(value: string): FailOn {\n const allowed: FailOn[] = ['none', 'low', 'medium', 'high', 'critical', 'any'];\n if (!(allowed as string[]).includes(value)) {\n throw new UserError(`--fail-on must be one of ${allowed.join('|')} (got \"${value}\")`);\n }\n return value as FailOn;\n}\n\n/** Commander option parser for `routine --prune-days` — throws UserError (caught below) on a non-positive value. */\nfunction parsePruneDays(value: string): number {\n const n = Number(value);\n if (value.trim() === '' || !Number.isFinite(n) || n < 1) {\n throw new UserError(`--prune-days must be a positive number (got \"${value}\")`);\n }\n return n;\n}\n\nconst program = new Command();\n\nprogram\n .name('skill-forge')\n .description('The supply-chain gate for agent skills')\n .version(pkg.version);\n\nprogram\n .command('add <source>')\n .description('Quarantine-install a skill and run it through the safety gate')\n .option('-y, --yes', 'skip the interactive prompt; honor the gate verdict automatically')\n .option('-t, --target <dir>', 'skills root to promote into (defaults to config defaultTarget, then skillsRoots[0])')\n .option('--json', 'print the gate result as JSON instead of the terminal report')\n .option('--ingest', 'after a successful promote, hand off the bundled ingest prompt to a coding agent')\n .option('--artifact <type>', 'gate as a \"skill\" or an \"mcp\" server (default: skill)', parseArtifact)\n .option(\n '--mcp-target <file>',\n 'MCP config file to promote into (mcp artifacts only; defaults to config.mcpTargets[0], then <cwd>/.mcp.json)'\n )\n .option('--force', 'overwrite an existing same-name MCP server entry when promoting (mcp artifacts only)')\n .option(\n '--skill-map <path>',\n 'path to a generated rhize-plugins skill map; reports existing-capability overlap against every skill node in it before promotion (curation-rule check)'\n )\n .action(add);\n\nprogram\n .command('scan <source>')\n .description('Run the safety gate against a skill without installing it')\n .option('--json', 'print the gate result as JSON instead of the terminal report')\n .option('--artifact <type>', 'gate as a \"skill\" or an \"mcp\" server (default: skill)', parseArtifact)\n .option(\n '--skill-map <path>',\n 'path to a generated rhize-plugins skill map; reports existing-capability overlap against every skill node in it (curation-rule check)'\n )\n .action(scan);\n\nprogram\n .command('list')\n .description('List skills currently in quarantine')\n .action(list);\n\nprogram\n .command('status')\n .description('Show skill-forge configuration and quarantine status')\n .action(status);\n\nprogram\n .command('promote <id>')\n .description('Re-gate a held quarantine entry and promote it (finishes a decision left on hold)')\n .option('-t, --target <dir>', 'skills root to promote into (defaults to config defaultTarget, then skillsRoots[0])')\n .option('--force', 'overwrite an existing same-name skill dir / MCP server entry in the target')\n .option('--mcp-target <file>', 'MCP config file to promote a held MCP candidate into (defaults to config.mcpTargets[0], then <cwd>/.mcp.json)')\n .option('-y, --yes', 'skip the prompt; honor the re-gate verdict automatically')\n .option('--json', 'print the re-gate result as JSON instead of the terminal report')\n .action(promoteHeld);\n\nprogram\n .command('reject <id>')\n .description('Discard a held quarantine entry without promoting it')\n .option('-y, --yes', 'skip the confirmation prompt')\n .action(rejectHeld);\n\nconst configCmd = program\n .command('config')\n .description('Read and write open-ended preferences (agent suggestions go to a review queue, never straight in)');\nconfigCmd\n .command('list', { isDefault: true })\n .description('Show accepted preferences and any pending proposals')\n .option('--json', 'print as JSON')\n .action(configList);\nconfigCmd\n .command('get <key>')\n .description('Print one preference value')\n .option('--json', 'print the full entry as JSON')\n .action(configGet);\nconfigCmd\n .command('set <key> <value>')\n .description('Set a preference you are stating yourself (takes effect immediately)')\n .option('--note <text>', 'why this is true')\n .option('--origin <text>', 'where it came from')\n .action(configSet);\nconfigCmd\n .command('unset <key>')\n .description('Remove a preference')\n .action(configUnset);\nconfigCmd\n .command('propose <key> <value>')\n .description('Queue a preference for review — the agent-facing door; never takes effect on its own')\n .option('--note <text>', 'why the agent believes this')\n .option('--origin <text>', 'which agent/session proposed it')\n .action(configPropose);\nconfigCmd\n .command('review')\n .description('Review pending agent-proposed preferences one at a time')\n .option('--accept-all', 'accept every pending proposal without prompting')\n .option('--reject-all', 'reject every pending proposal without prompting')\n .action(configReview);\n\nprogram\n .command('guide [topic]')\n .description('What this tool does, what state you are in, and the next command to run')\n .action(guide);\n\nprogram\n .command('evolve <target-skill-dir>')\n .description('Self-evolve a skill via SkillOpt-Sleep, re-gate the proposal, then promote/hold/reject (Pro)')\n .option('--project <dir>', 'project dir passed to SkillOpt-Sleep as --project (defaults to cwd)')\n .option('--dry-run', 'SkillOpt-Sleep dry-run: report only, nothing staged')\n .option('--backend <name>', 'SkillOpt-Sleep backend (default: mock, offline — a real backend requires --yes or interactive confirmation)')\n .option('--lookback-hours <n>', 'hours of session history for SkillOpt-Sleep to harvest', parseLookbackHours)\n .option('-y, --yes', 'skip interactive prompts; honor the re-gate verdict automatically')\n .option('--json', 'print the re-gate result as JSON instead of the terminal report')\n .option('--force', 'allow re-adopting a staging dir whose proposed content already matches the live skill')\n .action(evolveCommand);\n\nprogram\n .command('audit')\n .alias('doctor')\n .description('Doctor-style health check over the configured skill/MCP set: inventory, hygiene findings, and consolidation/refinement opportunities')\n .option('--json', 'print the audit report as JSON instead of the terminal summary (non-interactive: skips the business-profile prompt)')\n .option('-y, --yes', 'skip interactive prompts (business profile, foundation, handoff); never implies --foundation or --handoff')\n .option('--report <file>', 'write the report to this path instead of the default ~/.skill-forge/reports/audit-<timestamp>.md (refuses an existing path)')\n .option('--foundation', 'authorizes exactly one skills-root write: scaffold a business-foundation skill from the captured business profile')\n .option('--handoff', 'authorizes launching the configured agent with the bundled curation prompt against the written report')\n .action(auditCommand);\n\nconst finding = program\n .command('finding')\n .description('Review and acknowledge audit findings — human-only, never a gate input (see docs/accepted-findings-schema.md)');\nfinding\n .command('accept <fingerprint-prefix>')\n .description('Accept a currently-active LOW/MEDIUM finding (no override for HIGH/CRITICAL); re-runs the audit engine to resolve it')\n .requiredOption('--reason <text>', 'why this finding is accepted (mandatory, 1-200 chars, no control characters)')\n .option('--json', 'print the accepted record as JSON')\n .action(findingAcceptCommand);\nfinding\n .command('revoke <fingerprint-prefix>')\n .description('Remove a stored acceptance so the finding reports again')\n .option('--json', 'print the removed record as JSON')\n .action(findingRevokeCommand);\nfinding\n .command('list', { isDefault: true })\n .description('List every accepted finding')\n .option('--stale', 'only records whose target no longer exists on disk')\n .option('--json', 'print records as JSON')\n .action(findingListCommand);\n\nprogram\n .command('organize')\n .description('Build the set-level capability registry + dependency graph across configured skills roots (Pro)')\n .option('--json', 'print the registry+graph report as JSON instead of the terminal summary')\n .option('--out <file>', 'write the full report JSON to this path (refuses an existing path)')\n .option('--usage-snapshot <file>', 'join per-skill usage counts from a skill-monitor snapshot JSON')\n .action(organizeCommand);\n\nprogram\n .command('find [query]')\n .description('Discover skills via skills.sh and check partner security audits (requires VERCEL_OIDC_TOKEN)')\n .option('--limit <n>', 'max search results (default 10)', parseLimit)\n .option('--audit <id>', 'show partner audit verdicts (Socket/Snyk/…) for a skills.sh id')\n .option('--get <id>', 'show detail + file tree for a skills.sh id')\n .option('--curated', 'list skills.sh curated skills')\n .option('--json', 'print the raw API payload as JSON')\n .action(find);\n\nprogram\n .command('watch')\n .description('Drift check across every SOURCES.md provenance ledger: which ingested upstreams have moved (Pro)')\n .option('--offline', 'skip all network checks; list entries for manual comparison only')\n .option('--json', 'print the drift report as JSON instead of the terminal summary')\n .option(\n '--skill-map <path>',\n 'also drift-check every fork-of edge in a generated rhize-plugins skill map (contentHash compare against upstream)'\n )\n .action(watchCommand);\n\nprogram\n .command('routine')\n .description('One scheduled maintenance pass: audit + drift + registry, one report, cron-friendly exit codes (Pro)')\n .option('--json', 'print the routine result as JSON instead of the terminal summary')\n .option('--offline', 'skip every network check (drift entries are reported unchecked)')\n .option('--skip-watch', 'skip the drift check')\n .option('--skip-organize', 'skip the capability registry')\n .option('--fail-on <level>', 'exit 1 when a finding at or above this severity exists: none|low|medium|high|critical|any (default none)', parseFailOn)\n .option('--housekeeping', 'opt in to bounded writes: prune expired quarantine sandboxes, close orphaned queue entries')\n .option('--prune-days <n>', 'quarantine age threshold in days for --housekeeping (default 30)', parsePruneDays)\n .option('--dry-run', 'with --housekeeping: report what would change, change nothing')\n .option('--handoff', 'hand the written report to the configured agent (curation prompt)')\n .option('--report <file>', 'write the report here instead of ~/.skill-forge/reports/routine-<timestamp>.md (refuses an existing path)')\n .action(routineCommand);\n\nprogram\n .command('ingest')\n .description(\n 'Hand the pending-ingestion queue to the configured coding agent for the decide/absorb pass (Pro). ' +\n 'For a single new source, use `skill-forge add <source> --ingest` instead.'\n )\n .option('--list', 'print pending queue entries and exit — no handoff')\n .option('--json', 'with --list: print pending entries as JSON')\n .action(ingest);\n\nconst queue = program.command('queue').description('Manage the pending-ingestion queue (Pro)');\nqueue\n .command('close <id>')\n .description('Close a queue entry after the decide pass — agents use this instead of hand-editing queue.json')\n .requiredOption('--status <status>', 'final status: \"ingested\" or \"dismissed\"')\n .action(queueClose);\n\nconst refine = program\n .command('refine')\n .description('Capture, apply, and generalize improvements to installed skills from usage feedback (Pro)');\nrefine\n .command('capture', { isDefault: true })\n .description('Record a refinement and write a project-scope override artifact (never touches the base skill)')\n .option('--skill <name>', 'target skill name')\n .option('--category <cat>', 'trigger|content|hook|tool|pattern|config|new')\n .option('--target <section>', 'heading path or file the refinement targets, e.g. \"Hooks > duplicate-check\"')\n .option('--override-type <type>', 'patch|extend|config|full|hook|script|new')\n .option('--action <action>', 'patch action: append|prepend|replace-section|insert-after|insert-before|delete-section')\n .option('--marker <text>', 'marker text for insert-* / replace-section / delete-section (filename for full|hook|script)')\n .option('--content <text>', 'override content inline')\n .option('--content-file <file>', 'read override content from a file')\n .option('--expected <text>', 'expected behavior')\n .option('--actual <text>', 'actual observed behavior')\n .option('--example <text>', 'concrete example of the gap')\n .option('--outcome <text>', 'desired outcome')\n .option('--root-cause <text>', 'root cause per the gap analysis')\n .option('--pattern-id <id>', 'join an existing pattern (PAT-xxxx) instead of fingerprint matching')\n .option('--scope <scope>', 'where to write the artifact: \"local\" (.claude/skills, default) or \"shared\" (./skills)')\n .option('--dry-run', 'preview the artifact diff; write nothing')\n .option('--json', 'print the result as JSON')\n .option('-y, --yes', 'skip the interactive confirm; never implies --handoff')\n .option('--handoff', 'authorizes launching the configured agent with the bundled refine prompt')\n .action(refineCaptureCommand);\nrefine\n .command('list')\n .description('Show refinement history from the user-scope store')\n .option('--status <status>', 'filter by refinement status')\n .option('--skill <name>', 'filter by skill')\n .option('--project <name>', 'filter by project')\n .option('--json', 'print records as JSON')\n .action(refineListCommand);\nrefine\n .command('patterns')\n .description('Review tracked patterns and the generalization queue')\n .option('--status <status>', 'filter: tracking|ready|generalized|dismissed')\n .option('--skill <name>', 'filter by affected skill')\n .option('--project <name>', 'filter by project')\n .option('--json', 'print patterns as JSON')\n .action(refinePatternsCommand);\nrefine\n .command('which <skill>')\n .description('Print override-scope resolution order and which override files exist at each scope')\n .option('--user-root <dir>', 'explicit user-scope skills root')\n .option('--json', 'print the resolution as JSON')\n .action(refineWhichCommand);\nrefine\n .command('promote <pattern-id>')\n .description('Materialize a READY pattern into the user-scope skill default (backup + manifest first)')\n .option('--force', 'promote a pattern that is not READY')\n .option('--dry-run', 'preview the user-scope diff; write nothing')\n .option('--user-root <dir>', 'explicit user-scope skills root')\n .option('--json', 'print the result as JSON')\n .option('-y, --yes', 'skip the interactive confirm')\n .action(refinePromoteCommand);\nrefine\n .command('rollback <backup-id>')\n .description('Restore a promotion backup (refuses if files changed since promotion unless --force)')\n .option('--force', 'restore even if current files differ from the post-promotion state')\n .option('--json', 'print the result as JSON')\n .option('-y, --yes', 'skip the interactive confirm')\n .action(refineRollbackCommand);\n\nprogram\n .command('init')\n .description('Detect installed agents and set gate targets, default promotion target, and handoff agent')\n .option('--defaults', 'non-interactive: use all detected agents as gate targets, the first as the default target')\n .option('--list', 'print detected agent skill roots and exit — no writes')\n .option('--all-agents', 'pre-select every detected root, not just those of agents whose CLI is on PATH')\n .action(init);\n\ntry {\n await program.parseAsync();\n} catch (err) {\n if (err instanceof UserError) {\n console.error(`skill-forge: ${err.message}`);\n process.exit(1);\n }\n\n if (process.env.DEBUG === 'skill-forge') {\n console.error(err);\n } else {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`skill-forge: unexpected error: ${message}`);\n console.error('Re-run with DEBUG=skill-forge to see the full stack trace.');\n }\n process.exit(1);\n}\n","import { join } from 'node:path';\nimport { loadConfig, type Config } from '../config.js';\nimport { resolve as resolveSource, type ArtifactType } from '../install/resolve.js';\nimport { quarantine } from '../install/quarantine.js';\nimport { promote, reject, hold } from '../install/promote.js';\nimport { fingerprintDirs } from '../install/fingerprint.js';\nimport { promoteMcp } from '../install/promoteMcp.js';\nimport type { McpServerProfile } from '../install/promoteMcp.js';\nimport { resolveMcpCandidateDetail } from '../install/mcpCandidate.js';\nimport { appendProvenance } from '../provenance.js';\nimport { printGateOutput, runGate, resolveMcpCandidatePath } from './gatePipeline.js';\nimport { formatSafetyFinding } from '../gate/scanShared.js';\nimport { suggestVerbCode } from '../gate/overlap.js';\nimport { appendQueueEntry } from '../queue.js';\nimport { runHandoff } from '../handoff.js';\nimport { maybeOfferInit } from './init.js';\nimport { logOutcome, promptDecision, yesDecision } from '../decision.js';\n\nconst PRO_FEATURE_LABEL = 'overlap analysis, the provenance ledger, and the pending-ingestion queue';\nconst MCP_PRO_FEATURE_LABEL = 'overlap analysis and the pending-ingestion queue';\n\nexport interface AddOptions {\n yes?: boolean;\n target?: string;\n json?: boolean;\n ingest?: boolean;\n /** Gate/promote as a skill (default) or an MCP server — v0.5. */\n artifact?: ArtifactType;\n /** MCP config file to promote into (mcp artifacts only). Defaults to config.mcpTargets[0], then <cwd>/.mcp.json. */\n mcpTarget?: string;\n /** Overwrite an existing same-name MCP server entry when promoting (mcp artifacts only). */\n force?: boolean;\n /** Path to a generated rhize-plugins skill map; reports existing-capability overlap against it before promotion (skill artifacts only). */\n skillMap?: string;\n}\n\n/**\n * `skill-forge add <source>` — quarantine-install a skill and run it through\n * the safety gate (profile -> safety scan -> overlap analysis -> report ->\n * promote/hold/reject). `--yes` skips the interactive prompt and honors the\n * gate verdict; otherwise the decision is asked interactively.\n */\nexport async function add(source: string, options: AddOptions = {}): Promise<void> {\n await maybeOfferInit({ json: options.json, yes: options.yes });\n\n const config = loadConfig();\n const artifact: ArtifactType = options.artifact ?? 'skill';\n\n if (artifact === 'mcp') {\n await addMcp(source, config, options);\n return;\n }\n\n const targetRoot = options.target ?? config.defaultTarget ?? config.skillsRoots[0];\n if (!targetRoot) {\n throw new Error('no target skills root configured; pass --target <dir> or set skillsRoots in the config');\n }\n\n const resolved = resolveSource(source, artifact);\n const entry = await quarantine(resolved, config.quarantineDir);\n // Captured BEFORE the gate reads anything, and re-checked inside promote(): together they prove\n // the bytes that were scanned are the bytes that get installed, across both the scan and the\n // (potentially long) interactive decision. See src/install/fingerprint.ts.\n const gatedFingerprint = fingerprintDirs(entry.skillDirs).digest;\n\n if (entry.skillDirs.length === 0) {\n console.error(`[skill-forge] no SKILL.md found in installed source: ${source}`);\n reject(entry);\n process.exitCode = 1;\n return;\n }\n if (entry.skillDirs.length > 1) {\n console.error(\n `[skill-forge] ${entry.skillDirs.length} skills found in ${source}; gating the first — the whole ` +\n 'quarantine entry is promoted/rejected together:\\n' +\n entry.skillDirs.map((d) => ` - ${d}`).join('\\n')\n );\n }\n\n const { safety, gateResult, proEnabled } = await runGate(entry.skillDirs[0], targetRoot, {\n proFeatureLabel: PRO_FEATURE_LABEL,\n strictness: config.strictness,\n artifact,\n skillMapPath: options.skillMap,\n });\n const { profile, overlap } = gateResult;\n if (profile.kind !== 'skill') {\n // Unreachable in practice: runGate()'s asProfile() already throws before\n // returning a non-skill profile for artifact 'skill'. Narrows the type.\n throw new Error(`expected a skill candidate, found a ${profile.kind}`);\n }\n\n printGateOutput(gateResult, options.json);\n\n // --json implies non-interactive: honor the gate verdict the same way --yes does, never prompt.\n const decision =\n options.yes || options.json ? yesDecision(safety.verdict) : await promptDecision(safety.verdict, 'this skill');\n\n let installedPath: string | null = null;\n\n if (decision === 'promote') {\n const result = promote(entry, targetRoot, { expectedFingerprint: gatedFingerprint });\n logOutcome(`[skill-forge] promoted -> ${result.promoted.join(', ')}`, options.json);\n // Free-tier `add` makes no absorb/fork judgment — it either installs the\n // candidate as-is or it doesn't. DEFER is the matching verb from the\n // five-verb matrix (\"adopt as-is; just install/keep it\").\n // Provenance recording is Pro — see the notice printed above the report.\n if (proEnabled) {\n // Wrapped for the same reason commands/held.ts wraps its copy: a ledger-write failure AFTER\n // a successful promote must not surface as a bare throw that hides the promote that did\n // happen. Name the target instead, so the entry can be recorded by hand.\n try {\n const ledgerPath = appendProvenance(\n {\n name: profile.name ?? entry.id,\n source,\n version: profile.version,\n license: profile.license,\n verb: 'DEFER',\n target: targetRoot,\n took: `installed as-is (${result.promoted.length} skill dir${result.promoted.length === 1 ? '' : 's'})`,\n notes:\n `gate: safety=${safety.verdict}` +\n (overlap ? `, overlap nearest=${overlap.nearest} (${overlap.nearestScore})` : ''),\n },\n targetRoot\n );\n logOutcome(`[skill-forge] provenance recorded -> ${ledgerPath}`, options.json);\n } catch (err) {\n console.error(\n `[skill-forge] promoted into ${targetRoot}, but recording provenance failed: ${(err as Error).message}`\n );\n }\n }\n installedPath = result.promoted[0] ?? null;\n } else if (decision === 'reject') {\n reject(entry);\n logOutcome('[skill-forge] rejected — quarantine sandbox removed', options.json);\n if (safety.verdict === 'block') process.exitCode = 1;\n return; // rejected candidates are gone — nothing to queue for ingestion\n } else {\n hold(entry);\n logOutcome(`[skill-forge] held in quarantine -> ${entry.quarantinePath}`, options.json);\n }\n\n // Pending-ingestion queue and --ingest handoff are Pro; free `add` stops\n // at promote/hold, which is the complete free-tier flow. (Unlicensed/locked\n // case is covered by the notice printed above the report, not here.)\n if (proEnabled) {\n // The queue entry records the gate result at decision time so a later\n // ingest pass doesn't need to re-run it.\n appendQueueEntry({\n source,\n sourceType: resolved.type,\n installedPath,\n quarantinePath: entry.quarantinePath,\n gate: {\n license: profile.license ?? null,\n safetyVerdict: safety.verdict,\n safetyFindings: safety.findings.map(formatSafetyFinding),\n overlapTop: overlap ? overlap.ranking.slice(0, 3).map((r) => ({ skill: r.skill, score: r.score })) : [],\n suggestedVerb: overlap ? suggestVerbCode(overlap.nearestScore) : null,\n },\n status: 'pending',\n });\n\n if (options.ingest && installedPath) {\n // Runs the bundled agent-neutral ingest prompt (assets/ingest-prompt.md) via the\n // configured handoffCommand template, or a detected-agent fallback. See src/handoff.ts.\n runHandoff(config, installedPath);\n }\n }\n}\n\n/**\n * MCP branch of `add` (v0.5, `--artifact mcp`): quarantine-install an MCP\n * server (local dir / git URL / npm package — see `install/resolve.ts` and\n * `install/quarantine.ts`), gate it (`mcpScan` + MCP overlap via\n * `runGate`'s `artifact: 'mcp'` routing), then promote/hold/reject. Promote\n * writes the server entry into an MCP config file via `promoteMcp` instead of\n * moving anything into a skills root — there is no skill-style provenance\n * ledger for MCP artifacts (`SOURCES.md` is a skill-ingestion ledger; nothing\n * analogous exists for MCP config writes), but the pending-ingestion queue\n * and `--ingest` handoff both apply the same way, keyed on the written config\n * file path.\n */\nasync function addMcp(source: string, config: Config, options: AddOptions): Promise<void> {\n const resolved = resolveSource(source, 'mcp');\n const entry = await quarantine(resolved, config.quarantineDir);\n const candidatePath = resolveMcpCandidatePath(entry);\n\n const { safety, gateResult, proEnabled } = await runGate(candidatePath, '', {\n proFeatureLabel: MCP_PRO_FEATURE_LABEL,\n strictness: config.strictness,\n artifact: 'mcp',\n mcpTargets: config.mcpTargets ?? [],\n });\n const { profile, overlap } = gateResult;\n if (profile.kind !== 'mcp') {\n // Unreachable in practice: runGate()'s asProfile() already throws before\n // returning a non-mcp profile for artifact 'mcp'. Narrows the type.\n throw new Error(`expected an MCP candidate, found a ${profile.kind}`);\n }\n if (profile.serverCount > 1) {\n console.error(\n `[skill-forge] ${profile.serverCount} MCP servers found in ${source}; gating/promoting the first — ` +\n `the whole quarantine entry is promoted/rejected together:\\n ${profile.servers.join(', ')}`\n );\n }\n\n printGateOutput(gateResult, options.json);\n\n // --json implies non-interactive: honor the gate verdict the same way --yes does, never prompt.\n const decision =\n options.yes || options.json ? yesDecision(safety.verdict) : await promptDecision(safety.verdict, 'this skill');\n\n let installedPath: string | null = null;\n\n if (decision === 'promote') {\n const detail = resolveMcpCandidateDetail(profile);\n const serverProfile: McpServerProfile = {\n name: detail.name,\n // version may be absent — promoteMcp refuses the unpinnable npx fallback\n // rather than writing \"npx pkg@unknown\"; see deriveServerEntry in promoteMcp.ts.\n version: detail.version,\n // packageName (v0.7) — the candidate's own scanned package.json name, so\n // deriveServerEntry can auto-pin an unpinned npx spec in candidateEntry\n // only when it provably matches the package that was actually scanned.\n packageName: detail.packageName,\n envVars: detail.env ? Object.keys(detail.env) : undefined,\n candidateEntry: detail.command ? { command: detail.command, args: detail.args, env: detail.env } : undefined,\n };\n const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join(process.cwd(), '.mcp.json');\n\n const result = promoteMcp(entry, serverProfile, mcpTargetPath, { force: options.force });\n reject(entry); // promoteMcp doesn't touch the quarantine sandbox — clean it up now that the entry is written\n\n logOutcome(`[skill-forge] promoted -> ${result.target} (server \"${result.serverName}\")`, options.json);\n if (result.autoPinned) {\n logOutcome(`[skill-forge] pinned launch spec ${result.autoPinned.from} -> ${result.autoPinned.to}`, options.json);\n }\n if (result.backupPath) {\n logOutcome(`[skill-forge] backed up existing config -> ${result.backupPath}`, options.json);\n }\n if (result.envVarsToFill.length > 0) {\n logOutcome(`[skill-forge] fill in these env vars before use: ${result.envVarsToFill.join(', ')}`, options.json);\n }\n installedPath = result.target;\n } else if (decision === 'reject') {\n reject(entry);\n logOutcome('[skill-forge] rejected — quarantine sandbox removed', options.json);\n if (safety.verdict === 'block') process.exitCode = 1;\n return; // rejected candidates are gone — nothing to queue for ingestion\n } else {\n hold(entry);\n logOutcome(`[skill-forge] held in quarantine -> ${entry.quarantinePath}`, options.json);\n }\n\n // Pending-ingestion queue and --ingest handoff are Pro; free `add` stops\n // at promote/hold, which is the complete free-tier flow.\n if (proEnabled) {\n appendQueueEntry({\n source,\n sourceType: resolved.type,\n installedPath,\n quarantinePath: entry.quarantinePath,\n gate: {\n license: null,\n safetyVerdict: safety.verdict,\n safetyFindings: safety.findings.map(formatSafetyFinding),\n overlapTop: overlap ? overlap.ranking.slice(0, 3).map((r) => ({ skill: r.skill, score: r.score })) : [],\n suggestedVerb: overlap ? suggestVerbCode(overlap.nearestScore) : null,\n },\n status: 'pending',\n artifactType: 'mcp',\n capabilities: profile.capabilities,\n });\n\n if (options.ingest && installedPath) {\n // Same generic handoff as the skill path — {path} substitutes to the\n // written MCP config file rather than an installed skill dir.\n runHandoff(config, installedPath);\n }\n }\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport {\n normalizePreferences,\n normalizeProposals,\n type PreferenceEntry,\n type PreferenceProposal,\n} from './preferences.js';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nexport type Strictness = 'block-high' | 'block-medium' | 'warn-only';\n\n/** Single source of truth for the strictness fallback — used by `defaultConfig()` and the gate's default parameters. */\nexport const DEFAULT_STRICTNESS: Strictness = 'block-high';\n\n/**\n * Business context captured interactively by `skill-forge audit` (v0.8, never invented) —\n * grounds the business-foundation skill scaffold and the curation handoff prompt. See\n * src/audit.ts.\n */\nexport interface BusinessProfile {\n businessName?: string;\n industry?: string;\n audiences: string[];\n workflows: string[];\n constraints: string[];\n /** ISO-8601 timestamp of the last time this profile was captured/confirmed. */\n updatedAt: string;\n}\n\nexport interface Config {\n skillsRoots: string[];\n quarantineDir: string;\n strictness: Strictness;\n /** Pro license key (skf1.<payload>.<signature>). SKILL_FORGE_LICENSE env takes precedence — see src/license.ts. */\n licenseKey?: string;\n /** Default skills root to promote into when `add`'s `--target` is omitted. Set by `skill-forge init`. */\n defaultTarget?: string;\n /** Agent-to-skillsRoot mapping recorded by `skill-forge init` (see src/agents.ts for the agent matrix). */\n agents?: { id: string; skillsRoot: string }[];\n /** Argv-style template for handing a prompt to the chosen handoff agent, e.g. `['claude', '-p', '{prompt}']`. Set by `skill-forge init`. See src/handoff.ts. */\n handoffCommand?: string[];\n /** MCP config file targets recorded by `skill-forge init` (see Agent#mcpConfig in src/agents.ts). Consulted by MCP `promote` as a fallback when `--mcp-target` is omitted. */\n mcpTargets?: string[];\n /** Business context captured by `skill-forge audit`'s interactive Q&A. See BusinessProfile above. */\n businessProfile?: BusinessProfile;\n /** Path to the scaffolded business-foundation skill, once created by `skill-forge audit --foundation`. See src/audit.ts. */\n foundationSkillPath?: string;\n /**\n * Open-ended learned/stated facts about this user, keyed by a kebab-case name (v0.11). The\n * schema-free counterpart to `businessProfile`'s five fixed fields — see src/preferences.ts.\n * Accepted values only; agent suggestions live in `proposedPreferences` until reviewed.\n */\n preferences?: Record<string, PreferenceEntry>;\n /** Agent-proposed preferences awaiting `skill-forge config review`. NEVER read as effective values. */\n proposedPreferences?: PreferenceProposal[];\n}\n\n/**\n * Resolves the skill-forge home directory. Honors SKILL_FORGE_HOME so tests\n * (and users) can redirect config/quarantine storage away from ~/.skill-forge.\n */\nexport function getHomeDir(): string {\n return process.env.SKILL_FORGE_HOME ?? join(homedir(), '.skill-forge');\n}\n\nexport function getConfigPath(): string {\n return join(getHomeDir(), 'config.json');\n}\n\n/**\n * Repairs a possibly hand-edited `businessProfile` rather than letting a malformed one crash\n * later: `audiences`/`workflows`/`constraints` MUST be arrays of strings (consumed by\n * `bulletList()` in `src/audit.ts`'s foundation-skill renderer) — a missing or wrong-shaped\n * value is coerced to `[]` instead of propagating a raw TypeError out of `renderFoundationSkillMd`.\n * `businessName`/`industry`/`updatedAt` pass through unchanged.\n */\nfunction normalizeBusinessProfile(raw: BusinessProfile | undefined): BusinessProfile | undefined {\n if (!raw || typeof raw !== 'object') return undefined;\n const toStringArray = (v: unknown): string[] => (Array.isArray(v) ? v.filter((x): x is string => typeof x === 'string') : []);\n return {\n ...raw,\n audiences: toStringArray(raw.audiences),\n workflows: toStringArray(raw.workflows),\n constraints: toStringArray(raw.constraints),\n };\n}\n\n/**\n * Collapses a path list to its distinct entries, first occurrence wins.\n *\n * Repairs configs written before init deduped its own detection (v0.11): many agents legitimately\n * share one skills root (18 of the 73 entries in src/agents.ts use `.agents/skills`), and the\n * pre-fix wizard wrote one entry per AGENT — so `skillsRoots` could hold the same path 18 times,\n * making every root-iterating command (`watch`, `organize`, `refine rollback`, `ingest`) do the\n * same work, and report the same finding, once per duplicate. String-identical only: this is\n * cheap normalization, not a symlink/realpath equivalence check.\n */\nfunction uniquePaths(paths: string[] | undefined): string[] | undefined {\n return paths ? [...new Set(paths)] : paths;\n}\n\nexport function defaultConfig(): Config {\n const home = getHomeDir();\n return {\n skillsRoots: [join(process.cwd(), '.claude', 'skills')],\n quarantineDir: join(home, 'quarantine'),\n strictness: DEFAULT_STRICTNESS,\n };\n}\n\n/**\n * Loads config from disk, falling back to defaults for any missing keys.\n * Does not write anything to disk.\n */\nexport function loadConfig(): Config {\n const configPath = getConfigPath();\n const defaults = defaultConfig();\n\n if (!existsSync(configPath)) {\n return defaults;\n }\n\n const raw = readFileSync(configPath, 'utf-8');\n const parsed = JSON.parse(raw) as Partial<Config>;\n\n return {\n skillsRoots: uniquePaths(parsed.skillsRoots) ?? defaults.skillsRoots,\n quarantineDir: parsed.quarantineDir ?? defaults.quarantineDir,\n strictness: parsed.strictness ?? defaults.strictness,\n licenseKey: parsed.licenseKey ?? defaults.licenseKey,\n defaultTarget: parsed.defaultTarget ?? defaults.defaultTarget,\n agents: parsed.agents ?? defaults.agents,\n handoffCommand: parsed.handoffCommand ?? defaults.handoffCommand,\n mcpTargets: uniquePaths(parsed.mcpTargets) ?? defaults.mcpTargets,\n businessProfile: normalizeBusinessProfile(parsed.businessProfile) ?? defaults.businessProfile,\n foundationSkillPath: parsed.foundationSkillPath ?? defaults.foundationSkillPath,\n // Both are repaired on read (invalid/secret-shaped entries dropped) — config.json is a plain\n // file anything can edit, and these render straight into a terminal. See src/preferences.ts.\n preferences: normalizePreferences(parsed.preferences) ?? defaults.preferences,\n proposedPreferences: normalizeProposals(parsed.proposedPreferences) ?? defaults.proposedPreferences,\n };\n}\n\nexport function saveConfig(config: Config): void {\n const home = getHomeDir();\n if (!existsSync(home)) {\n mkdirSync(home, { recursive: true });\n }\n writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n}\n","/**\n * Friendly, expected-failure errors — bad input, unreachable sources, missing\n * config, etc. Thrown anywhere in the pipeline and caught once at the top\n * level (src/cli.ts), which prints just the message and exits 1 instead of a\n * raw stack trace. Anything that is NOT a UserError is treated as a bug: the\n * top-level handler prints a short message plus a hint to re-run with\n * DEBUG=skill-forge to see the full stack.\n */\nexport class UserError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'UserError';\n }\n}\n","import { readFileSync, readdirSync, statSync } from 'node:fs';\nimport { extname, join } from 'node:path';\nimport type { Strictness } from '../config.js';\n\n/**\n * Helpers shared between the gate scanners — `gate/safety.ts` (the base\n * deny-ruleset scanner), `gate/mcpSafety.ts` (MCP-specific findings layered on\n * top of it), and `gate/mcpCapabilities.ts` (static capability profiling): the\n * file-tree walker, safe text-file reader, and the severity->verdict mapping.\n * These modules import from here instead of maintaining their own copies —\n * previously these were duplicated per file and risked drifting out of sync.\n */\n\nexport type Severity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';\nexport type Verdict = 'pass' | 'warn' | 'block';\n\nexport interface SafetyFinding {\n severity: Severity;\n rule: string;\n file: string;\n detail: string;\n /**\n * Content-derived identity component for accepted-finding fingerprinting (v0.12, `src/findings.ts`)\n * — the offending line's text, or a rule-specific stable token. Deliberately additive/optional: a\n * rule that doesn't set it falls back to `detail` at the fingerprinting boundary, never to `''`\n * (an unpopulated context must still fail open when the message changes). See\n * `docs/accepted-findings-schema.md`'s per-rule context table for what each rule sets here and why.\n */\n context?: string;\n}\n\nexport interface SafetyResult {\n findings: SafetyFinding[];\n verdict: Verdict;\n}\n\nconst SKIP_DIRS = new Set(['.git', 'node_modules', 'dist', 'build', '__pycache__', '.venv', 'venv']);\nconst BINARY_EXT = new Set([\n '.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.pdf', '.zip', '.tar', '.gz',\n '.woff', '.woff2', '.ttf', '.eot', '.mp4', '.mp3', '.wasm', '.bin', '.exe',\n]);\nconst MAX_FILE_BYTES = 2 * 1024 * 1024; // 2MB — skills/servers ship small text/scripts\n\nexport function* walk(dir: string): Generator<string> {\n let entries;\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue;\n yield* walk(full);\n } else if (entry.isFile()) {\n yield full;\n }\n }\n}\n\nexport function readTextFile(file: string): string | undefined {\n if (BINARY_EXT.has(extname(file).toLowerCase())) return undefined;\n try {\n if (statSync(file).size > MAX_FILE_BYTES) return undefined;\n const content = readFileSync(file, 'utf-8');\n if (content.slice(0, 512).includes('\\0')) return undefined; // crude binary sniff\n return content;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Ported from skill_safety.py's SkillSpector severity->verdict mapping\n * (LOW allow, MEDIUM caution, HIGH/CRITICAL block).\n *\n * `config.strictness` (see src/config.ts) selects which severities block:\n * `block-high` (default) is the mapping above; `block-medium` also blocks on\n * MEDIUM; `warn-only` never blocks (findings still surface, capped at warn).\n */\nconst BLOCK_SEVERITIES_BY_STRICTNESS: Record<Strictness, ReadonlySet<Severity>> = {\n 'block-high': new Set(['HIGH', 'CRITICAL']),\n 'block-medium': new Set(['MEDIUM', 'HIGH', 'CRITICAL']),\n 'warn-only': new Set(),\n};\n\nexport function verdictFor(findings: SafetyFinding[], strictness: Strictness): Verdict {\n const blockSeverities = BLOCK_SEVERITIES_BY_STRICTNESS[strictness];\n if (findings.some((f) => blockSeverities.has(f.severity))) return 'block';\n if (findings.length > 0) return 'warn';\n return 'pass';\n}\n\n/** Formats a single finding as `SEVERITY [rule] file: detail` — shared by the pending-queue `safetyFindings` formatters in commands/add.ts and evolve.ts. */\nexport function formatSafetyFinding(f: SafetyFinding): string {\n return `${f.severity} [${f.rule}] ${f.file}: ${f.detail}`;\n}\n\n/**\n * Scoped packages pin as `@scope/name@version`; unscoped as `name@version`.\n * A spec only counts as pinned when the version after the final `@` is\n * digit-leading (semver-ish, e.g. `1.2.3`) — moving/dist tags like `latest`,\n * `next`, `beta`, `canary`, or `dev` (or any other non-numeric tag) resolve to\n * whatever the registry currently points at and must be treated as unpinned.\n * Shared by `gate/mcpSafety.ts`'s scan and `install/promoteMcp.ts`'s\n * documented-candidateEntry version-pin enforcement (v0.7).\n */\nexport function isPinnedPackageSpec(spec: string): boolean {\n const withoutScope = spec.startsWith('@') ? spec.slice(spec.indexOf('/') + 1) : spec;\n const atIdx = withoutScope.lastIndexOf('@');\n if (atIdx <= 0) return false;\n return /^\\d/.test(withoutScope.slice(atIdx + 1));\n}\n\n/** Strips a leading directory and a trailing `.cmd`/`.exe`, case-insensitively, so `/usr/bin/npx`, `npx.cmd`, and `NPX.CMD` all count. Shared by `gate/mcpSafety.ts` and `install/promoteMcp.ts` (v0.7). */\nexport function isNpxCommand(command: string): boolean {\n const base = command.split(/[\\\\/]/).pop() ?? command;\n return base.replace(/\\.(cmd|exe)$/i, '').toLowerCase() === 'npx';\n}\n\n/** `name@latest` -> `name` (drops a trailing `@tag`, preserving any `@scope/`) for suggestion text. Shared by `gate/mcpSafety.ts` and `install/promoteMcp.ts` (v0.7). */\nexport function packageSpecBase(spec: string): string {\n const scopeEnd = spec.startsWith('@') ? spec.indexOf('/') + 1 : 0;\n const atIdx = spec.lastIndexOf('@');\n return atIdx > scopeEnd ? spec.slice(0, atIdx) : spec;\n}\n\n/**\n * npx's own value-consuming CLI flags — verified against the installed npm\n * 12.0.1's `node_modules/npm/bin/npx-cli.js`. That file hardcodes an `opts`\n * set of flags that ALWAYS consume the next token (or an attached\n * `=value`) as their value rather than letting it become the launched\n * command: `package`/`p`, `cache`, `userconfig`, `call`/`c`, `shell`,\n * `node-arg`/`n`, and the deprecated-but-still-parsed `npm`. `registry` is\n * added here deliberately even though it is NOT in that hardcoded set:\n * npx-cli.js's parser has a generic fallback (any unrecognized flag that\n * isn't a known boolean switch consumes the next token as a value unless\n * that token itself looks like a flag) which makes `--registry <url>`\n * behave the same way in practice — verified empirically. This is a\n * DELIBERATE, DOCUMENTED SIMPLIFICATION, not a full reimplementation of\n * npx's parser: that fallback rule also consults npm's complete,\n * version-dependent set of boolean switches (`@npmcli/config`'s\n * `definitions`), which this stdlib-only tool does not bundle. Every other\n * `-`-prefixed token is treated as a boolean flag.\n */\nconst NPX_VALUE_FLAGS = new Set(['p', 'package', 'c', 'call', 'cache', 'userconfig', 'shell', 'n', 'node-arg', 'npm', 'registry']);\n\n/** Of NPX_VALUE_FLAGS, only `-p`/`--package`'s value(s) are themselves package specs — the rest (cache paths, config files, shell binaries, registry URLs, ...) are not. */\nconst NPX_PACKAGE_SPEC_FLAGS = new Set(['p', 'package']);\n\n/**\n * Every `-p`/`--package` value found (repeatable — npx installs ALL of\n * them, in addition to the launched command, before running it), and the\n * positional launch spec if one was found — kept separate rather than a\n * flat list because policy (`install/promoteMcp.ts`) treats them\n * differently: only the positional (the package actually being launched)\n * is ever eligible for auto-pinning; a `-p` dependency, even alone and\n * even if it happens to match the candidate's own package, is not.\n */\nexport interface NpxSpecCollection {\n packageFlagSpecs: string[];\n positionalSpec?: string;\n}\n\n/**\n * Collects every package spec that `npx -y <args>` would actually resolve\n * and install: every repeated `-p`/`--package` value, plus the positional\n * launch spec — the first token that's neither a recognized flag nor a\n * value a recognized flag consumed. Collection stops at that positional\n * token (mirroring npx-cli.js: once the positional is found, `--` is\n * spliced in front of it and everything after belongs to the *launched*\n * command's own argv, not npx's argument parsing). See NPX_VALUE_FLAGS\n * above for the documented scope/limits of this model. Shared by\n * `gate/mcpSafety.ts`'s scan and `install/promoteMcp.ts`'s\n * documented-candidateEntry version-pin enforcement (v0.7).\n */\nexport function collectNpxPackageSpecs(args: string[]): NpxSpecCollection {\n const packageFlagSpecs: string[] = [];\n let positionalSpec: string | undefined;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === '--') break;\n\n if (arg.startsWith('-')) {\n const eqIdx = arg.indexOf('=');\n const key = (eqIdx === -1 ? arg : arg.slice(0, eqIdx)).replace(/^-+/, '');\n const attached = eqIdx === -1 ? undefined : arg.slice(eqIdx + 1);\n\n if (NPX_VALUE_FLAGS.has(key)) {\n let value = attached;\n if (value === undefined) {\n // No attached =value — the next token is consumed as this flag's\n // value, whatever it looks like (mirrors npx-cli.js's unconditional\n // consumption for a known value-flag).\n value = args[i + 1];\n i++;\n }\n if (NPX_PACKAGE_SPEC_FLAGS.has(key) && value !== undefined) {\n packageFlagSpecs.push(value);\n }\n continue;\n }\n\n // Unrecognized flag — treated as boolean (see NPX_VALUE_FLAGS's doc\n // comment for this simplification's documented limits).\n continue;\n }\n\n // First token that's neither a flag nor a consumed value — the\n // positional launch spec. Collect it and stop.\n positionalSpec = arg;\n break;\n }\n\n return { packageFlagSpecs, positionalSpec };\n}\n\n/**\n * Heuristic \"this looks like a random secret\" test, shared by `gate/mcpSafety.ts`'s inline-credential\n * finding and `src/preferences.ts`'s hard refusal (v0.11). Lives here for the same reason the walker\n * and severity helpers do: credential recognition is shared scan policy, and a second private copy\n * drifts — before this was shared, a 40-char random hex value was flagged by the MCP scanner and\n * silently accepted into config.json by preferences.\n *\n * The two CONSUMERS keep their own policies (an advisory finding vs. a refusal); only the\n * recognizer is shared.\n */\n/** ISO-8601 date or date-time, the shape a `last_updated`/`generatedAt` field carries. */\nconst ISO_TIMESTAMP_RE = /^\\d{4}-\\d{2}-\\d{2}(?:[T ]\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d+)?)?(?:Z|[+-]\\d{2}:?\\d{2})?)?$/;\n\n/** A conservative path segment: no `+`, `=`, or `%`, so a base64 blob starting with `/` never qualifies. */\nconst PATH_SEGMENT_RE = /^[A-Za-z0-9._-]+$/;\n\nfunction looksLikeFsPath(v: string): boolean {\n if (!/^(?:\\/|~\\/|\\.\\.?\\/)/.test(v)) return false;\n const segments = v.replace(/^~/, '').split('/').filter(Boolean);\n // >= 2 segments: `/opt` alone is too weak a signal, and a secret is never `a/b/c`-shaped.\n return segments.length >= 2 && segments.every((seg) => PATH_SEGMENT_RE.test(seg));\n}\n\nfunction looksLikeBareUrl(v: string): boolean {\n let url: URL;\n try {\n url = new URL(v);\n } catch {\n return false;\n }\n if (!['http:', 'https:', 'ws:', 'wss:', 'file:'].includes(url.protocol)) return false;\n // `https://user:pass@host` embeds a credential, and a query string can carry a token\n // (`?access_token=...`). Neither is exempt — they fall through to the entropy test.\n if (url.username !== '' || url.password !== '') return false;\n if (url.search !== '') return false;\n return true;\n}\n\n/**\n * True when a value is structurally recognizable as something that is NOT a secret: an ISO\n * timestamp, a credential-free URL, or a filesystem path.\n *\n * Exists because `looksHighEntropy` alone has terrible precision on real config files. Measured\n * against a live `~/.codex/config.toml` (2026-08-09), the entropy test alone reported 15 HIGH\n * \"inline credential\" findings, of which ZERO were credentials — 8 were `last_updated` timestamps,\n * the rest were paths and plain URLs. A security scan whose HIGH findings are 94% noise is one\n * people learn to skim, which costs more than the rule earns.\n *\n * Deliberately narrow: anything with embedded userinfo or a query string is NOT exempt, and the\n * path form rejects `+`/`=`/`%`, so base64-ish secrets are never mistaken for paths.\n */\nexport function looksStructuredNonSecret(value: string): boolean {\n // Strip TOML/JSON array and quote punctuation the line-based scanners capture along with the\n // value, e.g. `notify = [\"/Users/...` yields `[\"/Users/...`.\n const v = value.trim().replace(/^[[\\s'\"]+/, '').replace(/[\\]\\s'\",]+$/, '');\n if (v === '') return false;\n return ISO_TIMESTAMP_RE.test(v) || looksLikeBareUrl(v) || looksLikeFsPath(v);\n}\n\nexport function looksHighEntropy(value: string): boolean {\n if (looksStructuredNonSecret(value)) return false;\n if (/\\s/.test(value)) return false;\n if (value.length < 20) return false;\n const hasLower = /[a-z]/.test(value);\n const hasUpper = /[A-Z]/.test(value);\n const hasDigit = /[0-9]/.test(value);\n const varietyCount = [hasLower, hasUpper, hasDigit].filter(Boolean).length;\n const uniqueChars = new Set(value).size;\n return varietyCount >= 2 && uniqueChars / value.length > 0.4;\n}\n","import { UserError } from './errors.js';\nimport { looksHighEntropy } from './gate/scanShared.js';\nimport type { Config } from './config.js';\n\n/**\n * Open-ended, growable user context — the half of the config that has no fixed schema.\n *\n * `businessProfile` (v0.8) is five typed fields, so anything learned that isn't one of those five\n * has nowhere to live and gets forgotten between sessions. Preferences are free-form key/value\n * facts (\"prefers-typescript\", \"deploys-on-vercel\", \"never-auto-promote-mcp\") that accumulate\n * without a schema change.\n *\n * ## Why there are two stores, not one\n *\n * `preferences` is what the tool believes. `proposedPreferences` is what something has SUGGESTED\n * it believe. Agents write only to the second one.\n *\n * An agent draining the ingest/curation queue reads untrusted skill content, so anything it\n * concludes about you is downstream of text an attacker may have written. If an agent could write\n * `preferences` directly, a malicious SKILL.md could talk it into persisting an instruction into\n * your config, where every later run would read it back as your stated preference — a durable\n * prompt-injection foothold with a laundering step in the middle. Routing agent writes through a\n * review queue means the worst case is a proposal you decline.\n *\n * Effective reads NEVER consult the proposal queue: `getPreference` returns accepted values only.\n */\n\nexport type PreferenceSource = 'user' | 'agent';\n\ndeclare const acceptedBrand: unique symbol;\n\n/**\n * A preference value a human has accepted — nominally distinct from a raw `string`.\n *\n * The accepted/proposed split above is a security boundary, but until this brand existed it was\n * enforced only by which accessor a caller happened to reach for. Both stores held a plain\n * `string`, so the natural-looking \"effective value, or the latest claim about it\":\n *\n * ```ts\n * config.preferences?.[k]?.value ?? config.proposedPreferences?.find(p => p.key === k)?.value\n * ```\n *\n * type-checked cleanly and silently promoted an unreviewed agent claim to an effective preference\n * — precisely the laundering this module exists to prevent. `AcceptedValue` is assignable TO\n * `string` (reading is unaffected), but a plain `string` is NOT assignable to it, so any consumer\n * that declares it needs an accepted value cannot be handed a proposal's.\n *\n * Minting is deliberately confined to `markAccepted` below: grep it to audit every place a value\n * becomes trusted.\n */\nexport type AcceptedValue = string & { readonly [acceptedBrand]: true };\n\n/**\n * The ONLY place a raw string becomes an `AcceptedValue`. Every caller is a point where a human\n * has either typed the value (`config set`) or accepted it (`config review`), or where a value\n * previously accepted is being re-read from disk after re-validation.\n */\nfunction markAccepted(value: string): AcceptedValue {\n return value as AcceptedValue;\n}\n\nexport interface PreferenceEntry {\n value: AcceptedValue;\n /** Who asserted it. `user` means a human typed `config set`; `agent` means a reviewed proposal. */\n source: PreferenceSource;\n updatedAt: string;\n /** Free-text provenance: which command, agent, or session produced it. */\n origin?: string;\n note?: string;\n}\n\nexport interface PreferenceProposal {\n key: string;\n /** Deliberately a bare `string`, never `AcceptedValue` — nothing here has been reviewed. */\n value: string;\n proposedAt: string;\n origin?: string;\n note?: string;\n}\n\n/** Lowercase kebab/dot segments, 1-64 chars — a stable, greppable, filename-safe key space. */\nexport const PREFERENCE_KEY_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;\n\nexport const MAX_PREFERENCE_VALUE_LENGTH = 500;\n\n/**\n * Cap on the proposal queue. An agent in a loop must not be able to grow config.json without\n * bound — at the cap, new proposals are refused (loudly) rather than evicting older ones, since\n * silently dropping the oldest would let a spammer flush a real proposal out of the queue.\n */\nexport const MAX_PENDING_PROPOSALS = 50;\n\n/**\n * Key names and value shapes that look like credentials.\n *\n * config.json is plain text at default permissions, so a secret landing here is a leak, and the\n * most likely way one arrives is an agent \"helpfully\" persisting a token it saw. Both the key and\n * the value are checked: `api-key` is refused whatever its value, and an `sk-`/`ghp_`/`AKIA`\n * shaped value is refused whatever its key. This is a blunt filter on an open field, not a\n * secret-detection engine — it is deliberately biased toward refusing.\n */\nconst SECRET_KEY_PATTERN = /(^|[.\\-_])(password|passwd|secret|token|api[.\\-_]?key|apikey|credential|private[.\\-_]?key|auth)([.\\-_]|$)/i;\nconst SECRET_VALUE_PATTERNS: { pattern: RegExp; label: string }[] = [\n { pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----/, label: 'a PEM private key' },\n // sk-ant- first: the broader sk- pattern below also matches it, and the more specific label wins.\n { pattern: /\\bsk-ant-[A-Za-z0-9_-]{16,}/, label: 'an Anthropic-style secret key' },\n { pattern: /\\bsk-[A-Za-z0-9_-]{16,}/, label: 'an OpenAI-style secret key' },\n { pattern: /\\bgh[pousr]_[A-Za-z0-9]{20,}/, label: 'a GitHub token' },\n { pattern: /\\bxox[baprs]-[A-Za-z0-9-]{10,}/, label: 'a Slack token' },\n { pattern: /\\bAKIA[0-9A-Z]{16}\\b/, label: 'an AWS access key id' },\n { pattern: /\\bsntry[su]_[A-Za-z0-9]{20,}/, label: 'a Sentry token' },\n { pattern: /\\beyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\./, label: 'a JWT' },\n];\n\nexport const MAX_PREFERENCE_NOTE_LENGTH = 200;\n\n/**\n * Validates a free-text provenance/annotation field: length-capped, no C0/C1 control characters.\n *\n * Originally private and scoped to `origin`/`note` — supplied by whatever ran `config propose`\n * (i.e. by an agent that has been reading untrusted skill content) and rendered back at the\n * `config review` prompt, exactly the moment a human is deciding whether to trust the proposal.\n * Unfiltered escape sequences there could overwrite or disguise the key/value being reviewed,\n * which would defeat the whole propose/accept boundary.\n *\n * **Exported (v0.12)** and widened from the `'origin' | 'note'` literal union to a bare `field:\n * string` so `src/findings.ts`'s `validateAcceptReason` can reuse it for `finding accept`'s\n * mandatory `--reason` — same rendering-into-a-review-moment rationale, different field name. The\n * non-negotiable this satisfies: there is exactly one control-char/length validator for this shape\n * of field, never a second copy that could silently drift from this one.\n */\nexport function validateProvenanceText(field: string, text: string | undefined): void {\n if (text === undefined) return;\n if (text.length > MAX_PREFERENCE_NOTE_LENGTH) {\n throw new UserError(`--${field} is ${text.length} chars — the limit is ${MAX_PREFERENCE_NOTE_LENGTH}`);\n }\n if (/[\\u0000-\\u001F\\u007F-\\u009F]/.test(text)) {\n throw new UserError(`--${field} contains control characters — refusing to store terminal escape sequences`);\n }\n}\n\n/** Throws a UserError when the key/value pair must not be persisted. Applied to BOTH set and propose. */\nexport function validatePreference(key: string, value: string): void {\n if (!PREFERENCE_KEY_PATTERN.test(key)) {\n throw new UserError(\n `invalid preference key \"${key}\" — use lowercase letters, digits, dot, dash or underscore (max 64 chars), e.g. \"prefers-typescript\"`\n );\n }\n if (SECRET_KEY_PATTERN.test(key)) {\n throw new UserError(\n `refusing to store \"${key}\": the key names a credential, and config.json is plain text. Keep secrets in your OS keychain or an env var.`\n );\n }\n if (value.length === 0) {\n throw new UserError(`preference \"${key}\" has an empty value — use \\`skill-forge config unset ${key}\\` to remove it`);\n }\n if (value.length > MAX_PREFERENCE_VALUE_LENGTH) {\n throw new UserError(\n `preference \"${key}\" is ${value.length} chars — the limit is ${MAX_PREFERENCE_VALUE_LENGTH}. Preferences are short facts, not documents.`\n );\n }\n // C0/C1 controls would smuggle ANSI escape sequences into every later render of this value.\n if (/[\\u0000-\\u001F\\u007F-\\u009F]/.test(value)) {\n throw new UserError(`preference \"${key}\" contains control characters — refusing to store terminal escape sequences`);\n }\n for (const { pattern, label } of SECRET_VALUE_PATTERNS) {\n if (pattern.test(value)) {\n throw new UserError(\n `refusing to store \"${key}\": the value looks like ${label}, and config.json is plain text. Keep secrets in your OS keychain or an env var.`\n );\n }\n }\n // Catches the secret shapes the prefix table above cannot: a long random token with no\n // recognizable vendor prefix. Shared with the MCP scanner's inline-credential rule\n // (gate/scanShared.ts) so the two cannot disagree about the same string — before they were\n // shared, this exact value was flagged there and accepted here.\n if (looksHighEntropy(value)) {\n throw new UserError(\n `refusing to store \"${key}\": the value looks like a random secret, and config.json is plain text. ` +\n 'Keep secrets in your OS keychain or an env var; if this really is a preference, use a shorter, human-readable value.'\n );\n }\n}\n\n/** Effective entry for a key — accepted preferences only; proposals are never read as truth. */\nexport function getPreference(config: Config, key: string): PreferenceEntry | undefined {\n return config.preferences?.[key];\n}\n\n/**\n * The sanctioned way to read an effective value. Returns `AcceptedValue`, so a consumer that\n * declares this as its input type cannot be handed a proposal's raw string by mistake — the\n * brand is only useful if the read path hands it out, otherwise every caller lands on `string`\n * and the distinction evaporates at the first assignment.\n */\nexport function getAcceptedValue(config: Config, key: string): AcceptedValue | undefined {\n return config.preferences?.[key]?.value;\n}\n\n/**\n * Records a preference the USER stated. Accepted immediately: a human typing `config set` IS the\n * review step. Returns a new Config; the caller saves it.\n */\nexport function setPreference(\n config: Config,\n key: string,\n value: string,\n options: { origin?: string; note?: string; source?: PreferenceSource; now?: Date } = {}\n): Config {\n validatePreference(key, value);\n validateProvenanceText('origin', options.origin);\n validateProvenanceText('note', options.note);\n const entry: PreferenceEntry = {\n value: markAccepted(value),\n source: options.source ?? 'user',\n updatedAt: (options.now ?? new Date()).toISOString(),\n origin: options.origin,\n note: options.note,\n };\n return { ...config, preferences: { ...config.preferences, [key]: entry } };\n}\n\nexport function unsetPreference(config: Config, key: string): Config {\n if (!config.preferences?.[key]) {\n throw new UserError(`no preference named \"${key}\" — \\`skill-forge config list\\` shows what is set`);\n }\n const next = { ...config.preferences };\n delete next[key];\n return { ...config, preferences: next };\n}\n\n/**\n * Queues an agent-proposed preference for review. Never becomes effective here — see the module\n * doc for why agent writes are separated from accepted values.\n *\n * A second proposal for the same key REPLACES the queued one rather than adding a duplicate, so a\n * chatty agent can refine its suggestion without flooding the review list.\n */\nexport function proposePreference(\n config: Config,\n key: string,\n value: string,\n options: { origin?: string; note?: string; now?: Date } = {}\n): Config {\n validatePreference(key, value);\n validateProvenanceText('origin', options.origin);\n validateProvenanceText('note', options.note);\n const existing = config.proposedPreferences ?? [];\n const withoutKey = existing.filter((p) => p.key !== key);\n if (withoutKey.length >= MAX_PENDING_PROPOSALS) {\n throw new UserError(\n `the preference proposal queue is full (${MAX_PENDING_PROPOSALS}) — run \\`skill-forge config review\\` before proposing more`\n );\n }\n const proposal: PreferenceProposal = {\n key,\n value,\n proposedAt: (options.now ?? new Date()).toISOString(),\n origin: options.origin,\n note: options.note,\n };\n return { ...config, proposedPreferences: [...withoutKey, proposal] };\n}\n\n/** Moves a queued proposal into the accepted set, tagged `source: 'agent'` so its origin stays visible. */\nexport function acceptProposal(config: Config, key: string, now: Date = new Date()): Config {\n const proposal = (config.proposedPreferences ?? []).find((p) => p.key === key);\n if (!proposal) {\n throw new UserError(`no pending proposal for \"${key}\" — \\`skill-forge config review\\` lists what is pending`);\n }\n const withPreference = setPreference(config, proposal.key, proposal.value, {\n source: 'agent',\n origin: proposal.origin,\n note: proposal.note,\n now,\n });\n return { ...withPreference, proposedPreferences: (config.proposedPreferences ?? []).filter((p) => p.key !== key) };\n}\n\nexport function rejectProposal(config: Config, key: string): Config {\n const pending = config.proposedPreferences ?? [];\n if (!pending.some((p) => p.key === key)) {\n throw new UserError(`no pending proposal for \"${key}\" — \\`skill-forge config review\\` lists what is pending`);\n }\n return { ...config, proposedPreferences: pending.filter((p) => p.key !== key) };\n}\n\n/**\n * Repairs a hand-edited or foreign `preferences` map on load: anything that fails validation, or\n * isn't shaped like an entry, is DROPPED rather than propagated. config.json is a plain file a\n * user (or a stray process) can edit, and everything downstream renders these into a terminal.\n */\nexport function normalizePreferences(raw: unknown): Record<string, PreferenceEntry> | undefined {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;\n const out: Record<string, PreferenceEntry> = {};\n for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {\n if (!value || typeof value !== 'object') continue;\n const entry = value as Partial<PreferenceEntry>;\n if (typeof entry.value !== 'string') continue;\n try {\n validatePreference(key, entry.value);\n } catch {\n continue;\n }\n const origin = typeof entry.origin === 'string' ? entry.origin : undefined;\n const note = typeof entry.note === 'string' ? entry.note : undefined;\n try {\n validateProvenanceText('origin', origin);\n validateProvenanceText('note', note);\n } catch {\n continue;\n }\n out[key] = {\n // Re-minted only after `validatePreference` passed above: a hand-edited config.json cannot\n // smuggle in a value that would fail today's rules.\n value: markAccepted(entry.value),\n source: entry.source === 'agent' ? 'agent' : 'user',\n updatedAt: typeof entry.updatedAt === 'string' ? entry.updatedAt : new Date(0).toISOString(),\n origin,\n note,\n };\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n/** Same repair pass for the proposal queue, plus the queue cap. */\nexport function normalizeProposals(raw: unknown): PreferenceProposal[] | undefined {\n if (!Array.isArray(raw)) return undefined;\n const out: PreferenceProposal[] = [];\n for (const item of raw) {\n if (!item || typeof item !== 'object') continue;\n const proposal = item as Partial<PreferenceProposal>;\n if (typeof proposal.key !== 'string' || typeof proposal.value !== 'string') continue;\n try {\n validatePreference(proposal.key, proposal.value);\n } catch {\n continue;\n }\n if (out.some((p) => p.key === proposal.key)) continue;\n const origin = typeof proposal.origin === 'string' ? proposal.origin : undefined;\n const note = typeof proposal.note === 'string' ? proposal.note : undefined;\n try {\n validateProvenanceText('origin', origin);\n validateProvenanceText('note', note);\n } catch {\n continue;\n }\n out.push({\n key: proposal.key,\n value: proposal.value,\n proposedAt: typeof proposal.proposedAt === 'string' ? proposal.proposedAt : new Date(0).toISOString(),\n origin,\n note,\n });\n if (out.length >= MAX_PENDING_PROPOSALS) break;\n }\n return out.length > 0 ? out : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Compile-time guards\n//\n// `tsconfig.json` includes only `src`, so a `@ts-expect-error` in a test file would not be\n// checked by `tsc --noEmit`. These assertions live here so removing the brand — or widening\n// `PreferenceEntry.value` back to `string` — fails the typecheck instead of silently restoring\n// the laundering path described on `AcceptedValue`.\n// ---------------------------------------------------------------------------\n\ntype Assert<T extends true> = T;\n\n/**\n * The entry's value must actually BE branded. This is the guard that bites if someone widens\n * `PreferenceEntry.value` back to `string` — testing only \"a bare string isn't an AcceptedValue\"\n * would still pass in that case, since the brand type would survive unused.\n */\ntype _EntryValueIsBranded = Assert<PreferenceEntry['value'] extends AcceptedValue ? true : false>;\n\n/** A proposal's value must NOT satisfy whatever type the entry's value has. */\ntype _ProposalValueIsNotEntryValue = Assert<PreferenceProposal['value'] extends PreferenceEntry['value'] ? false : true>;\n\n/** An accepted value must still be usable anywhere a plain string is wanted. */\ntype _AcceptedIsAString = Assert<PreferenceEntry['value'] extends string ? true : false>;\n\n/** A whole proposal must not be assignable where an entry is required. */\ntype _ProposalIsNotAnEntry = Assert<PreferenceProposal extends PreferenceEntry ? false : true>;\n","import { existsSync } from 'node:fs';\nimport { UserError } from '../errors.js';\nimport { expandHome } from '../util.js';\n\nexport type SourceType = 'skills.sh' | 'git' | 'local' | 'npm';\nexport type ArtifactType = 'skill' | 'mcp';\n\nexport interface ResolvedSource {\n type: SourceType;\n value: string;\n}\n\nconst GIT_URL_PATTERN = /^(git@|https?:\\/\\/|ssh:\\/\\/)/;\n// Unambiguous filesystem-path prefixes: no skills.sh `owner/name` slug starts\n// with any of these, so a missing path here is a friendly \"not found\" error\n// rather than a slow (network-dependent) skills.sh install attempt.\nconst LOCAL_PATH_PATTERN = /^(\\.{1,2}\\/|\\/|~\\/|~$)/;\n// A scoped npm package spec: \"@scope/name\", optionally with a version/tag\n// suffix on the name half (e.g. \"@scope/name@1.2.3\"). Never collides with a\n// skills.sh `owner/name` slug, which has no leading \"@\".\nconst NPM_SCOPED_PATTERN = /^@[^/]+\\/[^/]+$/;\n\n/**\n * Classifies a skill source string so the installer knows which quarantine\n * strategy to use:\n * - an http(s)/git@/ssh:// URL, or one ending in `.git`, is a git remote\n * - an existing filesystem path is a local skill\n * - a nonexistent path that's unambiguously path-shaped (./, ../, /, ~/) is\n * still local, so it fails fast with a friendly error instead of falling\n * through to skills.sh\n * - with `artifact: 'mcp'`, a scoped \"@scope/name\" package or a bare\n * (no-slash) package name is npm — an \"owner/name\" two-segment slug (no\n * leading \"@\") still falls through to skills.sh below, same as\n * `artifact: 'skill'`/absent: no ambiguity guessing between the two\n * owner/name-shaped source kinds\n * - everything else is treated as a skills.sh `owner/name` slug\n *\n * Skill classification (`artifact` 'skill' or absent) is unchanged from v0.4.\n */\nexport function resolve(source: string, artifact: ArtifactType = 'skill'): ResolvedSource {\n const value = source.trim();\n\n // Reject anything that looks like a CLI flag rather than a source: quarantine.ts\n // passes `value` straight through as a git-clone/npx/npm-pack argv positional, so\n // a source starting with \"-\" (e.g. \"--upload-pack=...\") could otherwise be\n // smuggled in as an injected flag. Fail fast with a friendly error instead of\n // reaching exec().\n if (value.startsWith('-')) {\n throw new UserError(`skill source cannot start with \"-\" (looks like a flag, not a source): ${value}`);\n }\n\n if (GIT_URL_PATTERN.test(value) || value.endsWith('.git')) {\n return { type: 'git', value };\n }\n\n // Expand ~ before existence checks so a real \"~/some-skill\" resolves instead\n // of erroring \"not found\" on the literal tilde string.\n const localCandidate = expandHome(value);\n if (existsSync(localCandidate) || LOCAL_PATH_PATTERN.test(value)) {\n return { type: 'local', value: localCandidate };\n }\n\n if (artifact === 'mcp' && (NPM_SCOPED_PATTERN.test(value) || !value.includes('/'))) {\n return { type: 'npm', value };\n }\n\n return { type: 'skills.sh', value };\n}\n","import { accessSync, constants, mkdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { basename, delimiter, dirname, join } from 'node:path';\nimport { randomUUID } from 'node:crypto';\nimport { UserError } from './errors.js';\n\n/** Expands a leading `~` / `~/` to the user's home directory. */\nexport function expandHome(p: string): string {\n if (p === '~') return homedir();\n if (p.startsWith('~/')) return join(homedir(), p.slice(2));\n return p;\n}\n\n/** Strips wrapping single/double quotes from a frontmatter value. */\nexport function stripQuotes(s: string): string {\n return s.replace(/^['\"]+/, '').replace(/['\"]+$/, '');\n}\n\n/** True if `p` exists and is a directory (false on any stat error). */\nexport function isDir(p: string): boolean {\n try {\n return statSync(p).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Resolves a bare command name against PATH and returns the first executable file that matches,\n * or undefined. Used by `skill-forge init` (v0.11) to tell an agent that is actually installed\n * from one that merely shares a skills directory with an installed agent.\n *\n * Look-up only: this NEVER executes the candidate — no `--version` probe, no spawn of anything\n * found on PATH — matching the static-only discipline the gate modules hold to. A name containing\n * a path separator is rejected outright rather than resolved relative to the cwd, so nothing here\n * can be steered into `./claude`.\n *\n * `pathEnv`/`platform`/`pathExt` are injectable so tests can probe a temp PATH instead of the\n * machine's real one.\n */\nexport function resolveBinaryOnPath(\n binary: string,\n pathEnv: string | undefined = process.env.PATH,\n platform: NodeJS.Platform = process.platform,\n pathExt: string | undefined = process.env.PATHEXT,\n): string | undefined {\n if (binary === '' || binary.includes('/') || binary.includes('\\\\')) return undefined;\n const dirs = (pathEnv ?? '').split(platform === 'win32' ? ';' : delimiter).filter(Boolean);\n const exts = platform === 'win32' ? (pathExt ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean) : [''];\n for (const dir of dirs) {\n for (const ext of exts) {\n const candidate = join(dir, binary + ext);\n try {\n // isFile guards against a directory named like the binary — a dir is \"executable\" to access().\n if (!statSync(candidate).isFile()) continue;\n accessSync(candidate, constants.X_OK);\n return candidate;\n } catch {\n // not there, or not executable by us — keep looking\n }\n }\n }\n return undefined;\n}\n\n/**\n * Writes JSON atomically and owner-only: serialize to a temp file in the SAME directory (so the\n * rename is same-filesystem and therefore atomic), then rename it over the target. A reader can\n * never observe a half-written file, and a crash mid-write leaves the previous file intact plus an\n * orphaned temp rather than a corrupted one.\n *\n * Single home for a primitive that had drifted into three copies (queue.ts, refine/store.ts,\n * auditState.ts) — the copies had already diverged on file mode, which is exactly the kind of\n * silent inconsistency a shared helper prevents.\n */\nexport function writeJsonAtomic(path: string, data: unknown): void {\n const dir = dirname(path);\n mkdirSync(dir, { recursive: true });\n const tempPath = join(dir, `.${basename(path)}.tmp-${randomUUID()}`);\n try {\n writeFileSync(tempPath, JSON.stringify(data, null, 2) + '\\n', { encoding: 'utf-8', mode: 0o600 });\n renameSync(tempPath, path);\n } catch (err) {\n rmSync(tempPath, { force: true });\n throw err;\n }\n}\n\n/**\n * Creates a file exclusively (`wx`) and owner-only (`0600`), refusing to overwrite an existing\n * path. This pairing is a documented privacy posture for everything this CLI writes reports to,\n * not an incidental flag choice — enforcing it by copy-paste is how it decays, so audit, organize,\n * and routine all route through here. `hint` names the flag a caller can use to pick another path.\n */\nexport function writeNewFile(path: string, contents: string, hint: string): void {\n mkdirSync(dirname(path), { recursive: true });\n try {\n writeFileSync(path, contents, { flag: 'wx', mode: 0o600 });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\n throw new UserError(`file already exists: ${path} — pass a different ${hint} path`);\n }\n throw err;\n }\n}\n","import { randomUUID } from 'node:crypto';\nimport { spawn } from 'node:child_process';\nimport { cpSync, existsSync, mkdirSync, readdirSync } from 'node:fs';\nimport type { Dirent } from 'node:fs';\nimport { basename, join, resolve as resolvePath } from 'node:path';\nimport type { ResolvedSource } from './resolve.js';\nimport { UserError } from '../errors.js';\n\nexport interface ExecResult {\n stdout: string;\n stderr: string;\n code: number;\n}\n\nexport type ExecFn = (\n command: string,\n args: string[],\n options: { cwd: string }\n) => Promise<ExecResult>;\n\nexport interface QuarantineEntry {\n id: string;\n quarantinePath: string;\n skillDirs: string[];\n /** Present only for npm-sourced (MCP) entries: the extracted `package/` dir inside the sandbox. */\n packageDir?: string;\n source: ResolvedSource;\n}\n\nexport interface QuarantineDeps {\n exec?: ExecFn;\n}\n\nconst SKILL_MANIFEST = 'SKILL.md';\nconst SKIP_DIRS = new Set(['.git', 'node_modules']);\n\n/**\n * Installs `source` into `<quarantineDir>/<id>/` — never into a target\n * skills root. skills.sh sources run `npx -y skills@latest add <slug>`\n * with cwd pinned to the sandbox; git sources run a shallow clone into a\n * named subdir of the sandbox; local sources are recursively copied in.\n * Returns the sandbox id/path plus every directory inside it that looks\n * like an installed skill (contains SKILL.md).\n */\nexport async function quarantine(\n source: ResolvedSource,\n quarantineDir: string,\n deps: QuarantineDeps = {}\n): Promise<QuarantineEntry> {\n const exec = deps.exec ?? defaultExec;\n const id = randomUUID();\n const quarantinePath = join(quarantineDir, id);\n mkdirSync(quarantinePath, { recursive: true });\n let packageDir: string | undefined;\n\n switch (source.type) {\n case 'skills.sh': {\n const result = await exec('npx', ['-y', 'skills@latest', 'add', source.value], {\n cwd: quarantinePath,\n });\n if (result.code !== 0) {\n throw new Error(\n `skills.sh install failed for \"${source.value}\" (exit ${result.code}): ${result.stderr || result.stdout}`\n );\n }\n break;\n }\n case 'git': {\n const clonePath = join(quarantinePath, deriveGitDirName(source.value));\n // `--` separates flags from positionals so a source value that looks like a\n // git flag (e.g. \"--upload-pack=...\") is always treated as the repo\n // positional, never parsed as an option — belt-and-suspenders alongside\n // resolve.ts's leading-dash rejection.\n const result = await exec('git', ['clone', '--depth', '1', '--', source.value, clonePath], {\n cwd: quarantinePath,\n });\n if (result.code !== 0) {\n throw new UserError(\n `git clone failed for \"${source.value}\" (exit ${result.code}): ${result.stderr || result.stdout}`\n );\n }\n break;\n }\n case 'local': {\n if (!existsSync(source.value)) {\n throw new UserError(`local path not found: ${source.value}`);\n }\n const dirName = basename(resolvePath(source.value));\n cpSync(source.value, join(quarantinePath, dirName), { recursive: true });\n break;\n }\n case 'npm': {\n // Fetch only — never `npm install`, never run scripts (`--ignore-scripts`\n // belt-and-suspenders alongside `npm pack` never running lifecycle\n // scripts on its own). Before extracting, list the tarball's members and\n // refuse to proceed if any member path could escape the sandbox\n // (path-traversal guard) — only once every member is confirmed safe do\n // we extract with system `tar`, so nothing from the package ever runs\n // during quarantine and nothing can be written outside the sandbox.\n const packResult = await exec(\n 'npm',\n ['pack', source.value, '--ignore-scripts', '--pack-destination', quarantinePath],\n { cwd: quarantinePath }\n );\n if (packResult.code !== 0) {\n throw new UserError(\n `npm pack failed for \"${source.value}\" (exit ${packResult.code}): ${packResult.stderr || packResult.stdout}`\n );\n }\n\n const tarballPath = join(quarantinePath, findTarball(quarantinePath, source.value));\n\n const listResult = await exec('tar', ['-tzf', tarballPath], { cwd: quarantinePath });\n if (listResult.code !== 0) {\n throw new UserError(\n `could not list tarball contents for \"${source.value}\" (exit ${listResult.code}): ${listResult.stderr || listResult.stdout}`\n );\n }\n assertSafeTarMembers(listResult.stdout, source.value);\n\n const extractResult = await exec('tar', ['-xzf', tarballPath, '-C', quarantinePath], {\n cwd: quarantinePath,\n });\n if (extractResult.code !== 0) {\n throw new UserError(\n `tarball extraction failed for \"${source.value}\" (exit ${extractResult.code}): ${extractResult.stderr || extractResult.stdout}`\n );\n }\n\n // `npm pack` tarballs always extract to a top-level `package/` dir.\n packageDir = join(quarantinePath, 'package');\n if (!existsSync(packageDir)) {\n throw new UserError(\n `npm pack for \"${source.value}\" did not produce the expected package/ dir after extraction`\n );\n }\n break;\n }\n default: {\n throw new UserError(`unknown source type: ${(source as ResolvedSource).type}`);\n }\n }\n\n return { id, quarantinePath, skillDirs: findSkillDirs(quarantinePath), packageDir, source };\n}\n\n/** IDs (sandbox dir names) of entries currently held in quarantine; [] when the dir doesn't exist. */\nexport function listQuarantineIds(quarantineDir: string): string[] {\n if (!existsSync(quarantineDir)) return [];\n return readdirSync(quarantineDir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n}\n\n/** Recursively finds every directory under `root` that directly contains SKILL.md. */\nexport function findSkillDirs(root: string): string[] {\n const found: string[] = [];\n\n function walk(dir: string): void {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n if (entries.some((entry) => entry.isFile() && entry.name === SKILL_MANIFEST)) {\n found.push(dir);\n }\n\n for (const entry of entries) {\n if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {\n walk(join(dir, entry.name));\n }\n }\n }\n\n walk(root);\n return found;\n}\n\n/**\n * Path-traversal guard: throws a UserError if any member path listed by\n * `tar -tzf` is absolute or contains a `..` path segment, BEFORE extraction\n * ever runs. (This checks member *names* only; link-type member safety still\n * rests on the system tar's own symlink safeguards.)\n */\nfunction assertSafeTarMembers(tarListOutput: string, sourceValue: string): void {\n for (const raw of tarListOutput.split('\\n')) {\n const member = raw.trim();\n if (!member) continue;\n if (member.startsWith('/') || member.split('/').includes('..')) {\n throw new UserError(\n `refusing to extract tarball for \"${sourceValue}\": unsafe member path \"${member}\" ` +\n '(absolute path or \"..\" segment — possible path traversal)'\n );\n }\n }\n}\n\n/** Locates the .tgz produced by `npm pack --pack-destination <dir>` (exactly one is expected, since each quarantine sandbox is freshly created before use). */\nfunction findTarball(dir: string, sourceValue: string): string {\n const tarball = readdirSync(dir).find((name) => name.endsWith('.tgz'));\n if (!tarball) {\n throw new UserError(`npm pack for \"${sourceValue}\" did not produce a .tgz file in ${dir}`);\n }\n return tarball;\n}\n\n/** Derives a filesystem-friendly dir name from a git URL (scp-like or http(s)). */\nfunction deriveGitDirName(url: string): string {\n const stripped = url.replace(/\\.git$/, '');\n const segments = stripped.split(/[/:]/).filter(Boolean);\n return segments[segments.length - 1] || 'repo';\n}\n\n/** Real argv-array subprocess runner (no shell) — the default ExecFn, and shared by evolve.ts's skillopt-sleep invocations. */\nexport function defaultExec(command: string, args: string[], options: { cwd: string }): Promise<ExecResult> {\n return new Promise((resolvePromise, reject) => {\n const child = spawn(command, args, { cwd: options.cwd });\n let stdout = '';\n let stderr = '';\n child.stdout?.on('data', (chunk: Buffer) => (stdout += chunk.toString()));\n child.stderr?.on('data', (chunk: Buffer) => (stderr += chunk.toString()));\n child.on('error', reject);\n child.on('close', (code) => resolvePromise({ stdout, stderr, code: code ?? 1 }));\n });\n}\n","import { cpSync, existsSync, mkdirSync, renameSync, rmSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport { UserError } from '../errors.js';\nimport { fingerprintDirs, isRealDirectory } from './fingerprint.js';\nimport type { QuarantineEntry } from './quarantine.js';\n\nexport interface PromoteOptions {\n force?: boolean;\n /**\n * Digest captured (via `fingerprintDirs`) when the candidate was gated, re-verified immediately\n * before anything moves. A mismatch means the vetted bytes are not the bytes about to be\n * installed, and promotion is refused. See src/install/fingerprint.ts for why this matters most\n * to `promote <id>`, where the gap between gate and promote can be days.\n */\n expectedFingerprint?: string;\n}\n\nexport interface PromoteResult {\n promoted: string[];\n}\n\n/**\n * Moves every skill dir found during quarantine into `targetSkillsRoot`.\n * Refuses to overwrite an existing dir of the same name unless `force` is\n * set — checked up front for every skill dir before anything is moved, so\n * a naming clash never leaves a partial promotion behind. Cleans up the\n * quarantine sandbox once every skill dir has been moved out.\n *\n * Pre-flight also re-validates the candidate against the state it was gated in (v0.11): each skill\n * dir must still be a REAL directory rather than a symlink, no symlink inside it may resolve out\n * of the sandbox, and — when the caller captured one — the content fingerprint must still match.\n * All three exist because the gate and the move are separated in time, and `renameSync` moves a\n * symlink as a link: a link planted in quarantine after the scan would otherwise land in the\n * trusted skills root still pointing at content its author can keep changing.\n */\nexport function promote(\n entry: QuarantineEntry,\n targetSkillsRoot: string,\n options: PromoteOptions = {}\n): PromoteResult {\n const force = options.force ?? false;\n\n for (const skillDir of entry.skillDirs) {\n if (!isRealDirectory(skillDir)) {\n throw new UserError(\n `refusing to promote ${skillDir}: it is no longer a real directory (a symlink or a deleted path) — ` +\n 're-run the gate on this candidate'\n );\n }\n const dest = join(targetSkillsRoot, basename(skillDir));\n if (existsSync(dest) && !force) {\n throw new Error(`Refusing to overwrite existing skill dir: ${dest} (pass force to override)`);\n }\n }\n\n const fingerprint = fingerprintDirs(entry.skillDirs);\n if (fingerprint.escaping.length > 0) {\n throw new UserError(\n `refusing to promote ${entry.id}: ${fingerprint.escaping.length} symlink(s) inside the candidate resolve outside ` +\n `the quarantine sandbox, and would land in your skills root still pointing there:\\n ` +\n fingerprint.escaping.join('\\n ')\n );\n }\n if (options.expectedFingerprint !== undefined && options.expectedFingerprint !== fingerprint.digest) {\n throw new UserError(\n `refusing to promote ${entry.id}: the candidate's contents changed after it was gated — ` +\n 'what was scanned is not what would be installed. Re-run `skill-forge scan` on the source.'\n );\n }\n\n mkdirSync(targetSkillsRoot, { recursive: true });\n\n const promoted: string[] = [];\n for (const skillDir of entry.skillDirs) {\n const dest = join(targetSkillsRoot, basename(skillDir));\n if (existsSync(dest)) {\n rmSync(dest, { recursive: true, force: true });\n }\n moveDir(skillDir, dest);\n promoted.push(dest);\n }\n\n rmSync(entry.quarantinePath, { recursive: true, force: true });\n\n return { promoted };\n}\n\n/** Deletes the quarantine sandbox without promoting anything into a skills root. */\nexport function reject(entry: QuarantineEntry): void {\n rmSync(entry.quarantinePath, { recursive: true, force: true });\n}\n\n/** No-op: leaves the quarantine sandbox exactly as-is for later review. */\nexport function hold(_entry: QuarantineEntry): void {\n // intentionally does nothing — quarantine dir is left in place\n}\n\nfunction moveDir(src: string, dest: string): void {\n try {\n renameSync(src, dest);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EXDEV') {\n cpSync(src, dest, { recursive: true });\n rmSync(src, { recursive: true, force: true });\n } else {\n throw err;\n }\n }\n}\n","import { createHash } from 'node:crypto';\nimport { lstatSync, readFileSync, readdirSync, readlinkSync, realpathSync } from 'node:fs';\nimport { isAbsolute, join, relative, sep } from 'node:path';\n\n/**\n * Content fingerprinting for quarantined candidates — the check that makes \"what was scanned is\n * what gets installed\" true rather than merely likely.\n *\n * The gate reads a candidate, then some time passes, then `install/promote.ts` moves it into a\n * trusted skills root. Everything in between is a TOCTOU window: whoever can write to the\n * quarantine directory can swap the vetted content for something else, and the move installs the\n * swap under the vetted content's clean bill of health. `skill-forge promote <id>` (v0.11) widened\n * that window from one process lifetime to however long a hold sits — days, per its own docs — so\n * the window is now large enough to be worth closing rather than just noting.\n *\n * Two distinct protections live here:\n *\n * 1. **The digest** detects any content change between capture and promote. It is not a security\n * boundary against a racing attacker (nothing short of fd-based syscalls Node doesn't expose\n * would be), but it turns a silent substitution into a refusal.\n * 2. **Symlink escape detection** is the sharper one. A symlink is the only way a swap survives\n * promotion as a *live* reference the attacker keeps controlling afterward: `renameSync` moves\n * a symlink as a link, so a link planted in quarantine lands in the trusted root still pointing\n * at attacker-controlled content. Links that stay inside the candidate are fine and are simply\n * hashed by target.\n *\n * Read-only and static — nothing here executes, resolves, or follows anything outside the tree it\n * is asked about.\n */\n\nexport interface FingerprintResult {\n /** sha256 over a canonical listing of the tree: entry type, relative path, and content/target hash. */\n digest: string;\n /** `relPath -> target` for every symlink found, whether or not it escapes. Recorded so a swap changes the digest. */\n symlinks: string[];\n /** Relative paths of symlinks whose target resolves OUTSIDE `root` — promotion refuses these. */\n escaping: string[];\n}\n\n/** Directories never worth hashing: huge, and not part of what a skill declares. */\nconst SKIP_DIRS = new Set(['.git', 'node_modules']);\n\nfunction isInside(child: string, root: string): boolean {\n const rel = relative(root, child);\n // `isAbsolute(rel)` catches the different-root case (Windows drives); `..`/`../` catch escapes.\n return rel === '' || (!rel.startsWith('..' + sep) && rel !== '..' && !isAbsolute(rel));\n}\n\n/**\n * Hashes a directory tree deterministically. Entries are sorted, so the digest depends only on\n * content and structure — never on readdir order, which is filesystem-dependent.\n */\nexport function fingerprintTree(root: string): FingerprintResult {\n const hash = createHash('sha256');\n const symlinks: string[] = [];\n const escaping: string[] = [];\n const realRoot = realpathSync(root);\n\n function walk(dir: string): void {\n const entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));\n\n for (const entry of entries) {\n const full = join(dir, entry.name);\n const rel = relative(root, full);\n\n if (entry.isSymbolicLink()) {\n const target = readlinkSync(full);\n symlinks.push(`${rel} -> ${target}`);\n hash.update(`L\\t${rel}\\t${target}\\n`);\n // Resolve the link against its own directory, then test containment. `realpathSync` on a\n // dangling link throws — a link we cannot resolve is treated as escaping, since we cannot\n // prove it stays inside.\n try {\n if (!isInside(realpathSync(full), realRoot)) escaping.push(rel);\n } catch {\n escaping.push(rel);\n }\n continue;\n }\n\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue;\n hash.update(`D\\t${rel}\\n`);\n walk(full);\n continue;\n }\n\n if (entry.isFile()) {\n const contentHash = createHash('sha256').update(readFileSync(full)).digest('hex');\n hash.update(`F\\t${rel}\\t${contentHash}\\n`);\n continue;\n }\n\n // Sockets, FIFOs, devices: record their presence so a swap is still detected, but never read.\n hash.update(`O\\t${rel}\\n`);\n }\n }\n\n walk(root);\n return { digest: hash.digest('hex'), symlinks, escaping };\n}\n\n/**\n * Fingerprints every skill directory a quarantine entry will promote, in a stable order.\n * `escaping` is unioned across the dirs, prefixed with the dir each came from.\n */\nexport function fingerprintDirs(dirs: string[]): FingerprintResult {\n const hash = createHash('sha256');\n const symlinks: string[] = [];\n const escaping: string[] = [];\n\n for (const dir of [...dirs].sort()) {\n let result: FingerprintResult;\n try {\n result = fingerprintTree(dir);\n } catch (err) {\n // A dir that vanished (or became unreadable) between capture and verify is itself a change:\n // fold the failure into the digest so the comparison fails loudly instead of throwing here.\n hash.update(`MISSING\\t${dir}\\t${(err as Error).message}\\n`);\n continue;\n }\n hash.update(`DIR\\t${dir}\\t${result.digest}\\n`);\n symlinks.push(...result.symlinks.map((s) => `${dir}: ${s}`));\n escaping.push(...result.escaping.map((s) => `${dir}: ${s}`));\n }\n\n return { digest: hash.digest('hex'), symlinks, escaping };\n}\n\n/** True if `path` exists and is a real directory — NOT a symlink to one. */\nexport function isRealDirectory(path: string): boolean {\n try {\n return lstatSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport type { QuarantineEntry } from './quarantine.js';\nimport { UserError } from '../errors.js';\nimport { collectNpxPackageSpecs, isNpxCommand, isPinnedPackageSpec, packageSpecBase } from '../gate/scanShared.js';\n\n/**\n * Minimal shape promoteMcp needs to derive a server config entry. Deliberately\n * decoupled from the skill/plugin/mcp-config profiling in gate/profile.ts —\n * this describes an MCP *server* candidate (name/version + whatever launch\n * info the profile/gate steps found), not a directory that merely ships an\n * .mcp.json referencing other servers.\n */\nexport interface McpServerProfile {\n /** Server name — used as the `mcpServers` config key (e.g. \"@scope/name\" or \"name\"). */\n name: string;\n /**\n * Pinned version to launch via `npx <name>@<version>` — used on the\n * npx-fallback path (no `candidateEntry`; see `deriveServerEntry` for the\n * refusal behavior when both are absent) and, when it matches\n * `packageName`, to auto-pin an unpinned `npx` spec found in a documented\n * `candidateEntry` (v0.7).\n */\n version?: string;\n /**\n * The package name from the candidate's own scanned, self-declared\n * `package.json` (v0.7, from `install/mcpCandidate.ts`'s sidecar read) —\n * for an npm-sourced candidate this IS the registry artifact being\n * profiled, but for a git/local source it's just whatever the candidate's\n * own `package.json` claims, unverified against any registry. Only when a\n * `candidateEntry`'s unpinned `npx` launch spec's package matches this\n * name is `version` above trusted to auto-pin it — a spec for some other\n * package is never pinned to a version scanned for a different package.\n */\n packageName?: string;\n /** Env var NAMES the server declares/needs. Values are never read or copied. */\n envVars?: string[];\n /**\n * The candidate's own documented server entry (e.g. from a README example or a\n * shipped .mcp.json), used for command/args when present instead of the\n * npx-derived default. Any `env` values on it are always stripped before use.\n * An unpinned `npx` launch spec on this entry is auto-pinned when it matches\n * the candidate's own scanned (self-declared) package (see\n * `packageName`/`version` above), otherwise promotion is refused — see\n * `deriveServerEntry` (v0.7).\n */\n candidateEntry?: { command: string; args?: string[]; env?: Record<string, unknown> };\n}\n\nexport interface PromoteMcpOptions {\n force?: boolean;\n}\n\nexport interface PromoteMcpResult {\n /** Absolute path to the target MCP config file that was written. */\n target: string;\n /** Server name (the `mcpServers` key) that was written. */\n serverName: string;\n /** Env var names the user must fill in before the server will work — values were never copied. */\n envVarsToFill: string[];\n /** Backup path created before modifying an existing target file; undefined when the file didn't exist. */\n backupPath?: string;\n /** Set when an unpinned `npx` spec in a documented `candidateEntry` was auto-pinned (v0.7) — the exact spec text before/after. */\n autoPinned?: { from: string; to: string };\n}\n\ninterface McpConfigFile {\n mcpServers?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * Writes `profile`'s derived server entry into `targetConfigPath`'s `mcpServers`\n * map — the only place an MCP candidate is written outside its quarantine\n * sandbox, mirroring install/promote.ts's target-untouched-until-promote\n * invariant for skills. Creates the file (and any missing parent dirs) if\n * absent. Every refusal path (unpinnable version, same-name clash without\n * `force`) is checked before anything is backed up, so a refusal never leaves\n * a partial write behind. When an existing file *is* about to be\n * modified, it's backed up first to `<file>.bak-<timestamp>`. Env values are\n * NEVER copied from the candidate — they're always emitted as empty strings,\n * and the var names the user must fill in are returned.\n */\nexport function promoteMcp(\n _entry: QuarantineEntry,\n profile: McpServerProfile,\n targetConfigPath: string,\n options: PromoteMcpOptions = {}\n): PromoteMcpResult {\n const force = options.force ?? false;\n const serverName = profile.name;\n\n // Can throw (version refusal) — derived before any read/backup so a refusal\n // never leaves a stray .bak file behind.\n const { command, args, envVarsToFill, autoPinned } = deriveServerEntry(profile);\n\n let config: McpConfigFile = {};\n let rawExisting: string | undefined;\n\n if (existsSync(targetConfigPath)) {\n rawExisting = readFileSync(targetConfigPath, 'utf-8');\n try {\n config = JSON.parse(rawExisting) as McpConfigFile;\n } catch (err) {\n throw new UserError(`could not parse existing MCP config ${targetConfigPath}: ${(err as Error).message}`);\n }\n }\n\n const servers = { ...(config.mcpServers ?? {}) };\n if (Object.prototype.hasOwnProperty.call(servers, serverName) && !force) {\n throw new UserError(\n `Refusing to overwrite existing MCP server entry \"${serverName}\" in ${targetConfigPath} (pass force to override)`\n );\n }\n\n let backupPath: string | undefined;\n if (rawExisting !== undefined) {\n backupPath = `${targetConfigPath}.bak-${Date.now()}`;\n writeFileSync(backupPath, rawExisting);\n }\n\n const env: Record<string, string> = {};\n for (const name of envVarsToFill) env[name] = '';\n\n servers[serverName] = { command, args, env };\n config.mcpServers = servers;\n\n mkdirSync(dirname(targetConfigPath), { recursive: true });\n writeFileSync(targetConfigPath, `${JSON.stringify(config, null, 2)}\\n`);\n\n return { target: targetConfigPath, serverName, envVarsToFill, backupPath, autoPinned };\n}\n\n/**\n * Derives {command, args} from the candidate's own documented entry when\n * present, else `npx <name>@<version>` pinned to the profiled version. Env\n * values are always stripped from whichever source is used — only var names\n * are returned. When there's no documented entry AND no resolved version,\n * throws a UserError instead of ever writing an unpinnable `npx pkg@unknown`\n * launch command.\n *\n * A documented `candidateEntry` is used verbatim EXCEPT when its own launch\n * command is `npx` and it carries an unpinned package spec (v0.7 —\n * previously written through unchanged, only ever flagged as a MEDIUM\n * safety warning). `gate/scanShared.ts`'s `collectNpxPackageSpecs()` parses\n * the args the way npx itself does — including every repeatable `-p`/\n * `--package` value, not just the first non-flag token (a naive \"first\n * non-flag arg\" heuristic misses that `-p`/`--package` consumes its value\n * even when that value doesn't start with `-`, which real npx/npm-exec\n * still installs — see that function's own doc comment):\n * - **no specs at all, or every spec found is already pinned** → verbatim.\n * - **exactly one spec total, it's the positional launch spec (not a `-p`\n * value), and it's unpinned**: auto-pinned to `<packageName>@<version>`\n * when the spec's package matches the candidate's own scanned\n * (self-declared) `packageName` and a `version` was scanned for it\n * (`autoPinned` records the change); otherwise refused.\n * - **anything else with an unpinned spec** (an unpinned `-p` value, or\n * more than one spec at all) — refused, naming every unpinned spec.\n * Under-checking a more complex shape must fail closed: there is no\n * auto-pin for a `-p` dependency (it isn't \"the launched package,\" even\n * if it happens to match) or for a multi-spec shape.\n * For an npm-sourced candidate, `packageName`/`version` come from the\n * registry artifact itself; for a git/local source they're only what the\n * candidate's own `package.json` self-declares. Auto-pinning there reduces\n * moving-tag drift (the spec can't silently resolve to something different\n * later) — it does not authenticate registry ownership of that name.\n * There is no override flag for either refusal. Both still run before any\n * read/backup (see promoteMcp() above), so a refusal leaves nothing behind.\n */\nfunction deriveServerEntry(profile: McpServerProfile): {\n command: string;\n args: string[];\n envVarsToFill: string[];\n autoPinned?: { from: string; to: string };\n} {\n const envNames = new Set(profile.envVars ?? []);\n if (profile.candidateEntry?.env) {\n for (const key of Object.keys(profile.candidateEntry.env)) envNames.add(key);\n }\n\n if (profile.candidateEntry) {\n const { command, args = [] } = profile.candidateEntry;\n\n if (isNpxCommand(command)) {\n const { packageFlagSpecs, positionalSpec } = collectNpxPackageSpecs(args);\n const allSpecs = positionalSpec ? [...packageFlagSpecs, positionalSpec] : packageFlagSpecs;\n const unpinned = allSpecs.filter((spec) => !isPinnedPackageSpec(spec));\n\n if (unpinned.length > 0) {\n const isSoleUnpinnedPositional =\n packageFlagSpecs.length === 0 && allSpecs.length === 1 && positionalSpec === unpinned[0];\n\n if (isSoleUnpinnedPositional) {\n const spec = unpinned[0];\n const specPackage = packageSpecBase(spec);\n if (profile.packageName && specPackage === profile.packageName && profile.version) {\n const to = `${profile.packageName}@${profile.version}`;\n const specIdx = args.indexOf(spec);\n const pinnedArgs = [...args];\n pinnedArgs[specIdx] = to;\n return {\n command,\n args: pinnedArgs,\n envVarsToFill: Array.from(envNames),\n autoPinned: { from: spec, to },\n };\n }\n\n const reason =\n profile.packageName && specPackage !== profile.packageName\n ? `the launch spec names package \"${specPackage}\", but the candidate's own package.json is \"${profile.packageName}\" — ` +\n 'pinning it to a version scanned for a different package would be unsafe'\n : \"no version was found in the candidate's own package.json to pin it to\";\n throw new UserError(\n `cannot promote MCP server \"${profile.name}\": its documented launch command \"npx ${spec}\" is unpinned ` +\n `and can't be safely auto-pinned (${reason}). Pin the spec to an exact version in the candidate's ` +\n `config (e.g. \\`${specPackage}@<version>\\`) and try again — there is no override for this.`\n );\n }\n\n throw new UserError(\n `cannot promote MCP server \"${profile.name}\": its documented launch command \"npx\" has unpinned package ` +\n `spec(s) — ${unpinned.map((s) => `\"${s}\"`).join(', ')} — and this shape (a -p/--package dependency, ` +\n \"or more than one spec) can't be safely auto-pinned. Pin every spec to an exact version in the \" +\n \"candidate's config and try again — there is no override for this.\"\n );\n }\n }\n\n return {\n command,\n args,\n envVarsToFill: Array.from(envNames),\n };\n }\n\n if (!profile.version) {\n throw new UserError(\n `cannot promote MCP server \"${profile.name}\": no version was found in its package.json, and there is ` +\n 'no documented launch command to fall back to — refusing to write an unpinnable \"npx ' +\n `${profile.name}@unknown\" command`\n );\n }\n\n return {\n command: 'npx',\n args: ['-y', `${profile.name}@${profile.version}`],\n envVarsToFill: Array.from(envNames),\n };\n}\n","import { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport type { McpProfile } from '../gate/profile.js';\n\n/**\n * Launch details for the ONE MCP server being gated/promoted, derived from a\n * `McpProfile` (`gate/profile.ts`) plus — when the profile came from an\n * explicit `.mcp.json`/`mcp.json` — a second read of that file's chosen\n * server entry. `McpProfile` intentionally only tracks server NAMES (see\n * `profileMcp` in `gate/profile.ts`), not launch command/args/env, since its\n * source config may list more than one server; this is the one place that\n * resolves a profile down to a single server's launch details. Shared by\n * `commands/gatePipeline.ts`'s MCP overlap step and `install/promoteMcp.ts`'s\n * promote step so both derive from exactly the same read.\n */\nexport interface McpCandidateDetail {\n name: string;\n version?: string;\n command?: string;\n args?: string[];\n env?: Record<string, unknown>;\n /**\n * The package `name` from the candidate's own sibling `package.json`\n * (v0.7), when one exists next to an explicit `.mcp.json`/`mcp.json`. Used\n * by `install/promoteMcp.ts` to auto-pin an unpinned `npx` launch spec in\n * a documented `candidateEntry` — only when the spec's package matches\n * this name is the candidate's own scanned `version` trusted for pinning.\n * On the package.json-profile branch, `name` above already comes from\n * this same file, so `packageName` is left unset there (redundant).\n */\n packageName?: string;\n}\n\ninterface ConfigEntry {\n command?: string;\n args?: string[];\n env?: Record<string, unknown>;\n}\n\ninterface PackageInfo {\n name?: string;\n version?: string;\n}\n\n/** Tolerant read of a package.json's `name`/`version` — never throws; returns {} if missing/malformed. */\nfunction readPackageInfo(pkgPath: string): PackageInfo {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as Record<string, unknown>;\n return {\n name: typeof pkg.name === 'string' ? pkg.name : undefined,\n version: typeof pkg.version === 'string' ? pkg.version : undefined,\n };\n } catch {\n return {};\n }\n}\n\nfunction readConfigEntry(configPath: string, name: string): ConfigEntry | undefined {\n try {\n const cfg = JSON.parse(readFileSync(configPath, 'utf-8')) as Record<string, unknown>;\n const servers = (cfg.mcpServers ?? cfg.servers ?? {}) as Record<string, unknown>;\n const raw = (servers[name] ?? {}) as Record<string, unknown>;\n return {\n command: typeof raw.command === 'string' ? raw.command : undefined,\n args: Array.isArray(raw.args) ? raw.args.filter((a): a is string => typeof a === 'string') : undefined,\n env:\n raw.env && typeof raw.env === 'object' && !Array.isArray(raw.env)\n ? (raw.env as Record<string, unknown>)\n : undefined,\n };\n } catch {\n return undefined;\n }\n}\n\n/**\n * Resolves launch details for `profile.servers[0]` — when a config lists more\n * than one server, gates/promotes the first, matching `add`'s/`scan`'s\n * existing \"N skills found — using the first\" convention for multi-skill\n * sources. A package.json-shaped profile (npm-sourced server) has no\n * command/args/env of its own — `promoteMcp` derives the launch command from\n * `name`/`version` (`npx <name>@<version>`) when both are absent here.\n *\n * On the explicit `.mcp.json`/`mcp.json` branch (v0.7), also tolerantly reads\n * a sibling `package.json` next to the config file for its `name`/`version`\n * — populating `version`/`packageName` there too — so `promoteMcp.ts` can\n * auto-pin an unpinned `npx` spec in a documented `candidateEntry` when it\n * provably matches the candidate's own scanned package.\n */\nexport function resolveMcpCandidateDetail(profile: McpProfile): McpCandidateDetail {\n const name = profile.servers[0];\n\n if (profile.config.endsWith('package.json')) {\n return { name, version: readPackageInfo(profile.config).version };\n }\n\n const entry = readConfigEntry(profile.config, name);\n const sidecar = readPackageInfo(join(dirname(profile.config), 'package.json'));\n return {\n name,\n command: entry?.command,\n args: entry?.args,\n env: entry?.env,\n version: sidecar.version,\n packageName: sidecar.name,\n };\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/**\n * Provenance ledger. Appends a structured entry to `<skillsRoot>/SOURCES.md`, the audit\n * ledger of every external-skill ingestion decision. Field names and formatting stay fixed\n * so an existing SOURCES.md ledger stays consistent across every entry ever appended to it.\n */\n\nexport const FORGE_VERBS = ['DEFER', 'ABSORB', 'FORK', 'REJECT', 'WATCH'] as const;\nexport type ForgeVerb = (typeof FORGE_VERBS)[number];\n\nexport interface ProvenanceEntry {\n name: string;\n source: string;\n verb: ForgeVerb;\n version?: string;\n license?: string;\n target?: string;\n took?: string;\n verified?: string;\n driftCheck?: string;\n notes?: string;\n}\n\nconst LEDGER_HEADER = '# Skill Forge — Provenance Ledger\\n\\nOne entry per external-skill ingestion decision.\\n';\n\nfunction todayLocalIsoDate(): string {\n const d = new Date();\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, '0');\n const day = String(d.getDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Appends a provenance entry to `<skillsRoot>/SOURCES.md`, creating the\n * ledger with its header on first write. Throws if `verb` isn't a valid\n * forge verb or `skillsRoot` isn't an existing directory — mirrors the\n * Python script's fail-loud `fail()` calls. Returns the ledger path.\n */\nexport function appendProvenance(entry: ProvenanceEntry, skillsRoot: string): string {\n if (!FORGE_VERBS.includes(entry.verb)) {\n throw new Error(`verb must be one of ${FORGE_VERBS.join(', ')}`);\n }\n if (!existsSync(skillsRoot) || !statSync(skillsRoot).isDirectory()) {\n throw new Error(`skills-root not a directory: ${skillsRoot}`);\n }\n\n const today = todayLocalIsoDate();\n const driftCheck = entry.driftCheck ?? `# define how to detect upstream change for ${entry.name}`;\n\n const block =\n `\\n## ${entry.name} — ${today}\\n` +\n `- **Source:** ${entry.source}\\n` +\n `- **Upstream ref:** ${entry.version ?? 'n/a'}\\n` +\n `- **License:** ${entry.license ?? 'UNKNOWN'}\\n` +\n `- **Verb:** ${entry.verb}\\n` +\n `- **Target:** ${entry.target ?? 'n/a'}\\n` +\n `- **Took:** ${entry.took ?? 'nothing'}\\n` +\n `- **Verified:** ${entry.verified ?? 'n/a'}\\n` +\n `- **Drift check:** \\`${driftCheck}\\`\\n` +\n `- **Notes:** ${entry.notes ?? ''}\\n`;\n\n const ledgerPath = join(skillsRoot, 'SOURCES.md');\n if (!existsSync(ledgerPath)) {\n writeFileSync(ledgerPath, LEDGER_HEADER, 'utf-8');\n }\n appendFileSync(ledgerPath, block, 'utf-8');\n\n return ledgerPath;\n}\n\n// ---------------------------------------------------------------------------\n// Ledger PARSING — inverse of appendProvenance's format, tolerant of hand-edits.\n// Backs `skill-forge watch` (src/commands/watch.ts). Never throws on a single\n// malformed block: it's skipped and a warning is recorded, so one bad\n// hand-edit can't take down the whole drift check.\n// ---------------------------------------------------------------------------\n\n/** One parsed `## <name> — <date>` block from a SOURCES.md ledger. Field values are raw ledger text — untrusted, and must be sanitized (see audit.ts's `sanitizeForMarkdown`) before landing in human-readable output. */\nexport interface ProvenanceLedgerEntry {\n name: string;\n date: string;\n source: string;\n upstreamRef: string;\n license: string;\n verb: ForgeVerb;\n target: string;\n took: string;\n verified: string;\n /** The stored drift-check command string, exactly as recorded — DATA ONLY. Never execute this; it is attacker-influenceable ledger content (see src/commands/watch.ts). */\n driftCheck: string;\n notes: string;\n}\n\nexport interface ParsedProvenanceLedger {\n entries: ProvenanceLedgerEntry[];\n /** One line per skipped/malformed block, describing why it was skipped. Parsing never throws — a bad block is reported here instead. */\n warnings: string[];\n}\n\n/** Extracts a single `**Label:** value` field's value (up to end of line) from a ledger block, or undefined if absent. */\nfunction extractField(block: string, label: string): string | undefined {\n const escaped = label.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const match = block.match(new RegExp(`\\\\*\\\\*${escaped}:\\\\*\\\\*\\\\s*(.*)`));\n return match ? match[1].trim() : undefined;\n}\n\n/**\n * Parses a SOURCES.md ledger's full text into structured entries — the inverse of\n * `appendProvenance`'s block format. Tolerant of hand-edits: a block missing a name/date\n * header or a missing/invalid `Verb` is skipped with a warning rather than thrown. The\n * leading document header (everything before the first `## `) is ignored.\n */\nexport function parseProvenanceLedger(text: string): ParsedProvenanceLedger {\n const entries: ProvenanceLedgerEntry[] = [];\n const warnings: string[] = [];\n\n // Mirrors record_provenance.py's `re.split(r\"\\n## \", text)` — blocks[0] is\n // the document header (or leading garbage before the first entry) and is\n // never treated as an entry.\n const blocks = text.split(/\\n## /);\n\n for (const block of blocks.slice(1)) {\n const headerLine = block.split('\\n', 1)[0] ?? '';\n const [namePart, ...dateParts] = headerLine.split(' — ');\n const name = namePart?.trim();\n if (!name) {\n warnings.push(`skipped malformed ledger block: missing \"## <name> — <date>\" header (saw \"${headerLine.slice(0, 80)}\")`);\n continue;\n }\n const date = dateParts.join(' — ').trim();\n\n const verbRaw = extractField(block, 'Verb');\n if (!verbRaw || !(FORGE_VERBS as readonly string[]).includes(verbRaw)) {\n warnings.push(`skipped malformed ledger block for \"${name}\": missing or invalid Verb (saw \"${verbRaw ?? ''}\")`);\n continue;\n }\n\n const driftCheckMatch = block.match(/\\*\\*Drift check:\\*\\*\\s*`([^`]*)`/);\n\n entries.push({\n name,\n date,\n source: extractField(block, 'Source') ?? '',\n upstreamRef: extractField(block, 'Upstream ref') ?? '',\n license: extractField(block, 'License') ?? '',\n verb: verbRaw as ForgeVerb,\n target: extractField(block, 'Target') ?? '',\n took: extractField(block, 'Took') ?? '',\n verified: extractField(block, 'Verified') ?? '',\n driftCheck: driftCheckMatch ? driftCheckMatch[1] : '',\n notes: extractField(block, 'Notes') ?? '',\n });\n }\n\n return { entries, warnings };\n}\n\nexport interface ReadLedgerResult extends ParsedProvenanceLedger {\n ledgerPath: string;\n exists: boolean;\n}\n\n/**\n * Reads and parses `<skillsRoot>/SOURCES.md`. A missing ledger is not an error here (unlike\n * `appendProvenance`, which is a write path) — `exists: false` with empty entries/warnings lets\n * callers (e.g. `skill-forge watch`) report \"no ledger yet\" for a root cleanly.\n */\nexport function readProvenanceLedger(skillsRoot: string): ReadLedgerResult {\n const ledgerPath = join(skillsRoot, 'SOURCES.md');\n if (!existsSync(ledgerPath)) {\n return { ledgerPath, exists: false, entries: [], warnings: [] };\n }\n const text = readFileSync(ledgerPath, 'utf-8');\n const parsed = parseProvenanceLedger(text);\n return { ledgerPath, exists: true, ...parsed };\n}\n","import { readdirSync } from 'node:fs';\nimport { formatAuditAdvisory, readAuditState } from '../auditState.js';\nimport { join } from 'node:path';\nimport { DEFAULT_STRICTNESS, type Strictness } from '../config.js';\nimport { asProfile, type McpProfile } from '../gate/profile.js';\nimport { scanSafety, type SafetyResult as GateSafetyResult } from '../gate/safety.js';\nimport { scanOverlap, type OverlapResult } from '../gate/overlap.js';\nimport { mcpScan } from '../gate/mcpSafety.js';\nimport { scanMcpOverlap } from '../gate/mcpOverlap.js';\nimport { verdictFor } from '../gate/scanShared.js';\nimport { loadSkillMap } from '../gate/skillMap.js';\nimport { scanMapOverlap, mapOverlapDecision, type MapOverlapDecision } from '../gate/mapOverlap.js';\nimport { resolveMcpCandidateDetail } from '../install/mcpCandidate.js';\nimport type { ArtifactType } from '../install/resolve.js';\nimport type { QuarantineEntry } from '../install/quarantine.js';\nimport { proFeatureStatus } from '../license.js';\nimport { SAFETY_VERDICT_MAP, mapSafetyFindings, type GateResult, type MapOverlapReport } from '../report/render.js';\n\nexport { printGateOutput } from '../report/render.js';\n\n/**\n * Shared profile -> safety -> overlap pipeline used by both `add` and `scan`.\n * Bridges gate/safety.ts's SafetyResult (verdict: pass|warn|block, findings\n * with file/detail) into report/render.ts's SafetyResult shape (verdict:\n * ALLOW|CAUTION|BLOCK, findings with message) so both commands render through\n * the same Step-3 report box.\n */\n\nexport interface GateRunResult {\n /** Gate-level safety result (verdict: pass|warn|block) — drives the promote/reject decision. */\n safety: GateSafetyResult;\n /** Report input; also carries the profile, overlap, and any Pro-feature notice produced by the gate. */\n gateResult: GateResult;\n /** Whether Pro features should run right now (valid license, or the 0.x beta-free policy) — checked once here so callers don't re-verify. */\n proEnabled: boolean;\n}\n\n/** Best-effort overlap: a missing/empty skills root is reported to stderr, not fatal to the gate. */\nfunction tryOverlap(candidatePath: string, skillsRoot: string): OverlapResult | undefined {\n try {\n return scanOverlap(candidatePath, skillsRoot);\n } catch (err) {\n console.error(`[skill-forge] overlap scan skipped: ${(err as Error).message}`);\n return undefined;\n }\n}\n\n/**\n * MCP counterpart of `tryOverlap`: ranks the candidate against every server\n * entry found across `mcpTargets` (see `gate/mcpOverlap.ts`). `scanMcpOverlap`\n * itself already tolerates missing/malformed/empty target files (returns an\n * empty ranking rather than throwing) — this try/catch is defensive only.\n */\nfunction tryMcpOverlap(profile: McpProfile, mcpTargets: string[]): OverlapResult | undefined {\n try {\n const detail = resolveMcpCandidateDetail(profile);\n return scanMcpOverlap(\n {\n name: detail.name,\n command: detail.command,\n args: detail.args,\n packageName: detail.command ? undefined : detail.name,\n },\n mcpTargets\n );\n } catch (err) {\n console.error(`[skill-forge] mcp overlap scan skipped: ${(err as Error).message}`);\n return undefined;\n }\n}\n\n/**\n * Best-effort skill-map overlap (Phase 4, `skill-map-graph-substrate.md`): loads the\n * generated `rhize-plugins` skill map from `skillMapPath` and ranks the candidate's\n * profiled name/description against every `skill` node it contains — see\n * `gate/mapOverlap.ts`'s doc comment for why this is a separate check from\n * `gate/overlap.ts`'s local-skillsRoot overlap. A missing/unreadable/malformed map is\n * reported to stderr and treated as \"no map available\", never fatal to the gate — same\n * failure posture as `tryOverlap`/`tryMcpOverlap` above.\n *\n * `extendsDeclarations` is the candidate's own `metadata.rhize.extends` frontmatter\n * (`SkillProfile.extends`) — passed through to `mapOverlapDecision` so a declared\n * specialization of the top match is downgraded to an informational note instead of a\n * HOLD-level finding (see that function's doc comment).\n */\nfunction tryMapOverlapDecision(\n skillMapPath: string | undefined,\n candidate: { name?: string; description?: string },\n extendsDeclarations: string[]\n): { decision?: MapOverlapDecision; report: MapOverlapReport } {\n if (!skillMapPath) return { report: { status: 'not-requested' } };\n try {\n const map = loadSkillMap(skillMapPath);\n const result = scanMapOverlap(candidate, map);\n return {\n decision: mapOverlapDecision(result, extendsDeclarations),\n report: {\n status: 'checked',\n compared: result.compared,\n topMatch: result.topMatch,\n matches: result.matches,\n },\n };\n } catch (err) {\n const reason = (err as Error).message;\n console.error(`[skill-forge] skill-map overlap check skipped: ${reason}`);\n return { report: { status: 'skipped', reason } };\n }\n}\n\n/**\n * Candidate directory to profile/gate for an MCP artifact (v0.5): the\n * extracted npm package dir when the source was npm, else the single\n * subdirectory `quarantine()` copied/cloned the source into — local dir and\n * git URL, the other two v0.5 MCP source forms, both land in exactly one\n * subdirectory of a freshly created quarantine sandbox (see\n * `install/quarantine.ts`). Falls back to the sandbox root itself if that\n * can't be determined unambiguously.\n */\nexport function resolveMcpCandidatePath(entry: QuarantineEntry): string {\n if (entry.packageDir) return entry.packageDir;\n let children: string[] = [];\n try {\n children = readdirSync(entry.quarantinePath, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => d.name);\n } catch {\n // fall through to the sandbox root\n }\n return children.length === 1 ? join(entry.quarantinePath, children[0]) : entry.quarantinePath;\n}\n\n/**\n * Runs profile -> safety -> overlap for a single quarantined candidate and\n * builds the report input. Overlap analysis (and any other Pro feature the\n * caller gates on `proEnabled`) is checked exactly once via `proFeatureStatus`\n * — during the 0.x beta this is `enabled: true` for everyone, with an\n * optional notice surfaced through `gateResult.notices`; post-1.0 (or with an\n * invalid/expired key once the beta flag flips) it's `enabled: false` and\n * `overlapLocked` is set so the report shows the upgrade notice in place of\n * the overlap lines. `proFeatureLabel` names the Pro feature(s) this\n * particular caller exercises, for the printed/JSON notice text — callers\n * that also gate other Pro features off the same `proEnabled` result (e.g.\n * `add`'s provenance ledger and pending-queue write) should pass a label\n * covering all of them so only one notice is produced per invocation.\n * `strictness` selects which finding severities produce a `block` safety\n * verdict (see `src/gate/safety.ts`); `add`/`scan` pass the value from the\n * config they already load, so the gate honors whatever is configured without\n * re-reading `config.json` here. Callers that omit it (e.g. unit tests) get\n * the `block-high` default. `artifact` (v0.5) routes the whole pipeline: for\n * `'mcp'` (explicit `--artifact mcp` only — never auto-detected here, see\n * `install/resolve.ts`'s \"no ambiguity guessing\" source-resolution policy),\n * safety runs `mcpScan` (base ruleset + MCP-specific rules) instead of\n * `scanSafety`, and overlap ranks against `mcpTargets` (every configured MCP\n * config file) via `scanMcpOverlap` instead of `scanOverlap`-against-\n * `skillsRoot`; `skillsRoot`/`mcpTargets` are simply unused by whichever\n * branch doesn't apply. Defaults to `'skill'`, the pre-v0.5 behavior.\n */\nexport async function runGate(\n candidatePath: string,\n skillsRoot: string,\n {\n proFeatureLabel = 'overlap analysis',\n strictness = DEFAULT_STRICTNESS,\n artifact = 'skill',\n mcpTargets = [],\n skillMapPath,\n }: {\n proFeatureLabel?: string;\n strictness?: Strictness;\n artifact?: ArtifactType;\n mcpTargets?: string[];\n /** Path to the generated `rhize-plugins` skill map (Phase 4) — enables the marketplace-wide curation-overlap check. Skill/plugin artifacts only; unused for 'mcp'. Optional — omitted means no map check runs. */\n skillMapPath?: string;\n } = {}\n): Promise<GateRunResult> {\n const profile = asProfile(candidatePath, artifact);\n const proStatus = proFeatureStatus(proFeatureLabel);\n\n const safety = artifact === 'mcp' ? await mcpScan(candidatePath, strictness) : await scanSafety(candidatePath, strictness);\n const overlap = !proStatus.enabled\n ? undefined\n : artifact === 'mcp'\n ? tryMcpOverlap(profile as McpProfile, mcpTargets)\n : tryOverlap(candidatePath, skillsRoot);\n\n // Free (not Pro-gated) — this enforces the marketplace's own curation rule, a policy\n // check, not a premium ranking feature. Not applicable to 'mcp' artifacts: the map only\n // carries `skill` nodes today.\n const mapOutcome =\n artifact === 'mcp'\n ? { decision: undefined, report: { status: 'not-requested' as const } }\n : tryMapOverlapDecision(\n skillMapPath,\n {\n name: 'name' in profile ? profile.name : undefined,\n description: 'description' in profile ? profile.description : undefined,\n },\n profile.kind === 'skill' ? profile.extends : []\n );\n const mapDecision = mapOutcome.decision;\n const mapFinding = mapDecision?.finding;\n\n // A strong map-overlap match is folded into the same findings list `verdictFor` already\n // grades safety findings against, so it can escalate the verdict (hold/block) rather than\n // sit off to the side as informational-only text nobody has to act on. A DECLARED\n // extension (see `gate/mapOverlap.ts`'s `mapOverlapDecision`) never reaches this list at\n // all — it surfaces below as an informational notice instead, so it can never affect the\n // verdict.\n const combinedSafety = mapFinding\n ? { verdict: verdictFor([...safety.findings, mapFinding], strictness), findings: [...safety.findings, mapFinding] }\n : safety;\n\n // Gathered HERE rather than at each call site: `add` (skill), `add --artifact mcp`, `scan`, and\n // `promote <id>` all funnel through runGate, and when each built this itself they immediately\n // drifted — the MCP path silently had no audit context at all.\n const advisory = formatAuditAdvisory(readAuditState(), {\n targetRoot: skillsRoot || undefined,\n candidateName: 'name' in profile ? profile.name : undefined,\n });\n\n const notices = [\n ...(proStatus.notice ? [proStatus.notice] : []),\n ...(mapDecision?.informational ? [mapDecision.informational] : []),\n ];\n\n const gateResult: GateResult = {\n profile,\n overlap,\n overlapLocked: !proStatus.enabled,\n notices: notices.length > 0 ? notices : undefined,\n mapOverlap: mapOutcome.report,\n safety: {\n verdict: SAFETY_VERDICT_MAP[combinedSafety.verdict],\n findings: mapSafetyFindings(combinedSafety.findings),\n },\n advisory,\n };\n\n return { safety: combinedSafety, gateResult, proEnabled: proStatus.enabled };\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\nimport type { AuditReport } from './audit.js';\nimport { sanitizeForMarkdown } from './audit.js';\nimport type { SuggestedVerbCode } from './gate/overlap.js';\nimport type { Severity } from './gate/scanShared.js';\nimport { getHomeDir } from './config.js';\nimport { writeJsonAtomic } from './util.js';\n\n/**\n * The audit's machine-readable residue: a small summary every `audit` run leaves behind at\n * `~/.skill-forge/audit-state.json`, so the rest of the CLI can cite it.\n *\n * Before v0.11 `audit` was a dead end — it printed a report, wrote a markdown file, and nothing\n * else ever read either. `add`/`scan` therefore gated candidates into roots whose known problems\n * they couldn't mention, and nothing anywhere could tell you your last audit was four months old.\n *\n * **Advisory only, by deliberate design.** Nothing derived from this file may change a safety\n * verdict or auto-reject a candidate. A gate verdict is a function of the CANDIDATE; audit\n * findings describe the SET YOU ALREADY HAVE, and letting the latter silently veto the former\n * would make `add` non-deterministic for the same input and give a stale report the power to\n * block work. It surfaces context next to the decision and leaves the decision where it was.\n */\n\n/** Finding counts by severity — the same `Record<Severity, number>` shape `AuditSummary.findingCounts` uses, so nothing is lost or remapped in translation. */\nexport type AuditStateCounts = Record<Severity, number>;\n\nexport interface AuditStateCluster {\n /** Skill NAMES (not paths) — the report holds paths; names are what a candidate can be compared against. */\n members: string[];\n topScore: number;\n /** Kept as the narrow union rather than widened to `string` at the persistence boundary. */\n suggestedVerb: SuggestedVerbCode;\n}\n\nexport interface AuditState {\n schemaVersion: 1;\n /** ISO-8601 timestamp of the audit run that produced this. */\n lastRunAt: string;\n /** Path of the markdown report that run wrote, when it wrote one. */\n reportPath?: string;\n skillCount: number;\n mcpTargetCount: number;\n /** Whole-set finding counts by severity. */\n findings: AuditStateCounts;\n /** Finding counts keyed by the configured root each finding's skill lives under. */\n byRoot: Record<string, AuditStateCounts>;\n /** Names of skills the audit clustered as overlapping — a candidate matching one is worth flagging. */\n overlapClusters: AuditStateCluster[];\n /** True when the overlap scan was Pro-locked for that run: empty clusters mean \"not analyzed\", not \"none found\". */\n overlapLocked: boolean;\n evolveEligibleCount: number;\n /**\n * Number of findings this run matched to a stored acceptance (v0.12) — additive, defaults to `0`\n * on load so a pre-v0.12 state file stays valid. `findings`/`byRoot` above already count ACTIVE\n * findings only (see `summarizeAudit`'s doc comment) — this field is purely informational, not a\n * second source of the active/accepted split.\n */\n acceptedCount?: number;\n}\n\nexport function getAuditStatePath(): string {\n return join(getHomeDir(), 'audit-state.json');\n}\n\nfunction emptyCounts(): AuditStateCounts {\n return { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };\n}\n\nfunction bump(counts: AuditStateCounts, severity: Severity): void {\n counts[severity] = (counts[severity] ?? 0) + 1;\n}\n\n/** Findings serious enough to mention next to a promote decision. */\nfunction seriousCount(counts: AuditStateCounts): number {\n return (counts.CRITICAL ?? 0) + (counts.HIGH ?? 0) + (counts.MEDIUM ?? 0);\n}\n\n/**\n * Derives the persisted summary from a full `AuditReport`.\n *\n * Findings are attributed to a root by looking the finding's `target` path up in the inventory —\n * a finding whose target isn't a known skill path (an MCP target file, a root-level problem) is\n * counted in the whole-set totals but not attributed to any root, rather than being guessed into\n * one.\n */\nexport function summarizeAudit(report: AuditReport, reportPath?: string): AuditState {\n // Keyed on the skill DIRECTORY only — the parent walk below reaches SKILL.md and anything else\n // inside it, so a second entry per skill would be redundant.\n const rootOfSkillPath = new Map<string, string>();\n for (const skill of report.inventory.skills) {\n rootOfSkillPath.set(skill.path, skill.root);\n }\n\n const byRoot: Record<string, AuditStateCounts> = {};\n for (const finding of report.hygiene) {\n // Walk up the target's parent directories: a finding targets a file inside a skill dir as\n // often as the dir itself. Parent-walking IS the longest-prefix match (scanning the map and\n // taking the first `startsWith` hit would return whichever path happened to be inserted\n // first), and it costs path-depth lookups instead of one pass over every known skill.\n let root: string | undefined;\n for (let dir = finding.target; ; dir = dirname(dir)) {\n root = rootOfSkillPath.get(dir);\n if (root || dirname(dir) === dir) break;\n }\n if (!root) continue;\n byRoot[root] ??= emptyCounts();\n bump(byRoot[root], finding.severity);\n }\n\n return {\n schemaVersion: 1,\n lastRunAt: report.generatedAt,\n reportPath,\n skillCount: report.summary.skillCount,\n mcpTargetCount: report.summary.mcpTargetCount,\n // Taken from the report rather than re-accumulated: `audit.ts` already counted exactly this\n // array, and a second tally is how the terminal summary and the markdown report end up\n // disagreeing about the same run.\n findings: { ...report.summary.findingCounts },\n byRoot,\n overlapClusters: report.opportunities.overlapClusters.map((c) => ({\n // Cluster members are SKILL.md paths; the skill name is the parent directory name. Stripped\n // of control characters at the boundary where they are PERSISTED, because these names come\n // from directories on disk and are later printed into the gate report.\n members: c.members.map((m) => sanitizeForMarkdown(basename(m.replace(/\\/SKILL\\.md$/, '')))),\n topScore: c.topScore,\n suggestedVerb: c.suggestedVerb,\n })),\n overlapLocked: report.opportunities.overlapLocked === true,\n evolveEligibleCount: report.summary.evolveEligibleCount,\n acceptedCount: report.summary.acceptedCount,\n };\n}\n\n/** Persists the summary. Atomic + owner-only via the shared writer in util.ts. */\nexport function writeAuditState(state: AuditState): void {\n writeJsonAtomic(getAuditStatePath(), state);\n}\n\n/**\n * Reads the persisted summary, or undefined when there is none / it is unusable.\n *\n * Never throws: this is consulted from inside `add`'s gate path, and a corrupt advisory file must\n * degrade to \"no audit context\" rather than break an install. A `schemaVersion` this build does\n * not know is treated the same way — future-version state is ignored, not misread.\n */\nexport function readAuditState(): AuditState | undefined {\n const path = getAuditStatePath();\n if (!existsSync(path)) return undefined;\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf-8')) as Partial<AuditState>;\n if (parsed.schemaVersion !== 1 || typeof parsed.lastRunAt !== 'string') return undefined;\n return {\n schemaVersion: 1,\n lastRunAt: parsed.lastRunAt,\n reportPath: parsed.reportPath,\n skillCount: parsed.skillCount ?? 0,\n mcpTargetCount: parsed.mcpTargetCount ?? 0,\n findings: parsed.findings ?? emptyCounts(),\n byRoot: parsed.byRoot ?? {},\n overlapClusters: parsed.overlapClusters ?? [],\n overlapLocked: parsed.overlapLocked === true,\n evolveEligibleCount: parsed.evolveEligibleCount ?? 0,\n acceptedCount: parsed.acceptedCount ?? 0,\n };\n } catch {\n return undefined;\n }\n}\n\n/** Whole days since the audit ran; undefined when the timestamp is unparseable. */\nexport function auditAgeDays(state: AuditState, now: Date = new Date()): number | undefined {\n const then = Date.parse(state.lastRunAt);\n if (Number.isNaN(then)) return undefined;\n return Math.max(0, Math.floor((now.getTime() - then) / 86_400_000));\n}\n\n/** Audits older than this are called out as stale. Two weeks: long enough not to nag, short enough to matter. */\nexport const STALE_AUDIT_DAYS = 14;\n\n/** One-line freshness summary for `status`/`guide`/`routine`. */\nexport function formatAuditFreshness(state: AuditState | undefined, now: Date = new Date()): string {\n if (!state) return 'never run — `skill-forge audit`';\n const age = auditAgeDays(state, now);\n if (age === undefined) return `last run at an unreadable timestamp (${state.lastRunAt})`;\n const when = age === 0 ? 'today' : age === 1 ? '1 day ago' : `${age} days ago`;\n const stale = age >= STALE_AUDIT_DAYS ? ' — stale, consider re-running `skill-forge audit`' : '';\n return `${when}${stale}`;\n}\n\n/**\n * Advisory lines for the gate report — plain text, NO label or padding: `report/render.ts` owns\n * the label column (it knows `LABEL_WIDTH`), so this module cannot misalign against it.\n *\n * `candidateName` is matched against the names in each recorded overlap cluster: promoting a\n * skill into a set that ALREADY has an unresolved overlap cluster around that name is exactly the\n * moment to mention it. Never returns anything that looks like a verdict.\n */\nexport function formatAuditAdvisory(\n state: AuditState | undefined,\n options: { targetRoot?: string; candidateName?: string; now?: Date } = {}\n): string[] {\n if (!state) {\n return ['no audit on record — `skill-forge audit` reports what your installed set already looks like'];\n }\n const lines = [`last audit ${formatAuditFreshness(state, options.now)}`];\n\n if (options.targetRoot) {\n const counts = state.byRoot[options.targetRoot];\n if (counts && seriousCount(counts) > 0) {\n const parts = [\n counts.CRITICAL ? `${counts.CRITICAL} critical` : '',\n counts.HIGH ? `${counts.HIGH} high` : '',\n counts.MEDIUM ? `${counts.MEDIUM} medium` : '',\n ].filter(Boolean);\n lines.push(`target root has ${parts.join(' / ')} open finding(s) from that audit`);\n }\n }\n\n if (options.candidateName) {\n const needle = options.candidateName.toLowerCase();\n const hit = state.overlapClusters.find((c) => c.members.some((m) => m.toLowerCase() === needle));\n if (hit) {\n lines.push(\n `\"${options.candidateName}\" is already in a flagged overlap cluster (${hit.members.join(', ')}; suggested ${hit.suggestedVerb})`\n );\n }\n }\n\n return lines;\n}\n","import {\n existsSync,\n lstatSync,\n mkdtempSync,\n readFileSync,\n readdirSync,\n realpathSync,\n renameSync,\n rmSync,\n statSync,\n writeFileSync,\n type Dirent,\n} from 'node:fs';\nimport { basename, delimiter, extname, join, resolve as resolvePath } from 'node:path';\nimport type { BusinessProfile, Config, Strictness } from './config.js';\nimport { UserError } from './errors.js';\nimport { scanSafety } from './gate/safety.js';\nimport { scanMcpFindings } from './gate/mcpSafety.js';\nimport { scanOverlapPaths, suggestVerbCode, type OverlapPathPair, type SuggestedVerbCode } from './gate/overlap.js';\nimport {\n collectNpxPackageSpecs,\n formatSafetyFinding,\n isNpxCommand,\n verdictFor,\n type SafetyFinding,\n type Severity,\n type Verdict,\n} from './gate/scanShared.js';\nimport { fingerprintFinding, FINGERPRINT_DISPLAY_LENGTH, type AcceptedFinding } from './findings.js';\nimport { proFeatureStatus } from './license.js';\nimport { appendProvenance } from './provenance.js';\nimport { expandHome, isDir, stripQuotes } from './util.js';\n\n/**\n * `skill-forge audit` engine (v0.8, MIT, side-effect-free except\n * `scaffoldFoundationSkill`) — a doctor-style health check over the\n * EXISTING configured skill roots and MCP targets: inventory, static\n * hygiene/safety findings, and consolidation/refinement opportunities. The\n * decide/absorb judgment itself is handed to the user's coding agent via the\n * bundled `assets/curation-prompt.md` (see src/handoff.ts) — this module\n * only produces the static evidence, never a verdict on what to actually do.\n *\n * Nothing scanned by this module is ever executed. `auditConfiguredSet` only\n * reads; `scaffoldFoundationSkill` is the one function here that writes, and\n * only under an explicit caller opt-in (see src/commands/audit.ts).\n */\n\n/** Threshold above which a SKILL.md's estimated token count becomes an `oversized-skill` finding. */\nexport const OVERSIZED_SKILL_EST_TOKENS = 5_000;\n\nexport interface AuditOptions {\n /** Overrides gate/overlap.ts's default cross-root overlap threshold (0.45). */\n overlapThreshold?: number;\n /** Overrides OVERSIZED_SKILL_EST_TOKENS for the oversized-skill finding. */\n oversizedSkillTokens?: number;\n /** Injectable clock for deterministic `generatedAt` in tests. Defaults to `() => new Date()`. */\n now?: () => Date;\n /** Injectable PATH lookup for the tooling section (`skillspector` detection). Defaults to a real PATH scan. */\n which?: (cmd: string) => string | undefined;\n /** Injectable environment for the tooling section (VERCEL_OIDC_TOKEN / SKILLSPECTOR_PROVIDER / provider key presence). Defaults to `process.env`. */\n env?: NodeJS.ProcessEnv;\n /**\n * Accepted-findings records to partition active findings against (v0.12) — injectable like\n * `now`/`which`/`env` so the engine stays pure. Callers (`commands/audit.ts`,\n * `commands/routine.ts`, `commands/finding.ts`) load `~/.skill-forge/accepted-findings.json` via\n * `src/findings.ts`'s `readAcceptedFindings()` and pass its `records` here; any store-read\n * notices they got back are the caller's job to fold into the returned report, not this engine's.\n * Defaults to `[]` — nothing accepted, every finding reports active.\n */\n accepted?: AcceptedFinding[];\n}\n\nexport interface AuditedRoot {\n /** The root exactly as configured (may contain `~`). */\n root: string;\n /** Resolved realpath, or the configured root itself when it doesn't exist / can't be resolved. */\n realPath: string;\n exists: boolean;\n skillCount: number;\n}\n\nexport interface AuditedSkill {\n name: string;\n /** Canonical (first-seen, realpath-deduped) skill directory. */\n path: string;\n skillMdPath: string;\n /** The configured root this skill was discovered under. */\n root: string;\n /** Other configured-root paths (symlink aliases) that resolve to this same physical skill. */\n aliasPaths: string[];\n validFrontmatter: boolean;\n description?: string;\n sizeBytes: number;\n estTokens: number;\n}\n\nexport interface AuditedMcpServer {\n name: string;\n commandBasename?: string;\n packageSpec?: string;\n argCount: number;\n envCount: number;\n}\n\nexport type AuditedMcpTargetStatus = 'clean' | 'missing' | 'unreadable' | 'malformed';\n\nexport interface AuditedMcpTarget {\n path: string;\n format: 'json' | 'toml';\n status: AuditedMcpTargetStatus;\n /** Server enumeration — JSON targets only; always empty for TOML (structured enumeration unsupported) or a non-clean status. */\n servers: AuditedMcpServer[];\n}\n\nexport interface AuditFinding {\n severity: Severity;\n rule: string;\n target: string;\n detail: string;\n /** Content-derived fingerprinting input (v0.12) — see `src/findings.ts`'s module doc. Additive/optional. */\n context?: string;\n /**\n * sha256 identity computed by this engine for EVERY finding (active or accepted) — v0.12,\n * additive. Absent only on a pre-existing `AuditFinding` object built by hand rather than by\n * `auditConfiguredSet` (e.g. `test/auditState.test.ts`'s fixtures).\n */\n fingerprint?: string;\n}\n\n/** One record in `AuditReport.accepted` (v0.12) — an active finding this run matched to a stored acceptance, paired with why and when a human accepted it. */\nexport interface AcceptedFindingEntry {\n finding: AuditFinding;\n reason: string;\n acceptedAt: string;\n}\n\n/** Reshapes gate/scanShared.ts's `{severity,rule,file,detail,context?}` SafetyFinding into this module's `{severity,rule,target,detail,context?}` AuditFinding — shared by the per-skill safety scan and the MCP target scan. */\nfunction toAuditFindings(source: SafetyFinding[]): AuditFinding[] {\n return source.map((f) => ({ severity: f.severity, rule: f.rule, target: f.file, detail: f.detail, context: f.context }));\n}\n\n/** Reshapes AuditFinding[] back into SafetyFinding[] shape (target -> file) so `gate/scanShared.ts`'s `verdictFor` can be reused for the v0.12 evolve-eligible recompute below, without a second severity->verdict mapping. */\nfunction activeVerdictFor(findings: AuditFinding[], strictness: Strictness): Verdict {\n return verdictFor(\n findings.map((f) => ({ severity: f.severity, rule: f.rule, file: f.target, detail: f.detail })),\n strictness\n );\n}\n\nexport interface OverlapCluster {\n /** SKILL.md paths in this connected component, sorted for determinism — always at least 2 (a cluster only exists when at least one scored pair formed it). */\n members: [string, string, ...string[]];\n topScore: number;\n /** Bare verb code (`suggestVerbCode`'s typed union) — markdown renders it as-is; prose belongs to the agent-side curation prompt, not this report. */\n suggestedVerb: SuggestedVerbCode;\n}\n\nexport interface EvolveCandidate {\n name: string;\n path: string;\n}\n\n/** Discriminated on `available`: the `false` branch always carries the existing path (never optional-and-absent by accident). */\nexport type FoundationOpportunity =\n | { available: true; message: string }\n | { available: false; existingPath: string; message: string };\n\nexport interface AuditSummary {\n rootCount: number;\n skillCount: number;\n mcpTargetCount: number;\n /** Counts ACTIVE findings only (v0.12) — an accepted finding is excluded, same rationale as `hygiene` below. */\n findingCounts: Record<Severity, number>;\n overlapClusterCount: number;\n evolveEligibleCount: number;\n /** Number of active findings this run matched to a stored acceptance (v0.12). */\n acceptedCount: number;\n}\n\nexport interface AuditInventory {\n skillRoots: AuditedRoot[];\n skills: AuditedSkill[];\n mcpTargets: AuditedMcpTarget[];\n}\n\nexport interface AuditOpportunities {\n overlapClusters: OverlapCluster[];\n evolveEligible: EvolveCandidate[];\n foundation: FoundationOpportunity;\n /** True when the Pro overlap gate was locked for this run (see `notices` for the upgrade notice) — lets JSON consumers branch on this instead of string-matching `notices`. */\n overlapLocked: boolean;\n}\n\n/** `skillspector` CLI (deep local safety scan, github.com/NVIDIA/SkillSpector) readiness — see gate/safety.ts's own PATH-based `scanWithSkillSpector`, which this only reports on, never invokes. */\nexport interface AuditToolingSkillspector {\n installed: boolean;\n /** Setup guidance, present only when `installed` is false. */\n hint?: string;\n}\n\n/** `VERCEL_OIDC_TOKEN` presence — the auth anchor for skills.sh discovery/partner audits (`skill-forge find`). Presence only; the token value is never read into a report. */\nexport interface AuditToolingSkillsShToken {\n present: boolean;\n /** Setup guidance, present only when `present` is false. */\n hint?: string;\n}\n\n/** Optional SkillSpector LLM (semantic) stage readiness — static scanning works without it. Provider NAME only; the associated API key's value is never read into a report. */\nexport interface AuditToolingSkillspectorLlm {\n configured: boolean;\n provider?: string;\n}\n\nexport interface AuditTooling {\n skillspector: AuditToolingSkillspector;\n skillsShToken: AuditToolingSkillsShToken;\n skillspectorLlm: AuditToolingSkillspectorLlm;\n}\n\nexport interface AuditReport {\n schemaVersion: 1;\n generatedAt: string;\n inventory: AuditInventory;\n /** ACTIVE findings only (v0.12) — an accepted finding moves to `accepted` below, never disappears. */\n hygiene: AuditFinding[];\n /** Findings this run matched to a stored acceptance (v0.12) — permanently visible, excluded from `hygiene`/`summary.findingCounts`. */\n accepted: AcceptedFindingEntry[];\n opportunities: AuditOpportunities;\n /** Skill/MCP tooling readiness (skillspector CLI, skills.sh auth, optional SkillSpector LLM stage) — port of skill_doctor.py. Additive (v0.8.1); absent only on a pre-existing report object built by hand rather than by `auditConfiguredSet`. */\n tooling?: AuditTooling;\n notices: string[];\n summary: AuditSummary;\n}\n\n// ---------------------------------------------------------------------------\n// Inventory: symlink-aware skill discovery\n// ---------------------------------------------------------------------------\n\n/** Resolves a configured root (which may contain `~`) to an absolute filesystem path, without probing existence. */\nfunction resolveConfiguredRoot(root: string): string {\n return resolvePath(expandHome(root));\n}\n\n/** `statSync` that follows symlinks and swallows any error (missing/broken/permission) rather than throwing. */\nfunction safeStat(p: string) {\n try {\n return statSync(p);\n } catch {\n return undefined;\n }\n}\n\nfunction isDirFollowingSymlink(p: string): boolean {\n return safeStat(p)?.isDirectory() ?? false;\n}\n\n/**\n * Subdirectories of `dir`, including symlinked ones that resolve to a directory. Deliberately\n * does NOT rely on `Dirent#isDirectory()` alone — for a symlink entry, `readdir`'s dirent type\n * reflects the link itself (DT_LNK), not what it points at, so a symlinked skills root or skill\n * dir would otherwise be silently skipped (the same lesson noted on gate/overlap.ts's `find`-based\n * walker, which has the identical gap).\n */\nfunction listSubdirsFollowingSymlinks(dir: string): string[] {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const out: string[] = [];\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory() || (entry.isSymbolicLink() && isDirFollowingSymlink(full))) {\n out.push(full);\n }\n }\n return out;\n}\n\n/**\n * Same shape as `listSubdirsFollowingSymlinks`, but does NOT follow symlinked entries — only\n * real directories (`Dirent#isDirectory()`, no stat-follow). Used exclusively by\n * `findSkillDirsUnderRoot`'s third (nested `topDir/skills/*`) discovery loop, to close a\n * symlink-escape path: a malicious INSTALLED skill (itself a legitimate top-level entry under a\n * configured root) could otherwise plant its own `skills/<name>` symlink pointing outside the\n * root, and have whatever SKILL.md lives at that external destination silently absorbed into\n * this root's inventory — audited and disclosed into reports/handoffs as if it belonged to the\n * configured root. Top-level (`root/*`) and `root/skills/*` discovery deliberately keep\n * following symlinks — legitimately symlinked skill dirs at those levels are a primary use case.\n */\nfunction listSubdirsNoSymlinks(dir: string): string[] {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n return entries.filter((e) => e.isDirectory()).map((e) => join(dir, e.name));\n}\n\n/** Same three glob shapes as gate/overlap.ts's `findSkillMdFiles`, but symlink-aware (see `listSubdirsFollowingSymlinks`) and returning skill DIRECTORIES, not SKILL.md paths. */\nfunction findSkillDirsUnderRoot(root: string): string[] {\n const found = new Set<string>();\n const rootSubdirs = listSubdirsFollowingSymlinks(root);\n\n for (const dir of rootSubdirs) {\n if (existsSync(join(dir, 'SKILL.md'))) found.add(dir);\n }\n for (const dir of listSubdirsFollowingSymlinks(join(root, 'skills'))) {\n if (existsSync(join(dir, 'SKILL.md'))) found.add(dir);\n }\n for (const topDir of rootSubdirs) {\n // Nested loop: does NOT follow symlinked entries — see listSubdirsNoSymlinks's doc comment.\n for (const dir of listSubdirsNoSymlinks(join(topDir, 'skills'))) {\n if (existsSync(join(dir, 'SKILL.md'))) found.add(dir);\n }\n }\n\n return Array.from(found).filter((p) => !p.includes('/.git/'));\n}\n\ninterface SkillCandidate {\n /** Path as discovered by the shape-walk above — may traverse through a symlink. */\n configuredPath: string;\n /** The configured root (raw, as given in config.skillsRoots) this candidate was found under. */\n root: string;\n}\n\ninterface RootMeta {\n exists: boolean;\n realPath?: string;\n}\n\n/** Probes every configured root and collects every skill-dir candidate found under it (pre-dedupe). */\nfunction collectSkillCandidates(roots: readonly string[]): {\n candidates: SkillCandidate[];\n rootMeta: Map<string, RootMeta>;\n} {\n const candidates: SkillCandidate[] = [];\n const rootMeta = new Map<string, RootMeta>();\n\n for (const root of roots) {\n const resolvedRoot = resolveConfiguredRoot(root);\n const exists = isDir(resolvedRoot);\n let realPath: string | undefined;\n if (exists) {\n try {\n realPath = realpathSync(resolvedRoot);\n } catch {\n realPath = undefined;\n }\n }\n rootMeta.set(root, { exists, realPath });\n if (!exists) continue;\n\n for (const dir of findSkillDirsUnderRoot(resolvedRoot)) {\n candidates.push({ configuredPath: dir, root });\n }\n }\n\n return { candidates, rootMeta };\n}\n\ninterface ResolvedSkillGroup {\n realPath: string;\n /** First configured path seen for this physical skill — the canonical location reported. */\n canonicalPath: string;\n root: string;\n /** Other configured-root paths (symlink aliases) resolving to the same realPath. */\n aliasPaths: string[];\n}\n\n/**\n * Dedupes skill candidates by `realpathSync` so the same physical skill reached via two roots\n * (or a symlinked alias within one root) is reported once, with alias locations retained rather\n * than dropped. A candidate whose realpath can't be resolved (broken symlink, or an `ELOOP`\n * self-referential symlink cycle) becomes an `unreadable-skill` finding and is skipped, rather\n * than aborting the whole audit.\n */\nfunction dedupeSkillCandidates(candidates: SkillCandidate[], findings: AuditFinding[]): ResolvedSkillGroup[] {\n const byRealPath = new Map<string, ResolvedSkillGroup>();\n const order: string[] = [];\n\n for (const candidate of candidates) {\n let realPath: string;\n try {\n realPath = realpathSync(candidate.configuredPath);\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'unreadable-skill',\n target: candidate.configuredPath,\n detail: `could not resolve skill directory (broken symlink or cycle): ${(err as Error).message}`,\n });\n continue;\n }\n\n const existing = byRealPath.get(realPath);\n if (existing) {\n if (existing.canonicalPath !== candidate.configuredPath && !existing.aliasPaths.includes(candidate.configuredPath)) {\n existing.aliasPaths.push(candidate.configuredPath);\n }\n continue;\n }\n\n byRealPath.set(realPath, { realPath, canonicalPath: candidate.configuredPath, root: candidate.root, aliasPaths: [] });\n order.push(realPath);\n }\n\n return order.map((realPath) => byRealPath.get(realPath)!);\n}\n\n// ---------------------------------------------------------------------------\n// Strict frontmatter validation — deliberately NOT gate/overlap.ts's readFrontmatter,\n// whose dirname/heading fallbacks exist for scoring robustness, not validation.\n// ---------------------------------------------------------------------------\n\ninterface StrictFrontmatterResult {\n valid: boolean;\n name?: string;\n description?: string;\n errors: string[];\n}\n\n/** Strict frontmatter parse: a missing/unclosed `---` fence or a missing/empty `name`/`description` field is a finding, never silently backfilled. */\nfunction parseStrictFrontmatter(text: string): StrictFrontmatterResult {\n if (!text.startsWith('---\\n')) {\n return { valid: false, errors: ['missing frontmatter fence (SKILL.md must start with \"---\")'] };\n }\n\n const afterOpen = text.slice(4);\n const closeIdx = afterOpen.indexOf('\\n---');\n if (closeIdx === -1) {\n return { valid: false, errors: ['unclosed frontmatter fence (no closing \"---\" found)'] };\n }\n const block = afterOpen.slice(0, closeIdx);\n const errors: string[] = [];\n\n const nameMatch = block.match(/^name:\\s*(.+)$/m);\n const name = nameMatch ? stripQuotes(nameMatch[1].trim()) : undefined;\n if (!name) errors.push('missing required \"name\" field in frontmatter');\n\n const descMatch = block.match(/^description:\\s*(.*)$/m);\n let description: string | undefined;\n if (descMatch) {\n const first = descMatch[1].trim();\n if (['>', '>-', '|', '|-', ''].includes(first)) {\n const lines = block.split(/\\r\\n|\\r|\\n/);\n const idx = lines.findIndex((l) => /^description:/.test(l));\n const buf: string[] = [];\n if (idx !== -1) {\n for (const l of lines.slice(idx + 1)) {\n if (/^\\s+\\S/.test(l)) buf.push(l.trim());\n else if (l.trim() === '') continue;\n else break;\n }\n }\n description = buf.join(' ').trim() || undefined;\n } else {\n description = stripQuotes(first);\n }\n }\n if (!description) errors.push('missing required \"description\" field in frontmatter');\n\n return { valid: errors.length === 0, name, description, errors };\n}\n\n/** Ceil(byteLength/4) — a rough, always-labeled-\"est.\" token estimate. Shared by the hygiene oversized check and the report renderer. */\nexport function estimateTokens(text: string): number {\n return Math.ceil(Buffer.byteLength(text, 'utf-8') / 4);\n}\n\nfunction profileAuditedSkill(\n group: ResolvedSkillGroup,\n findings: AuditFinding[],\n unreadableSkillMdPaths: Set<string>\n): AuditedSkill {\n const skillMdPath = join(group.canonicalPath, 'SKILL.md');\n const base: Omit<AuditedSkill, 'name' | 'validFrontmatter' | 'description' | 'sizeBytes' | 'estTokens'> = {\n path: group.canonicalPath,\n skillMdPath,\n root: group.root,\n aliasPaths: group.aliasPaths,\n };\n\n let text: string;\n let sizeBytes: number;\n try {\n sizeBytes = statSync(skillMdPath).size;\n text = readFileSync(skillMdPath, 'utf-8');\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'unreadable-skill',\n target: skillMdPath,\n detail: `could not read SKILL.md: ${(err as Error).message}`,\n });\n // Excluded from the cross-root overlap scan below (see auditConfiguredSet) — that scanner's\n // readFrontmatter does an unguarded readFileSync, so feeding it a path we already know is\n // unreadable would abort the whole audit rather than degrade gracefully.\n unreadableSkillMdPaths.add(skillMdPath);\n return { ...base, name: basename(group.canonicalPath), validFrontmatter: false, sizeBytes: 0, estTokens: 0 };\n }\n\n const fm = parseStrictFrontmatter(text);\n if (!fm.valid) {\n for (const error of fm.errors) {\n findings.push({ severity: 'MEDIUM', rule: 'malformed-frontmatter', target: skillMdPath, detail: error });\n }\n }\n\n return {\n ...base,\n name: fm.name ?? basename(group.canonicalPath),\n validFrontmatter: fm.valid,\n description: fm.description,\n sizeBytes,\n estTokens: estimateTokens(text),\n };\n}\n\n// ---------------------------------------------------------------------------\n// MCP target inventory — JSON structured enumeration (names/basenames/specs/COUNTS\n// only, never env values or arbitrary arg values); TOML is textual-scan-only.\n// ---------------------------------------------------------------------------\n\ninterface RawMcpServerEntry {\n command?: unknown;\n args?: unknown;\n env?: unknown;\n}\n\n/** True when a parsed JSON document has an `mcpServers` or `servers` object — the shape `enumerateMcpServers` knows how to read. Shared with `auditMcpTarget`'s `unrecognized-mcp-target-shape` finding so the two can't drift on what counts as \"MCP-shaped\". */\nfunction hasRecognizedMcpShape(parsed: unknown): boolean {\n if (typeof parsed !== 'object' || parsed === null) return false;\n const obj = parsed as Record<string, unknown>;\n const serversRaw = obj.mcpServers ?? obj.servers;\n return typeof serversRaw === 'object' && serversRaw !== null;\n}\n\n/** Enumerates a parsed MCP config's `mcpServers`/`servers` map into name/command-basename/package-spec/COUNTS only — see AuditedMcpServer's doc comment on the report shape. */\nfunction enumerateMcpServers(parsed: unknown): AuditedMcpServer[] {\n if (!hasRecognizedMcpShape(parsed)) return [];\n const obj = parsed as Record<string, unknown>;\n const serversRaw = obj.mcpServers ?? obj.servers;\n\n const out: AuditedMcpServer[] = [];\n for (const [name, raw] of Object.entries(serversRaw as Record<string, unknown>)) {\n if (typeof raw !== 'object' || raw === null) continue;\n const entry = raw as RawMcpServerEntry;\n const command = typeof entry.command === 'string' ? entry.command : undefined;\n const args = Array.isArray(entry.args) ? entry.args.filter((a): a is string => typeof a === 'string') : [];\n const env = entry.env && typeof entry.env === 'object' && !Array.isArray(entry.env) ? (entry.env as Record<string, unknown>) : {};\n\n out.push({\n name,\n commandBasename: command ? basename(command) : undefined,\n packageSpec: command && isNpxCommand(command) ? collectNpxPackageSpecs(args).positionalSpec : undefined,\n argCount: args.length,\n envCount: Object.keys(env).length,\n });\n }\n return out;\n}\n\nfunction auditMcpTarget(targetPath: string, notices: string[]): { target: AuditedMcpTarget; findings: AuditFinding[] } {\n const findings: AuditFinding[] = [];\n const isToml = extname(targetPath).toLowerCase() === '.toml';\n const format: AuditedMcpTarget['format'] = isToml ? 'toml' : 'json';\n\n if (!existsSync(targetPath)) {\n findings.push({\n severity: 'LOW',\n rule: 'missing-mcp-target',\n target: targetPath,\n detail: 'configured MCP target file does not exist',\n });\n return { target: { path: targetPath, format, status: 'missing', servers: [] }, findings };\n }\n\n // Textual scan (unpinned npx, inline credentials, dangerous flags) runs for both\n // formats — it's the ONLY enumeration TOML gets; JSON also gets structured below.\n try {\n findings.push(...toAuditFindings(scanMcpFindings(targetPath)));\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'unreadable-mcp-target',\n target: targetPath,\n detail: `MCP safety scan failed: ${(err as Error).message}`,\n });\n }\n\n if (isToml) {\n // Explicit readability probe: scanMcpFindings above swallows read failures internally\n // (readTextFile catches and returns undefined -> no findings, no throw), so without this\n // check an unreadable TOML file would otherwise fall straight through to a hard-coded\n // 'clean' status below.\n try {\n readFileSync(targetPath, 'utf-8');\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'unreadable-mcp-target',\n target: targetPath,\n detail: `could not read: ${(err as Error).message}`,\n });\n return { target: { path: targetPath, format, status: 'unreadable', servers: [] }, findings };\n }\n\n notices.push(`MCP target ${targetPath}: TOML format — structured server enumeration unsupported (textual safety scan only)`);\n return { target: { path: targetPath, format, status: 'clean', servers: [] }, findings };\n }\n\n let raw: string;\n try {\n raw = readFileSync(targetPath, 'utf-8');\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'unreadable-mcp-target',\n target: targetPath,\n detail: `could not read: ${(err as Error).message}`,\n });\n return { target: { path: targetPath, format, status: 'unreadable', servers: [] }, findings };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'malformed-mcp-target',\n target: targetPath,\n detail: `invalid JSON: ${(err as Error).message}`,\n });\n return { target: { path: targetPath, format, status: 'malformed', servers: [] }, findings };\n }\n\n if (!hasRecognizedMcpShape(parsed)) {\n findings.push({\n severity: 'LOW',\n rule: 'unrecognized-mcp-target-shape',\n target: targetPath,\n detail: 'valid JSON but no \"mcpServers\" or \"servers\" object was found — reporting 0 servers',\n });\n }\n\n return { target: { path: targetPath, format, status: 'clean', servers: enumerateMcpServers(parsed) }, findings };\n}\n\n// ---------------------------------------------------------------------------\n// Opportunities: cross-root overlap clustering (Pro), evolve eligibility, foundation\n// ---------------------------------------------------------------------------\n\n/** Union-find connected-component clustering over overlap pairs already filtered to >= threshold by scanOverlapPaths. */\nfunction clusterOverlapPairs(pairs: OverlapPathPair[]): OverlapCluster[] {\n const parent = new Map<string, string>();\n\n function find(x: string): string {\n if (!parent.has(x)) parent.set(x, x);\n let root = x;\n while (parent.get(root) !== root) root = parent.get(root)!;\n let cur = x;\n while (parent.get(cur) !== root) {\n const next = parent.get(cur)!;\n parent.set(cur, root);\n cur = next;\n }\n return root;\n }\n\n function union(a: string, b: string): void {\n const ra = find(a);\n const rb = find(b);\n if (ra !== rb) parent.set(ra, rb);\n }\n\n for (const pair of pairs) union(pair.aPath, pair.bPath);\n\n const groups = new Map<string, { members: Set<string>; topScore: number }>();\n for (const pair of pairs) {\n const root = find(pair.aPath);\n const group = groups.get(root) ?? { members: new Set<string>(), topScore: 0 };\n group.members.add(pair.aPath);\n group.members.add(pair.bPath);\n group.topScore = Math.max(group.topScore, pair.score);\n groups.set(root, group);\n }\n\n return Array.from(groups.values())\n .map((g) => ({ members: toClusterMembers(g.members), topScore: g.topScore, suggestedVerb: suggestVerbCode(g.topScore) }))\n .sort((a, b) => b.topScore - a.topScore || a.members[0].localeCompare(b.members[0]));\n}\n\n/** Sorts and type-asserts a cluster's member-path set into the `[string, string, ...string[]]` tuple floor — safe because a cluster is only ever built from >= 1 scored pair (see `clusterOverlapPairs`), so it always has at least 2 distinct member paths. */\nfunction toClusterMembers(members: Set<string>): [string, string, ...string[]] {\n const sorted = Array.from(members).sort();\n if (sorted.length < 2) {\n throw new Error(`internal error: overlap cluster has fewer than 2 members (${sorted.length})`);\n }\n return sorted as [string, string, ...string[]];\n}\n\n// ---------------------------------------------------------------------------\n// Tooling readiness — port of skill_doctor.py (rhize-plugins). Read-only:\n// PATH lookup + environment-variable PRESENCE checks only. Never reads a\n// secret's value into the report or --json output — provider names and\n// booleans only.\n// ---------------------------------------------------------------------------\n\n/** Locate an executable on PATH without shelling out — mirrors gate/safety.ts's own (unexported) `which`, duplicated here rather than imported so audit.ts's tooling check stays a pure PATH/env read with no dependency on gate/safety.ts's internals. */\nfunction defaultWhich(cmd: string): string | undefined {\n const pathEnv = process.env.PATH ?? '';\n const dirs = pathEnv.split(delimiter).filter(Boolean);\n const exts = process.platform === 'win32' ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT').split(';') : [''];\n for (const dir of dirs) {\n for (const ext of exts) {\n const candidate = join(dir, cmd + ext);\n try {\n if (statSync(candidate).isFile()) return candidate;\n } catch {\n // not found in this dir, keep looking\n }\n }\n }\n return undefined;\n}\n\n/** Maps a `SKILLSPECTOR_PROVIDER` value to the env var holding its API key — mirrors skill_doctor.py's `keymap`. */\nconst SKILLSPECTOR_PROVIDER_KEYMAP: Record<string, string> = {\n openai: 'OPENAI_API_KEY',\n anthropic: 'ANTHROPIC_API_KEY',\n nv_build: 'NVIDIA_INFERENCE_KEY',\n};\n\n/**\n * Port of skill_doctor.py's readiness checks: `skillspector` on PATH (deep local safety scan),\n * `VERCEL_OIDC_TOKEN` presence (skills.sh discovery/partner audits — presence only, deliberately\n * narrower than skill_doctor.py's additional `.env`/`.env.local` file grep, since this is a\n * side-effect-free audit engine that otherwise never reads arbitrary project files), and the\n * optional SkillSpector LLM stage (provider name + whether its key env var is set — never the\n * key's value).\n */\nfunction checkTooling(which: (cmd: string) => string | undefined, env: NodeJS.ProcessEnv): AuditTooling {\n const skillspectorPath = which('skillspector');\n const skillspector: AuditToolingSkillspector = skillspectorPath\n ? { installed: true }\n : {\n installed: false,\n hint: 'NOT installed — github.com/NVIDIA/skillspector (git clone + make install; Python 3.12+)',\n };\n\n const tokenPresent = Boolean(env.VERCEL_OIDC_TOKEN);\n const skillsShToken: AuditToolingSkillsShToken = tokenPresent\n ? { present: true }\n : {\n present: false,\n hint: 'missing — enable OIDC Federation on your Vercel project, then `vercel link && vercel env pull`',\n };\n\n const provider = env.SKILLSPECTOR_PROVIDER;\n const providerKeyEnv = provider ? SKILLSPECTOR_PROVIDER_KEYMAP[provider] : undefined;\n const skillspectorLlm: AuditToolingSkillspectorLlm = {\n configured: Boolean(provider && providerKeyEnv && env[providerKeyEnv]),\n provider: provider || undefined,\n };\n\n return { skillspector, skillsShToken, skillspectorLlm };\n}\n\n// ---------------------------------------------------------------------------\n// Top-level engine\n// ---------------------------------------------------------------------------\n\n/**\n * Side-effect-free audit of every configured skills root and MCP target: inventory, structural +\n * safety hygiene findings, and consolidation/refinement opportunities. Never executes anything it\n * scans. Per-item failures (unreadable skill, missing MCP target, malformed JSON, ...) become\n * findings and never abort the run.\n */\nexport async function auditConfiguredSet(config: Config, options: AuditOptions = {}): Promise<AuditReport> {\n const now = options.now ?? (() => new Date());\n const oversizedThreshold = options.oversizedSkillTokens ?? OVERSIZED_SKILL_EST_TOKENS;\n const findings: AuditFinding[] = [];\n const notices: string[] = [];\n\n // --- Inventory: skill roots + skills ---\n const { candidates, rootMeta } = collectSkillCandidates(config.skillsRoots);\n const groups = dedupeSkillCandidates(candidates, findings);\n const unreadableSkillMdPaths = new Set<string>();\n const skills = groups.map((g) => profileAuditedSkill(g, findings, unreadableSkillMdPaths));\n\n const skillRoots: AuditedRoot[] = config.skillsRoots.map((root) => {\n const meta = rootMeta.get(root) ?? { exists: false };\n if (!meta.exists) {\n findings.push({ severity: 'MEDIUM', rule: 'missing-skills-root', target: root, detail: 'configured skills root does not exist on disk' });\n } else if (!meta.realPath) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'unreadable-skills-root',\n target: root,\n detail: 'could not resolve skills root to a real path (broken symlink or cycle)',\n });\n }\n return {\n root,\n realPath: meta.realPath ?? resolveConfiguredRoot(root),\n exists: meta.exists,\n skillCount: skills.filter((s) => s.root === root).length,\n };\n });\n\n // --- Hygiene: duplicate names across roots, oversized skills ---\n const byName = new Map<string, AuditedSkill[]>();\n for (const skill of skills) {\n const arr = byName.get(skill.name) ?? [];\n arr.push(skill);\n byName.set(skill.name, arr);\n }\n for (const [name, group] of byName) {\n if (group.length > 1) {\n // One finding per duplicate path (not one comma-joined finding) — keeps AuditFinding.target\n // single-path everywhere, matching every other finding this module emits.\n for (const skill of group) {\n const otherPaths = group.filter((g) => g.path !== skill.path).map((g) => g.path);\n findings.push({\n severity: 'MEDIUM',\n rule: 'duplicate-skill-name',\n target: skill.path,\n detail: `name \"${name}\" is duplicated — also found at ${otherPaths.join(', ')}`,\n });\n }\n }\n }\n for (const skill of skills) {\n if (skill.estTokens > oversizedThreshold) {\n findings.push({\n severity: 'LOW',\n rule: 'oversized-skill',\n target: skill.skillMdPath,\n detail: `est. ${skill.estTokens} tokens exceeds the ${oversizedThreshold}-token threshold — consider trimming or splitting`,\n // Deliberate '' (v0.12, see docs/accepted-findings-schema.md's context table): the detail\n // embeds a token count that changes on every edit, so \"this skill may exceed the\n // threshold\" is genuinely a rule+path identity here. An accepted oversized-skill finding\n // therefore stays suppressed as the skill keeps growing — a documented tradeoff, not a bug.\n context: '',\n });\n }\n }\n\n // --- Hygiene: per-skill safety scan (never aborts the run on a single failure) ---\n // perSkillFindings/skillScanFailed feed the evolve-eligible recompute below (v0.12): eligibility\n // is judged against ACTIVE findings only, per skill, so a skill whose only findings are accepted\n // becomes eligible. The AuditFinding objects stored here are the SAME objects pushed into\n // `findings` below (not copies) — the fingerprint attached during the global partition later is\n // therefore visible through this map too.\n const perSkillFindings = new Map<string, AuditFinding[]>();\n const skillScanFailed = new Set<string>();\n for (const skill of skills) {\n try {\n const result = await scanSafety(skill.path, config.strictness);\n const skillFindings = toAuditFindings(result.findings);\n findings.push(...skillFindings);\n perSkillFindings.set(skill.path, skillFindings);\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'unreadable-skill',\n target: skill.path,\n detail: `safety scan failed: ${(err as Error).message}`,\n });\n skillScanFailed.add(skill.path);\n }\n }\n\n // --- Inventory: MCP targets ---\n const mcpTargetPaths = config.mcpTargets ?? [];\n const mcpTargets: AuditedMcpTarget[] = [];\n for (const targetPath of mcpTargetPaths) {\n const { target, findings: targetFindings } = auditMcpTarget(targetPath, notices);\n mcpTargets.push(target);\n findings.push(...targetFindings);\n }\n\n // --- Opportunities: cross-root overlap clusters (Pro) ---\n const overlapStatus = proFeatureStatus('installed-set overlap analysis');\n let overlapClusters: OverlapCluster[] = [];\n if (overlapStatus.enabled) {\n if (overlapStatus.notice) notices.push(overlapStatus.notice);\n try {\n // Excludes any SKILL.md path already known unreadable (see profileAuditedSkill) —\n // scanOverlapPaths's readFrontmatter does an unguarded readFileSync, so feeding it an\n // unreadable path would otherwise throw and abort the whole audit with no report.\n const readableSkillMdPaths = skills.map((s) => s.skillMdPath).filter((p) => !unreadableSkillMdPaths.has(p));\n const pairs = scanOverlapPaths(readableSkillMdPaths, options.overlapThreshold);\n overlapClusters = clusterOverlapPairs(pairs);\n } catch (err) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'overlap-scan-failed',\n target: 'installed-set overlap analysis',\n detail: `cross-root overlap scan failed: ${(err as Error).message}`,\n });\n notices.push(`cross-root overlap scan failed — overlap clusters unavailable this run: ${(err as Error).message}`);\n overlapClusters = [];\n }\n } else {\n notices.push(overlapStatus.notice ?? 'Pro feature (installed-set overlap analysis) locked.');\n }\n\n // --- Accepted-findings partition (v0.12) ---\n // Computed over EVERY finding collected so far (skills + MCP targets + overlap-scan-failed) —\n // fingerprints every finding, then splits into active (report.hygiene) vs accepted\n // (report.accepted). Mutates each AuditFinding in place to attach `.fingerprint` — the same\n // objects referenced by `perSkillFindings` above, so evolve-eligibility below sees it too.\n // Nothing here may influence a gate verdict (see src/findings.ts's module doc) — this is\n // classification for the audit REPORT only.\n const acceptedRecords = options.accepted ?? [];\n const acceptedByFingerprint = new Map(acceptedRecords.map((r) => [r.fingerprint, r]));\n const matchedFingerprints = new Set<string>();\n const activeFindings: AuditFinding[] = [];\n const acceptedEntries: AcceptedFindingEntry[] = [];\n\n for (const f of findings) {\n const context = f.context ?? f.detail;\n const fingerprint = fingerprintFinding({ severity: f.severity, rule: f.rule, target: f.target, context });\n f.fingerprint = fingerprint;\n const record = acceptedByFingerprint.get(fingerprint);\n if (record) {\n matchedFingerprints.add(fingerprint);\n acceptedEntries.push({ finding: f, reason: record.reason, acceptedAt: record.acceptedAt });\n } else {\n activeFindings.push(f);\n }\n }\n\n const unmatchedAcceptedCount = acceptedRecords.filter((r) => !matchedFingerprints.has(r.fingerprint)).length;\n if (unmatchedAcceptedCount > 0) {\n notices.push(\n `${unmatchedAcceptedCount} accepted finding(s) did not match anything this run (resolved, or content changed) — see \\`skill-forge finding list --stale\\``\n );\n }\n\n const activeFindingSet = new Set(activeFindings);\n\n // --- Opportunities: evolve-eligible (structurally valid + a clean ACTIVE-findings verdict) ---\n // v0.12 semantic change: judged against ACTIVE findings only (not scanSafety's raw verdict), so a\n // skill whose only findings are accepted becomes eligible. A scan failure (skillScanFailed) is\n // never eligible regardless of strictness — that guard predates and is unrelated to acceptance.\n const evolveEligible: EvolveCandidate[] = skills\n .filter((s) => {\n if (!s.validFrontmatter || skillScanFailed.has(s.path)) return false;\n const skillFindings = perSkillFindings.get(s.path) ?? [];\n const activeSkillFindings = skillFindings.filter((f) => activeFindingSet.has(f));\n return activeVerdictFor(activeSkillFindings, config.strictness) === 'pass';\n })\n .map((s) => ({ name: s.name, path: s.path }));\n\n // --- Opportunities: foundation skill ---\n const foundation: FoundationOpportunity = config.foundationSkillPath\n ? {\n available: false,\n existingPath: config.foundationSkillPath,\n message: `a business-foundation skill already exists at ${config.foundationSkillPath}`,\n }\n : {\n available: true,\n message: 'no business-foundation skill yet — `skill-forge audit --foundation` can scaffold one to ground future skills in your business profile',\n };\n\n // --- Summary ---\n // Active-only (v0.12) — an accepted finding is excluded from these counts (and therefore from\n // routine --fail-on and the add/scan advisory), but never disappears: it's always in `accepted`.\n const findingCounts: Record<Severity, number> = { LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0 };\n for (const f of activeFindings) findingCounts[f.severity]++;\n\n const summary: AuditSummary = {\n rootCount: skillRoots.length,\n skillCount: skills.length,\n mcpTargetCount: mcpTargets.length,\n findingCounts,\n overlapClusterCount: overlapClusters.length,\n evolveEligibleCount: evolveEligible.length,\n acceptedCount: acceptedEntries.length,\n };\n\n // --- Tooling readiness (skill_doctor.py port) ---\n const tooling = checkTooling(options.which ?? defaultWhich, options.env ?? process.env);\n\n return {\n schemaVersion: 1,\n generatedAt: now().toISOString(),\n inventory: { skillRoots, skills, mcpTargets },\n hygiene: activeFindings,\n accepted: acceptedEntries,\n opportunities: { overlapClusters, evolveEligible, foundation, overlapLocked: !overlapStatus.enabled },\n tooling,\n notices,\n summary,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Markdown report rendering\n// ---------------------------------------------------------------------------\n\nfunction bulletList(items: string[], emptyPlaceholder: string): string[] {\n return items.length > 0 ? items.map((i) => `- ${i}`) : [`- ${emptyPlaceholder}`];\n}\n\n/**\n * Strips C0 (0x00-0x1F) and C1 (0x7F-0x9F) control characters from an untrusted string before\n * it's interpolated into markdown report output — these are single-line interpolations, so\n * nothing needs to replace what's stripped. Guards against e.g. an installed skill's name or an\n * MCP server's name/command/package-spec carrying an embedded ANSI escape sequence that would\n * otherwise render raw into a terminal later `cat`ing the report. JSON output (`--json`) is\n * untouched — `JSON.stringify` already escapes control characters. Exported for reuse by every\n * command that renders untrusted inventory/ledger/API strings into human-readable output\n * (organize, find, watch, ingest).\n */\nexport function sanitizeForMarkdown(s: string): string {\n // eslint-disable-next-line no-control-regex -- intentional: stripping C0/C1 control chars\n return s.replace(/[\\x00-\\x1F\\x7F-\\x9F]/g, '');\n}\n\n/** Comma-joins the defined/truthy parts of a list of optional strings — shared by the skills and MCP-targets sections' inline detail summaries. */\nfunction joinDefined(parts: (string | undefined)[]): string {\n return parts.filter((v): v is string => Boolean(v)).join(', ');\n}\n\nfunction renderRootsSection(roots: AuditedRoot[]): string[] {\n return [\n '### Skill roots',\n '',\n ...bulletList(\n roots.map((r) => `${r.root}${r.exists ? '' : ' (MISSING)'} — ${r.skillCount} skill${r.skillCount === 1 ? '' : 's'}`),\n '(none configured)'\n ),\n ];\n}\n\nfunction renderSkillsSection(skills: AuditedSkill[]): string[] {\n return [\n '### Skills',\n '',\n ...bulletList(\n skills.map((s) => {\n const flags = joinDefined([\n s.validFrontmatter ? undefined : 'INVALID FRONTMATTER',\n s.aliasPaths.length > 0 ? `${s.aliasPaths.length} alias path(s)` : undefined,\n ]);\n return `${sanitizeForMarkdown(s.name)} — ${s.path} (est. ${s.estTokens} tokens)${flags ? ` [${flags}]` : ''}`;\n }),\n '(none found)'\n ),\n ];\n}\n\nfunction renderMcpTargetsSection(targets: AuditedMcpTarget[]): string[] {\n const lines = ['### MCP targets', ''];\n if (targets.length === 0) {\n lines.push('- (none configured)');\n return lines;\n }\n for (const t of targets) {\n lines.push(`- ${t.path} — ${t.format}, ${t.status}`);\n for (const server of t.servers) {\n const detail = joinDefined([\n server.commandBasename ? sanitizeForMarkdown(server.commandBasename) : undefined,\n server.packageSpec ? sanitizeForMarkdown(server.packageSpec) : undefined,\n `${server.argCount} arg${server.argCount === 1 ? '' : 's'}`,\n `${server.envCount} env var${server.envCount === 1 ? '' : 's'}`,\n ]);\n lines.push(` - ${sanitizeForMarkdown(server.name)} (${detail})`);\n }\n }\n return lines;\n}\n\n/** First `FINGERPRINT_DISPLAY_LENGTH` hex chars of a finding's fingerprint, or `'unknown'` for a hand-built `AuditFinding` from before v0.12 (fingerprint is optional). */\nfunction displayFingerprint(f: AuditFinding): string {\n return f.fingerprint ? f.fingerprint.slice(0, FINGERPRINT_DISPLAY_LENGTH) : 'unknown';\n}\n\n/** ACTIVE findings only (v0.12) — each suffixed with its fingerprint-12 so it can be passed to `skill-forge finding accept <fp-prefix>`. */\nfunction renderFindingsSection(findings: AuditFinding[]): string[] {\n return [\n '## Hygiene findings',\n '',\n ...bulletList(\n findings.map(\n (f) => `[${f.severity}] ${f.rule} — ${sanitizeForMarkdown(f.target)}: ${sanitizeForMarkdown(f.detail)} (fp ${displayFingerprint(f)})`\n ),\n '(none — clean)'\n ),\n ];\n}\n\n/** Permanent record of every finding a human has accepted (v0.12) — reason + date, never dropped from the report even once it stops matching a live finding (see the unmatched-ack notice for that case). */\nfunction renderAcceptedFindingsSection(entries: AcceptedFindingEntry[]): string[] {\n return [\n '## Accepted findings',\n '',\n ...bulletList(\n entries.map(\n (e) =>\n `[${e.finding.severity}] ${e.finding.rule} — ${sanitizeForMarkdown(e.finding.target)}: ${sanitizeForMarkdown(e.reason)} (accepted ${sanitizeForMarkdown(e.acceptedAt.slice(0, 10))})`\n ),\n '(none)'\n ),\n ];\n}\n\nfunction renderOpportunitiesSection(report: AuditReport): string[] {\n const { overlapClusters, evolveEligible, foundation } = report.opportunities;\n return [\n '## Opportunities',\n '',\n '### Overlap clusters',\n '',\n ...bulletList(\n overlapClusters.map((c) => `top score ${c.topScore} — ${c.suggestedVerb} — ${c.members.join(', ')}`),\n '(none — either no cross-root overlap found, or the Pro feature is locked, see Notices)'\n ),\n '',\n '### Evolve-eligible',\n '',\n ...bulletList(\n evolveEligible.map((c) => `${sanitizeForMarkdown(c.name)} — ${c.path}`),\n '(none)'\n ),\n '',\n '### Foundation skill',\n '',\n `- ${foundation.message}`,\n ];\n}\n\nfunction renderToolingSection(tooling: AuditTooling | undefined): string[] {\n const lines = ['## Tooling', ''];\n if (!tooling) {\n lines.push('- (not available in this report)');\n return lines;\n }\n\n lines.push(`- SkillSpector CLI: ${tooling.skillspector.installed ? 'installed' : 'NOT installed'}`);\n if (tooling.skillspector.hint) lines.push(` - ${tooling.skillspector.hint}`);\n\n lines.push(`- skills.sh auth (VERCEL_OIDC_TOKEN): ${tooling.skillsShToken.present ? 'present' : 'missing'}`);\n if (tooling.skillsShToken.hint) lines.push(` - ${tooling.skillsShToken.hint}`);\n\n const llm = tooling.skillspectorLlm;\n if (llm.configured) {\n lines.push(`- SkillSpector LLM stage (optional): configured (provider: ${sanitizeForMarkdown(llm.provider ?? '')})`);\n } else if (llm.provider) {\n lines.push(`- SkillSpector LLM stage (optional): not configured — SKILLSPECTOR_PROVIDER is \"${sanitizeForMarkdown(llm.provider)}\" but its API key env var is not set`);\n } else {\n lines.push(\n '- SkillSpector LLM stage (optional): not set — static scan works without it (use --no-llm); ' +\n 'set SKILLSPECTOR_PROVIDER + its API key for semantic analysis'\n );\n }\n\n return lines;\n}\n\n/** Renders an `AuditReport` as a standalone markdown document — the file `skill-forge audit` writes under `~/.skill-forge/reports/`. */\nexport function renderAuditMarkdown(report: AuditReport): string {\n const { summary } = report;\n const lines: string[] = [\n `# skill-forge audit — ${report.generatedAt}`,\n '',\n '## Summary',\n '',\n `- Skill roots: ${summary.rootCount}`,\n `- Skills: ${summary.skillCount}`,\n `- MCP targets: ${summary.mcpTargetCount}`,\n `- Findings: ${summary.findingCounts.CRITICAL} CRITICAL, ${summary.findingCounts.HIGH} HIGH, ${summary.findingCounts.MEDIUM} MEDIUM, ${summary.findingCounts.LOW} LOW`,\n `- Accepted: ${summary.acceptedCount} (suppressed from the counts above; see the Accepted findings section)`,\n `- Overlap clusters: ${summary.overlapClusterCount}`,\n `- Evolve-eligible skills: ${summary.evolveEligibleCount}`,\n '',\n '## Inventory',\n '',\n ...renderRootsSection(report.inventory.skillRoots),\n '',\n ...renderSkillsSection(report.inventory.skills),\n '',\n ...renderMcpTargetsSection(report.inventory.mcpTargets),\n '',\n ...renderFindingsSection(report.hygiene),\n '',\n ...renderAcceptedFindingsSection(report.accepted),\n '',\n ...renderOpportunitiesSection(report),\n '',\n ...renderToolingSection(report.tooling),\n '',\n '## Notices',\n '',\n ...bulletList(report.notices, '(none)'),\n ];\n return lines.join('\\n') + '\\n';\n}\n\n// ---------------------------------------------------------------------------\n// Foundation skill scaffold — the one write path in this module\n// ---------------------------------------------------------------------------\n\nfunction renderFoundationSkillMd(profile: BusinessProfile): string {\n const description = profile.businessName\n ? `Business-foundation context for ${profile.businessName} — grounds new and existing skills in its audiences, workflows, and constraints. Generated by skill-forge audit.`\n : 'Business-foundation context for this business — grounds new and existing skills in its audiences, workflows, and constraints. Generated by skill-forge audit.';\n\n const lines: (string | undefined)[] = [\n '---',\n 'name: business-foundation',\n `description: ${description}`,\n '---',\n '',\n `# Business Foundation${profile.businessName ? `: ${profile.businessName}` : ''}`,\n '',\n profile.industry ? `**Industry:** ${profile.industry}` : undefined,\n '',\n '## Audiences',\n '',\n ...bulletList(profile.audiences, '(none captured yet)'),\n '',\n '## Workflows',\n '',\n ...bulletList(profile.workflows, '(none captured yet)'),\n '',\n '## Constraints',\n '',\n ...bulletList(profile.constraints, '(none captured yet)'),\n '',\n '## Integration guidance',\n '',\n 'New and existing skills in this set should align with the business context above:',\n '- Match the audiences, terminology, and tone described here.',\n '- Respect the workflows and constraints listed above when proposing changes.',\n '- When curating or evolving skills (`skill-forge evolve`, `skill-forge add`), treat this file as ground ' +\n 'truth for what the business actually needs — update it by re-running `skill-forge audit` rather than ' +\n 'editing it out of band, so it stays a single source of truth.',\n ];\n\n return lines.filter((l): l is string => l !== undefined).join('\\n') + '\\n';\n}\n\n/**\n * Scaffolds `<targetRoot>/business-foundation/SKILL.md` from a captured business profile.\n * `targetRoot` must be one of `config.skillsRoots` (containment check) — this is the one write\n * path in this module, gated entirely behind the caller's explicit opt-in (see\n * src/commands/audit.ts's `--foundation` flag / interactive confirm). Refuses an existing\n * dir/file or a symlinked destination; writes atomically (builds in a sibling temp dir, then\n * renames the whole dir into place, so a failure never leaves a partial `business-foundation/`\n * visible); runs `scanSafety` on the generated content before the rename and refuses to place it\n * on a block verdict. Returns the canonical path — the caller is responsible for persisting it to\n * `config.foundationSkillPath`. Deliberately async (unlike the plan's originally sketched sync\n * signature) because `scanSafety`, which this function must run before placement, is itself\n * async — there is no synchronous way to honor \"scan before write\" here.\n */\nexport async function scaffoldFoundationSkill(config: Config, profile: BusinessProfile, targetRoot: string): Promise<string> {\n const resolvedRoot = resolveConfiguredRoot(targetRoot);\n const configuredRoots = config.skillsRoots.map(resolveConfiguredRoot);\n if (!configuredRoots.includes(resolvedRoot)) {\n throw new UserError(`refusing to scaffold a foundation skill under ${resolvedRoot}: not one of the configured skillsRoots`);\n }\n if (!isDir(resolvedRoot)) {\n throw new UserError(`skills root does not exist: ${resolvedRoot}`);\n }\n\n const destDir = join(resolvedRoot, 'business-foundation');\n\n let destLstat;\n try {\n destLstat = lstatSync(destDir);\n } catch {\n destLstat = undefined;\n }\n if (destLstat?.isSymbolicLink()) {\n throw new UserError(`refusing to scaffold a foundation skill at ${destDir}: destination is a symlink`);\n }\n if (destLstat) {\n throw new UserError(`refusing to scaffold a foundation skill at ${destDir}: already exists`);\n }\n\n const content = renderFoundationSkillMd(profile);\n const tempDir = mkdtempSync(join(resolvedRoot, '.business-foundation-'));\n try {\n writeFileSync(join(tempDir, 'SKILL.md'), content, { flag: 'wx', mode: 0o600 });\n\n const safety = await scanSafety(tempDir, config.strictness);\n if (safety.verdict === 'block') {\n throw new UserError(\n `refusing to scaffold a foundation skill: the generated SKILL.md was flagged by the safety scan (${safety.findings\n .map(formatSafetyFinding)\n .join('; ')})`\n );\n }\n\n renameSync(tempDir, destDir);\n } catch (err) {\n rmSync(tempDir, { recursive: true, force: true });\n throw err;\n }\n\n // The skill is already on disk at this point (renameSync above succeeded) — a provenance\n // failure here must not surface as a raw, unattributed error, and must not silently leave the\n // caller believing nothing happened (mirrors the v0.7 pattern in src/evolve.ts's adopt path).\n try {\n appendProvenance(\n {\n name: 'business-foundation',\n source: 'skill-forge audit (business profile)',\n verb: 'DEFER',\n target: destDir,\n took: 'scaffolded a new business-foundation skill from the captured business profile',\n notes: 'scaffolded by skill-forge audit',\n },\n resolvedRoot\n );\n } catch (err) {\n throw new UserError(\n `the business-foundation skill was created at ${destDir}, but recording the provenance entry failed: ${(err as Error).message}`\n );\n }\n\n return destDir;\n}\n","import { statSync } from 'node:fs';\nimport { execFile } from 'node:child_process';\nimport { delimiter, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { DEFAULT_STRICTNESS, type Strictness } from '../config.js';\nimport { readTextFile, verdictFor, walk } from './scanShared.js';\nimport type { SafetyFinding, SafetyResult, Severity } from './scanShared.js';\n\nexport type { SafetyFinding, SafetyResult, Severity, Verdict } from './scanShared.js';\n\nconst execFileAsync = promisify(execFile);\n\ninterface DenyRule {\n rule: string;\n severity: Severity;\n pattern: RegExp;\n message: string;\n /**\n * Optional second-stage judgment on a line the pattern already matched. A regex can say \"this\n * line contains `rm -rf`\"; only argument analysis can say whether that is `rm -rf dist` or\n * `rm -rf /`. Return `null` to suppress the finding entirely, or an overriding severity/message.\n *\n * Exists because pattern-only rules have poor precision on real content, and a noisy HIGH costs\n * more than it earns — people stop reading the list, including the one line that mattered.\n * `destructive-rm-rf` is the first user; add others as the same problem shows up.\n */\n refine?: (line: string) => { severity: Severity; message: string } | null;\n}\n\n/**\n * Target of an `rm -rf`, if one can be parsed off the line: the first non-flag argument, quoted\n * or bare. `[^\\s;|&)\"'`]+` stops at shell metacharacters so `rm -rf dist && echo ok` yields `dist`.\n */\nconst RM_RF_TARGET_RE = /\\brm\\s+(?:-[a-z]+\\s+)+(\"[^\"]*\"|'[^']*'|[^\\s;|&)\"'`]+)/i;\n\n/**\n * Judges an `rm -rf` by its target. Measured against a real installed skill set (2026-08-09),\n * every match was `rm -rf dist` or `rm -rf .next` in a package.json script — a 100% false-positive\n * rate for a HIGH finding.\n *\n * Suppressed: a relative path with no traversal and no shell expansion — the normal build-artifact\n * clean step. Still HIGH: root/home targets, bare globs, `..` traversal, and any `$` expansion\n * (an unset variable turns `rm -rf $DIR/` into something far worse). MEDIUM: a deep absolute path,\n * which is outside any project dir and worth a look without being alarming.\n */\nfunction refineRmRf(line: string): { severity: Severity; message: string } | null {\n const match = line.match(RM_RF_TARGET_RE);\n if (!match) {\n return { severity: 'HIGH', message: 'recursive force-delete (rm -rf) with an unparseable target — verify the path is scoped' };\n }\n const target = match[1].replace(/^[\"']|[\"']$/g, '').trim();\n\n if (target.includes('$') || target.includes('`')) {\n return {\n severity: 'HIGH',\n message: `recursive force-delete of \"${target}\" — the target is a shell expansion; if it is empty or unset this can delete far more than intended`,\n };\n }\n if (target === '/' || target === '/*' || target === '*' || target === '.' || target === '..' || target === '~' || target === '~/') {\n return { severity: 'HIGH', message: `recursive force-delete of \"${target}\" — this targets the filesystem root, the home directory, or everything in the working directory` };\n }\n if (target.startsWith('~')) {\n return { severity: 'HIGH', message: `recursive force-delete of \"${target}\" — targets a path inside the home directory, outside any project` };\n }\n if (target.split('/').includes('..')) {\n return { severity: 'HIGH', message: `recursive force-delete of \"${target}\" — the target escapes the current directory via \"..\"` };\n }\n if (target.startsWith('/')) {\n const segments = target.split('/').filter(Boolean);\n if (segments.length < 3) {\n return { severity: 'HIGH', message: `recursive force-delete of \"${target}\" — a shallow absolute path like this is a system directory` };\n }\n return { severity: 'MEDIUM', message: `recursive force-delete of \"${target}\" — an absolute path outside any project directory` };\n }\n // Relative, no traversal, no expansion: `rm -rf dist` / `rm -rf .next` / `rm -rf build/cache`.\n return null;\n}\n\nconst DENY_RULES: DenyRule[] = [\n {\n rule: 'curl-pipe-shell',\n severity: 'CRITICAL',\n pattern: /\\b(curl|wget)\\b[^\\n|]*\\|\\s*(sudo\\s+)?(bash|sh|zsh)\\b/i,\n message: 'downloads a remote script and pipes it directly into a shell',\n },\n {\n rule: 'base64-obfuscated-exec',\n severity: 'CRITICAL',\n pattern:\n /(base64\\s+(-d|--decode)\\b[^\\n|]*\\|\\s*(bash|sh|zsh|python3?)\\b)|(\\bexec\\s*\\(\\s*(base64\\.)?b64decode\\()|(\\beval\\s*\\(\\s*Buffer\\.from\\([^)]*['\"]base64['\"])/i,\n message: 'decodes a base64 payload and executes it (obfuscated execution)',\n },\n {\n rule: 'reverse-shell',\n severity: 'CRITICAL',\n pattern: /(\\/dev\\/tcp\\/\\S+\\/\\d+)|(\\bnc\\s+-e\\s+\\/bin\\/(ba)?sh\\b)|(bash\\s+-i\\s*>&\\s*\\/dev\\/tcp)/i,\n message: 'opens a reverse shell back to a remote host',\n },\n {\n rule: 'destructive-rm-rf',\n severity: 'HIGH',\n pattern: /\\brm\\s+-[a-z]*r[a-z]*f[a-z]*\\b|\\brm\\s+-[a-z]*f[a-z]*r[a-z]*\\b/i,\n message: 'recursive force-delete (rm -rf) — verify the target path is scoped',\n refine: refineRmRf,\n },\n {\n rule: 'credential-file-access',\n severity: 'HIGH',\n pattern:\n /(~|\\$HOME)?\\/\\.(ssh\\/id_(rsa|ed25519|ecdsa)|aws\\/credentials|npmrc|netrc|gnupg|docker\\/config\\.json)\\b|\\bid_rsa\\b|\\.env\\b.*\\bcat\\b|\\bcat\\b.*\\.env\\b/i,\n message: 'reads a credential/secrets file (SSH keys, cloud credentials, .env)',\n },\n {\n rule: 'credential-exfil',\n severity: 'HIGH',\n pattern: /\\b(curl|wget|fetch)\\b[^\\n]*(--data|--data-binary|-F|-d)\\b[^\\n]*(\\.ssh|\\.aws|\\.env|credentials|id_rsa)/i,\n message: 'uploads credential/secret file contents to a remote endpoint',\n },\n {\n rule: 'dynamic-eval',\n severity: 'MEDIUM',\n pattern: /\\beval\\s*\\(|\\bexec\\s*\\(\\s*compile\\s*\\(|\\bnew\\s+Function\\s*\\(/,\n message: 'evaluates dynamically constructed code',\n },\n {\n rule: 'shell-true-subprocess',\n severity: 'MEDIUM',\n pattern: /subprocess\\.(Popen|run|call)\\([^)]*shell\\s*=\\s*True/,\n message: 'runs a subprocess with shell=True (shell-injection risk)',\n },\n {\n rule: 'persistence-cron-rc',\n severity: 'MEDIUM',\n pattern: />>\\s*~?\\/?\\.(bashrc|zshrc|bash_profile|profile)\\b|\\bcrontab\\s+-/i,\n message: 'modifies shell startup files or crontab (persistence)',\n },\n {\n rule: 'sudo-usage',\n severity: 'LOW',\n pattern: /\\bsudo\\s+/,\n message: 'invokes sudo — review for unnecessary privilege escalation',\n },\n];\n\nfunction scanStaticRules(targetPath: string): SafetyFinding[] {\n const findings: SafetyFinding[] = [];\n let root;\n try {\n root = statSync(targetPath);\n } catch {\n return findings;\n }\n const files = root.isDirectory() ? Array.from(walk(targetPath)) : [targetPath];\n\n for (const file of files) {\n const content = readTextFile(file);\n if (content === undefined) continue;\n const lines = content.split('\\n');\n for (const rule of DENY_RULES) {\n for (let i = 0; i < lines.length; i++) {\n if (rule.pattern.test(lines[i])) {\n const refined = rule.refine ? rule.refine(lines[i]) : { severity: rule.severity, message: rule.message };\n if (!refined) continue; // second stage judged this match benign\n findings.push({\n severity: refined.severity,\n rule: rule.rule,\n file,\n detail: `line ${i + 1}: ${refined.message}`,\n context: lines[i].trim(),\n });\n }\n }\n }\n }\n return findings;\n}\n\n/** Locate an executable on PATH without shelling out (no `which`/`command -v` dependency). */\nfunction which(cmd: string): string | undefined {\n const pathEnv = process.env.PATH ?? '';\n const dirs = pathEnv.split(delimiter).filter(Boolean);\n const exts = process.platform === 'win32' ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT').split(';') : [''];\n for (const dir of dirs) {\n for (const ext of exts) {\n const candidate = join(dir, cmd + ext);\n try {\n if (statSync(candidate).isFile()) return candidate;\n } catch {\n // not found in this dir, keep looking\n }\n }\n }\n return undefined;\n}\n\ninterface SkillSpectorFinding {\n severity?: string;\n rule_id?: string;\n message?: string;\n}\n\ninterface SkillSpectorOutput {\n filtered_findings?: SkillSpectorFinding[];\n findings?: SkillSpectorFinding[];\n}\n\nfunction normalizeSeverity(sev: string | undefined): Severity {\n const s = (sev ?? '').toUpperCase();\n if (s === 'LOW' || s === 'MEDIUM' || s === 'HIGH' || s === 'CRITICAL') return s;\n return 'MEDIUM'; // unrecognized external severity: treat cautiously, don't silently trust as safe\n}\n\n/**\n * Mirrors skill_safety.py's `skillspector scan <target> --format json --no-llm`\n * invocation. Skipped cleanly (no findings, no error) when SkillSpector isn't\n * installed or its output can't be parsed — the built-in ruleset above is the\n * FREE-tier baseline; SkillSpector is additive when present.\n */\nasync function scanWithSkillSpector(targetPath: string): Promise<SafetyFinding[]> {\n const bin = which('skillspector');\n if (!bin) return [];\n\n try {\n const { stdout } = await execFileAsync(bin, ['scan', targetPath, '--format', 'json', '--no-llm'], {\n timeout: 60_000,\n maxBuffer: 10 * 1024 * 1024,\n });\n const parsed = JSON.parse(stdout) as SkillSpectorOutput;\n const raw = parsed.filtered_findings ?? parsed.findings ?? [];\n return raw.map((f) => ({\n severity: normalizeSeverity(f.severity),\n rule: f.rule_id ?? 'skillspector',\n file: targetPath,\n detail: f.message ?? '',\n }));\n } catch {\n return [];\n }\n}\n\n/**\n * Scans a quarantined skill (file or directory) for unsafe patterns: the\n * built-in deny-pattern ruleset always runs, and an installed SkillSpector\n * (https://github.com/NVIDIA/SkillSpector) is shelled out to and merged in\n * when present on PATH. `strictness` (default `DEFAULT_STRICTNESS` from\n * `config.ts`) selects which severities produce a `block` verdict —\n * see `BLOCK_SEVERITIES_BY_STRICTNESS` in `./scanShared.ts`.\n */\nexport async function scanSafety(targetPath: string, strictness: Strictness = DEFAULT_STRICTNESS): Promise<SafetyResult> {\n const findings = [...scanStaticRules(targetPath), ...(await scanWithSkillSpector(targetPath))];\n return { findings, verdict: verdictFor(findings, strictness) };\n}\n","import { statSync } from 'node:fs';\nimport { basename, extname } from 'node:path';\nimport { DEFAULT_STRICTNESS, type Strictness } from '../config.js';\nimport { scanSafety } from './safety.js';\nimport {\n collectNpxPackageSpecs,\n isNpxCommand,\n isPinnedPackageSpec,\n packageSpecBase,\n readTextFile,\n verdictFor,\n walk,\n looksHighEntropy,\n looksStructuredNonSecret,\n} from './scanShared.js';\nimport type { SafetyFinding, SafetyResult } from './scanShared.js';\n\n/**\n * MCP-specific findings, composed on top of the base deny-ruleset scan\n * (`scanSafety`). curl|bash is deliberately NOT re-implemented here — the\n * base scan's `curl-pipe-shell` rule already covers it; `mcpScan` merges its\n * findings in.\n *\n * Detects:\n * - mcp-inline-credential-value (HIGH): a *_KEY/*_TOKEN/*_SECRET-shaped env\n * var (or any high-entropy value) assigned a real-looking value instead of\n * an empty string / ${VAR} placeholder, in an MCP server's `env` block or\n * a config/example text file. Values that are structurally non-secret — ISO\n * timestamps, credential-free URLs, filesystem paths — are exempt; see\n * `looksStructuredNonSecret` in gate/scanShared.ts for why.\n * - mcp-credential-file-path (LOW): a credential-SHAPED key whose value is a\n * filesystem path rather than an inline secret (e.g.\n * GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json). That is the documented,\n * correct way to configure those variables, so it is informational: the value\n * leaks nothing, but the file it points at is worth protecting.\n * - mcp-unpinned-npx (MEDIUM with -y, LOW without — v0.12): an unpinned npx\n * launch spec (structured `{command,args}` or shell text). `npx -y <pkg>` /\n * `npx --yes <pkg>` without a pinned `@<version>` is MEDIUM (silent\n * install); `npx <pkg>` without `-y` and without a pin is LOW — npx\n * prompts before the FIRST install, but once cached the tag still floats\n * to whatever the registry currently points at, so it's still worth\n * pinning. A pinned spec is never a finding either way.\n * - mcp-dangerous-flag (HIGH): `--dangerously-*` or `--no-sandbox` launch args.\n * - mcp-filesystem-root-arg (MEDIUM): a launch arg that is exactly \"/\" or \"~\".\n */\n\nfunction isConfigOrExampleFile(file: string): boolean {\n const base = basename(file).toLowerCase();\n if (base.startsWith('.env')) return true;\n if (/\\.(json|ya?ml)$/.test(base)) return true;\n return /(example|sample|config)/.test(base);\n}\n\nconst CREDENTIAL_KEY_RE = /(API[_-]?KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL)/i;\nfunction looksCredentialKey(name: string): boolean {\n return CREDENTIAL_KEY_RE.test(name);\n}\n\nconst PLACEHOLDER_VALUE_RE =\n /^(\\$\\{.*\\}|\\$[A-Z0-9_]+|your[_-]?.*|<.*>|xxx+|changeme|replace[_-]?me|example|placeholder|todo|fill[_-]?me|none|null|redacted|\\.\\.\\.)$/i;\nfunction isPlaceholderValue(value: string): boolean {\n const v = value.trim();\n if (v === '') return true;\n if (v.startsWith('process.env')) return true;\n return PLACEHOLDER_VALUE_RE.test(v);\n}\n\n/** Length/charset heuristic for \"looks like a real secret\" — no whitespace, mixed charset, low repetition. */\n\ninterface ServerLikeNode {\n command?: unknown;\n args?: unknown;\n env?: unknown;\n}\n\nfunction collectServerNodes(node: unknown, out: ServerLikeNode[]): void {\n if (Array.isArray(node)) {\n for (const item of node) collectServerNodes(item, out);\n return;\n }\n if (node && typeof node === 'object') {\n const obj = node as Record<string, unknown>;\n if (typeof obj.command === 'string' || Array.isArray(obj.args) || (obj.env && typeof obj.env === 'object')) {\n out.push(obj);\n }\n for (const value of Object.values(obj)) collectServerNodes(value, out);\n }\n}\n\nfunction tryParseJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/** Structural scan of a parsed MCP-config-shaped JSON document (mcpServers/servers entries, nested or not). */\nfunction scanJsonConfig(file: string, parsed: unknown): SafetyFinding[] {\n const findings: SafetyFinding[] = [];\n const nodes: ServerLikeNode[] = [];\n collectServerNodes(parsed, nodes);\n\n for (const node of nodes) {\n const command = typeof node.command === 'string' ? node.command : undefined;\n const args = Array.isArray(node.args) ? node.args.filter((a): a is string => typeof a === 'string') : [];\n\n if (command && isNpxCommand(command)) {\n // v0.12: -y/--yes presence no longer gates whether the rule runs at all — it now only\n // selects severity/message (see the module doc comment). collectNpxPackageSpecs() parses\n // the way npx itself does, including every repeatable -p/--package value, not just the\n // first non-flag token (see its own doc comment in scanShared.ts), and now runs\n // unconditionally.\n const hasYes = args.includes('-y') || args.includes('--yes');\n const { packageFlagSpecs, positionalSpec } = collectNpxPackageSpecs(args);\n const allSpecs = positionalSpec ? [...packageFlagSpecs, positionalSpec] : packageFlagSpecs;\n for (const pkgSpec of allSpecs) {\n if (isPinnedPackageSpec(pkgSpec)) continue;\n if (hasYes) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'mcp-unpinned-npx',\n file,\n detail: `npx -y ${pkgSpec} is unpinned — pin an exact version (${packageSpecBase(pkgSpec)}@<version>) so the resolved server code can't change silently`,\n context: pkgSpec,\n });\n } else {\n findings.push({\n severity: 'LOW',\n rule: 'mcp-unpinned-npx',\n file,\n detail: `npx ${pkgSpec} is unpinned — without -y npx prompts before first install, but once cached the tag still floats to whatever the registry points at; pin ${packageSpecBase(pkgSpec)}@<version>`,\n context: pkgSpec,\n });\n }\n }\n }\n\n for (const a of args) {\n if (/^--dangerously-/i.test(a) || a === '--no-sandbox') {\n findings.push({\n severity: 'HIGH',\n rule: 'mcp-dangerous-flag',\n file,\n detail: `launch arg ${a} disables a safety guard`,\n context: a,\n });\n }\n if (a === '/' || a === '~') {\n findings.push({\n severity: 'MEDIUM',\n rule: 'mcp-filesystem-root-arg',\n file,\n detail: `launch arg \"${a}\" grants filesystem access at the root/home directory — scope this to a specific directory`,\n context: a,\n });\n }\n }\n\n if (node.env && typeof node.env === 'object' && !Array.isArray(node.env)) {\n for (const [key, value] of Object.entries(node.env as Record<string, unknown>)) {\n if (typeof value !== 'string') continue;\n const kind = classifyCredentialAssignment(key, value);\n if (kind === 'inline-secret') {\n findings.push({\n severity: 'HIGH',\n rule: 'mcp-inline-credential-value',\n file,\n detail: `env.${key} has an inline credential value — emit an empty string and document that the user must fill it in`,\n context: `env.${key}`,\n });\n } else if (kind === 'file-path') {\n findings.push({\n severity: 'LOW',\n rule: 'mcp-credential-file-path',\n file,\n detail: `env.${key} points at a credential file rather than an inline secret — the value leaks nothing, but keep that file out of version control`,\n context: `env.${key}`,\n });\n }\n }\n }\n }\n\n return findings;\n}\n\n// `(?:\\.cmd|\\.exe)?` and the `i` flag mirror isNpxCommand's basename\n// normalization so the text path honors the same \"recognized by basename\"\n// guarantee as the JSON path. v0.12: the `-y`/`--yes` group is now OPTIONAL\n// (group 1, presence selects severity) rather than mandatory — previously\n// its mandatory presence shielded the pattern from ever matching a flag as\n// the \"spec\"; making it optional means the spec group's first character MUST\n// be `@` or alphanumeric, so a `-`-leading token (`--version`, `--help`,\n// `-p`) can never be mistaken for a package spec. Documented limitation: a\n// line like `npx -p foo@1.2.3 bar` is still parsed imperfectly (line-regex,\n// not argv-grade) — the structural path (`scanJsonConfig`) is authoritative\n// for real configs; see docs/gate-policy.md.\nconst NPX_SHELL_RE = /\\bnpx(?:\\.cmd|\\.exe)?\\s+(?:(-y|--yes)\\s+)?((?:@[\\w.-]+\\/)?[A-Za-z0-9][\\w.-]*)(@[\\w.-]+)?/gi;\nconst DANGEROUS_FLAG_RE = /--dangerously-[\\w-]+|--no-sandbox\\b/g;\n/** Quoted assignments: `NAME=\"value\"` / `NAME: 'value'`. */\nconst CREDENTIAL_ASSIGNMENT_RE = /[\"']?([A-Za-z][A-Za-z0-9_]*)[\"']?\\s*[:=]\\s*[\"']([^\"']{6,})[\"']/g;\n/**\n * Unquoted assignments: `NAME=value` / `NAME: value` — no quotes around the\n * value — including the common prefixed forms `export NAME=value` (shell-sourced\n * .env) and `- NAME=value` (YAML list-style env, e.g. docker-compose).\n */\nconst CREDENTIAL_ASSIGNMENT_UNQUOTED_RE = /^\\s*(?:export\\s+|-\\s+)?([A-Za-z][A-Za-z0-9_]*)\\s*[:=]\\s*(\\S+)/;\n/** A value starting with a quote is the quoted regex's job — guards against double-reporting one line. */\nconst LEADING_QUOTE_RE = /^[\"']/;\n\n/**\n * Classifies one `key = value` pair. Split out so the structured (`env` block) and text-line\n * paths cannot drift — before this, only one of them knew about the path case.\n */\nfunction classifyCredentialAssignment(key: string, value: string): 'inline-secret' | 'file-path' | 'none' {\n if (isPlaceholderValue(value)) return 'none';\n const credentialKey = looksCredentialKey(key);\n // A credential-named var pointing at a FILE is correct usage, not a leak.\n if (credentialKey && looksStructuredNonSecret(value) && !/^[a-z]+:\\/\\//i.test(value.trim())) return 'file-path';\n if (credentialKey || looksHighEntropy(value)) return 'inline-secret';\n return 'none';\n}\n\nfunction pushCredentialFinding(\n findings: SafetyFinding[],\n file: string,\n lineNo: number,\n varName: string,\n value: string,\n): void {\n const kind = classifyCredentialAssignment(varName, value);\n if (kind === 'inline-secret') {\n findings.push({\n severity: 'HIGH',\n rule: 'mcp-inline-credential-value',\n file,\n detail: `line ${lineNo}: inline credential value assigned to ${varName}`,\n context: varName,\n });\n } else if (kind === 'file-path') {\n findings.push({\n severity: 'LOW',\n rule: 'mcp-credential-file-path',\n file,\n detail: `line ${lineNo}: ${varName} points at a credential file rather than an inline secret — the value leaks nothing, but keep that file out of version control`,\n context: varName,\n });\n }\n}\n\n/** Fallback line-based scan for non-JSON (or unparseable) text files: shell examples, README, .env. */\nfunction scanTextFile(file: string, content: string, allowCredentialCheck: boolean): SafetyFinding[] {\n const findings: SafetyFinding[] = [];\n const lines = content.split('\\n');\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const lineNo = i + 1;\n\n NPX_SHELL_RE.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = NPX_SHELL_RE.exec(line)) !== null) {\n const hasYes = Boolean(m[1]);\n const pkgSpec = m[2];\n const versionSuffix = m[3] ?? '';\n const fullSpec = pkgSpec + versionSuffix;\n if (!isPinnedPackageSpec(fullSpec)) {\n if (hasYes) {\n findings.push({\n severity: 'MEDIUM',\n rule: 'mcp-unpinned-npx',\n file,\n detail: `line ${lineNo}: npx -y ${pkgSpec} is unpinned — pin an exact version (${pkgSpec}@<version>)`,\n context: fullSpec,\n });\n } else {\n findings.push({\n severity: 'LOW',\n rule: 'mcp-unpinned-npx',\n file,\n detail: `line ${lineNo}: npx ${pkgSpec} is unpinned — without -y npx prompts before first install, but once cached the tag still floats to whatever the registry points at; pin ${pkgSpec}@<version>`,\n context: fullSpec,\n });\n }\n }\n }\n\n DANGEROUS_FLAG_RE.lastIndex = 0;\n while ((m = DANGEROUS_FLAG_RE.exec(line)) !== null) {\n findings.push({\n severity: 'HIGH',\n rule: 'mcp-dangerous-flag',\n file,\n detail: `line ${lineNo}: launch arg ${m[0]} disables a safety guard`,\n });\n }\n\n if (allowCredentialCheck) {\n CREDENTIAL_ASSIGNMENT_RE.lastIndex = 0;\n while ((m = CREDENTIAL_ASSIGNMENT_RE.exec(line)) !== null) {\n pushCredentialFinding(findings, file, lineNo, m[1], m[2]);\n }\n\n const unquoted = CREDENTIAL_ASSIGNMENT_UNQUOTED_RE.exec(line);\n if (unquoted && !LEADING_QUOTE_RE.test(unquoted[2])) {\n pushCredentialFinding(findings, file, lineNo, unquoted[1], unquoted[2]);\n }\n }\n }\n\n return findings;\n}\n\nfunction scanFileForMcpFindings(file: string): SafetyFinding[] {\n const content = readTextFile(file);\n if (content === undefined) return [];\n\n if (extname(file).toLowerCase() === '.json') {\n const parsed = tryParseJson(content);\n if (parsed !== undefined) return scanJsonConfig(file, parsed);\n // malformed JSON — fall through to the text scan below\n }\n\n return scanTextFile(file, content, isConfigOrExampleFile(file));\n}\n\n/** Walks `targetPath` and returns MCP-specific findings only (not merged with the base ruleset). */\nexport function scanMcpFindings(targetPath: string): SafetyFinding[] {\n let root;\n try {\n root = statSync(targetPath);\n } catch {\n return [];\n }\n const files = root.isDirectory() ? Array.from(walk(targetPath)) : [targetPath];\n\n const findings: SafetyFinding[] = [];\n for (const file of files) {\n findings.push(...scanFileForMcpFindings(file));\n }\n return findings;\n}\n\n/**\n * MCP gate safety scan: the base deny-ruleset (`scanSafety`, includes curl|bash,\n * credential-file-access, etc.) plus the MCP-specific findings above, merged\n * into a single verdict via the same strictness rules as `scanSafety`.\n */\nexport async function mcpScan(targetPath: string, strictness: Strictness = DEFAULT_STRICTNESS): Promise<SafetyResult> {\n const base = await scanSafety(targetPath, strictness);\n const mcpFindings = scanMcpFindings(targetPath);\n const findings = [...base.findings, ...mcpFindings];\n return { findings, verdict: verdictFor(findings, strictness) };\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { existsSync, readFileSync, readdirSync, type Dirent } from 'node:fs';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { expandHome, isDir, stripQuotes } from '../util.js';\n\n/**\n * Overlap gate — ranks a candidate skill against an existing skill set.\n *\n * Heuristic, dependency-free. Blends Jaccard token overlap with keyword containment of the\n * candidate's distinctive phrases.\n */\n\nconst STOPWORDS = new Set(\n `\na an the and or of to for in on with use used using when whether this that these those is are be\nyour you it its as at by from into via per also any all not no than then so if up out via about\nskill skills user claude code based comprehensive enhanced practical including include includes\n`\n .split(/\\s+/)\n .filter(Boolean),\n);\n\nconst DEFAULT_SET_THRESHOLD = 0.45;\n\nexport interface SkillMeta {\n name: string;\n description: string;\n}\n\nexport interface SkillOverlapScore {\n skill: string;\n path: string;\n score: number;\n}\n\nexport interface OverlapResult {\n candidate: string;\n nearest: string;\n nearestScore: number;\n suggestedVerb: string;\n ranking: SkillOverlapScore[];\n}\n\nexport interface OverlapPair {\n a: string;\n b: string;\n score: number;\n}\n\n/** `OverlapPair` plus the absolute SKILL.md paths behind `a`/`b` — paths are the stable identity across roots (names can collide). */\nexport interface OverlapPathPair extends OverlapPair {\n aPath: string;\n bPath: string;\n}\n\n/** Mirrors Python's `str.split(sep, maxSplit)` — returns up to maxSplit+1 parts. */\nfunction splitLimited(text: string, sep: string, maxSplit: number): string[] {\n const parts: string[] = [];\n let rest = text;\n for (let i = 0; i < maxSplit; i++) {\n const idx = rest.indexOf(sep);\n if (idx === -1) break;\n parts.push(rest.slice(0, idx));\n rest = rest.slice(idx + sep.length);\n }\n parts.push(rest);\n return parts;\n}\n\n/** Reads a SKILL.md's `name`/`description` frontmatter, falling back to heading + body lead-in. */\nexport function readFrontmatter(skillMdPath: string): SkillMeta {\n const text = readFileSync(skillMdPath, 'utf-8');\n const meta: SkillMeta = { name: basename(dirname(skillMdPath)), description: '' };\n\n if (text.startsWith('---\\n')) {\n const block = splitLimited(text, '---', 2)[1] ?? '';\n\n const nameMatch = block.match(/^name:\\s*(.+)$/m);\n if (nameMatch) {\n meta.name = stripQuotes(nameMatch[1].trim());\n }\n\n const descMatch = block.match(/^description:\\s*(.*)$/m);\n if (descMatch) {\n const first = descMatch[1].trim();\n if (['>', '>-', '|', '|-', ''].includes(first)) {\n const lines = block.split(/\\r\\n|\\r|\\n/);\n const idx = lines.findIndex((l) => /^description:/.test(l));\n const buf: string[] = [];\n if (idx !== -1) {\n for (const l of lines.slice(idx + 1)) {\n if (/^\\s+\\S/.test(l)) {\n buf.push(l.trim());\n } else if (l.trim() === '') {\n continue;\n } else {\n break;\n }\n }\n }\n meta.description = buf.join(' ');\n } else {\n meta.description = stripQuotes(first);\n }\n }\n } else {\n const heads = Array.from(text.matchAll(/^#\\s+(.+)$/gm)).map((m) => m[1]);\n meta.description = `${heads[0] ?? ''} ${text.slice(0, 400)}`;\n }\n\n return meta;\n}\n\nfunction tokenize(text: string): Set<string> {\n const matches = text.toLowerCase().match(/[a-z][a-z0-9+.-]{2,}/g) ?? [];\n return new Set(matches.filter((t) => !STOPWORDS.has(t)));\n}\n\n/**\n * Distinctive quoted trigger phrases and salient words from a description.\n * Ported for parity with the source script; not used by score() (which extracts phrases inline).\n */\nexport function keyphrases(text: string): Set<string> {\n const quoted = new Set<string>();\n for (const m of text.matchAll(/\"([^\"]{2,40})\"/g)) quoted.add(m[1].toLowerCase().trim());\n for (const m of text.matchAll(/'([^']{2,40})'/g)) quoted.add(m[1].toLowerCase().trim());\n return quoted.size > 0 ? quoted : tokenize(text);\n}\n\nfunction intersectSize(a: Set<string>, b: Set<string>): number {\n let n = 0;\n for (const x of a) if (b.has(x)) n += 1;\n return n;\n}\n\nfunction unionSize(a: Set<string>, b: Set<string>): number {\n const u = new Set(a);\n for (const x of b) u.add(x);\n return u.size;\n}\n\nfunction isSubset(sub: Set<string>, sup: Set<string>): boolean {\n for (const x of sub) if (!sup.has(x)) return false;\n return true;\n}\n\nfunction round3(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n\nfunction score(cand: SkillMeta, other: SkillMeta): number {\n const a = tokenize(`${cand.name} ${cand.description}`);\n const b = tokenize(`${other.name} ${other.description}`);\n if (a.size === 0) return 0.0;\n\n const inter = intersectSize(a, b);\n const union = unionSize(a, b);\n const jaccard = union ? inter / union : 0.0;\n const containment = inter / a.size;\n\n const phrases = new Set<string>();\n for (const m of cand.description.matchAll(/\"([^\"]{2,40})\"/g)) phrases.add(m[1]);\n for (const m of cand.description.matchAll(/'([^']{2,40})'/g)) phrases.add(m[1]);\n\n let pscore = 0.0;\n if (phrases.size > 0) {\n let hits = 0;\n for (const p of phrases) {\n const pw = tokenize(p);\n if (pw.size > 0 && isSubset(pw, b)) hits += 1;\n }\n pscore = hits / phrases.size;\n }\n\n return round3(0.3 * jaccard + 0.5 * containment + 0.2 * pscore);\n}\n\n/** Score tiers behind suggestVerb/suggestVerbCode — one table so prose and bare verb can't drift. */\nconst VERB_TIERS = [\n { min: 0.45, verb: 'ABSORB', prose: 'ABSORB (or REJECT if ours is already better)' },\n { min: 0.2, verb: 'FORK', prose: 'FORK (re-skin), or ABSORB one piece' },\n { min: -Infinity, verb: 'DEFER', prose: 'DEFER / new FORK (little overlap)' },\n] as const;\n\nfunction verbTier(topScore: number): (typeof VERB_TIERS)[number] {\n return VERB_TIERS.find((t) => topScore >= t.min) ?? VERB_TIERS[VERB_TIERS.length - 1];\n}\n\nexport function suggestVerb(topScore: number): string {\n return verbTier(topScore).prose;\n}\n\n/** Bare verb of the tier suggestVerb would pick — for callers that store an enum, not prose (e.g. the pending queue). */\nexport function suggestVerbCode(topScore: number): (typeof VERB_TIERS)[number]['verb'] {\n return verbTier(topScore).verb;\n}\n\n/** The typed union `suggestVerbCode` returns — shared by callers (e.g. `audit.ts`'s `OverlapCluster.suggestedVerb`) that want the bare enum's type without re-deriving it. */\nexport type SuggestedVerbCode = ReturnType<typeof suggestVerbCode>;\n\nfunction listSubdirs(dir: string): string[] {\n if (!existsSync(dir)) return [];\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n return entries.filter((e) => e.isDirectory()).map((e) => join(dir, e.name));\n}\n\n/**\n * Finds SKILL.md files matching the same three glob shapes as the Python script:\n * \"<dir>/SKILL.md\", \"skills/<dir>/SKILL.md\", \"<dir>/skills/<dir>/SKILL.md\" (one wildcard\n * <dir> level each) — deduped, .git excluded, sorted.\n */\nfunction findSkillMdFiles(root: string): string[] {\n const found = new Set<string>();\n\n for (const dir of listSubdirs(root)) {\n const candidate = join(dir, 'SKILL.md');\n if (existsSync(candidate)) found.add(resolve(candidate));\n }\n\n for (const dir of listSubdirs(join(root, 'skills'))) {\n const candidate = join(dir, 'SKILL.md');\n if (existsSync(candidate)) found.add(resolve(candidate));\n }\n\n for (const topDir of listSubdirs(root)) {\n for (const dir of listSubdirs(join(topDir, 'skills'))) {\n const candidate = join(dir, 'SKILL.md');\n if (existsSync(candidate)) found.add(resolve(candidate));\n }\n }\n\n return Array.from(found)\n .filter((p) => !p.includes('/.git/'))\n .sort();\n}\n\n/**\n * Ranks a single candidate skill against every skill found under `skillsRoot`.\n * Throws if `skillsRoot` isn't a directory, the candidate's SKILL.md can't be found,\n * or no existing skills are found under the root (mirrors the Python script's `fail()` exits).\n */\nexport function scanOverlap(candidatePath: string, skillsRoot: string): OverlapResult {\n const root = resolve(expandHome(skillsRoot));\n if (!isDir(root)) {\n throw new Error(`skills-root not a directory: ${root}`);\n }\n\n let cpath = resolve(expandHome(candidatePath));\n if (isDir(cpath)) {\n cpath = join(cpath, 'SKILL.md');\n }\n if (!existsSync(cpath)) {\n throw new Error(`candidate SKILL.md not found: ${cpath}`);\n }\n\n const cand = readFrontmatter(cpath);\n\n const existing = findSkillMdFiles(root).filter((p) => p !== resolve(cpath));\n if (existing.length === 0) {\n throw new Error(`no existing skills found under ${root}`);\n }\n\n const ranking: SkillOverlapScore[] = existing.map((p) => {\n const other = readFrontmatter(p);\n return { skill: other.name, path: p, score: score(cand, other) };\n });\n ranking.sort((a, b) => b.score - a.score);\n\n const top = ranking[0];\n return {\n candidate: cand.name,\n nearest: top.skill,\n nearestScore: top.score,\n suggestedVerb: suggestVerb(top.score),\n ranking,\n };\n}\n\n/**\n * N-way: pairwise overlap across an arbitrary set of SKILL.md paths; reports pairs scoring >=\n * threshold. Symmetric — the pair score is max(score(a,b), score(b,a)) since score() is\n * candidate-centric. Paths (not names) are the stable identity: names can collide across roots,\n * which is exactly why `skill-forge audit`'s cross-root clustering (src/audit.ts) needs the path\n * alongside each name rather than `scanOverlapSet`'s name-only `OverlapPair`.\n */\nexport function scanOverlapPaths(\n skillMdPaths: readonly string[],\n threshold: number = DEFAULT_SET_THRESHOLD,\n): OverlapPathPair[] {\n const metas = skillMdPaths.map(readFrontmatter);\n\n const pairs: OverlapPathPair[] = [];\n for (let i = 0; i < metas.length; i++) {\n for (let j = i + 1; j < metas.length; j++) {\n const sc = Math.max(score(metas[i], metas[j]), score(metas[j], metas[i]));\n if (sc >= threshold) {\n pairs.push({\n a: metas[i].name,\n b: metas[j].name,\n aPath: skillMdPaths[i],\n bPath: skillMdPaths[j],\n score: round3(sc),\n });\n }\n }\n }\n pairs.sort((a, b) => b.score - a.score);\n return pairs;\n}\n\n/**\n * N-way: pairwise overlap across the whole installed set; reports pairs scoring >= threshold.\n * Delegates to `scanOverlapPaths` (path-based identity) and strips paths back off, so its public\n * behavior — exact scoring, ordering, and the name-only `OverlapPair` shape — is unchanged.\n */\nexport function scanOverlapSet(\n skillsRoot: string,\n threshold: number = DEFAULT_SET_THRESHOLD,\n): OverlapPair[] {\n const root = resolve(expandHome(skillsRoot));\n if (!isDir(root)) {\n throw new Error(`skills-root not a directory: ${root}`);\n }\n\n const paths = findSkillMdFiles(root);\n return scanOverlapPaths(paths, threshold).map(({ a, b, score: s }) => ({ a, b, score: s }));\n}\n","import { createHash } from 'node:crypto';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { AuditFinding } from './audit.js';\nimport { getHomeDir } from './config.js';\nimport { UserError } from './errors.js';\nimport type { Severity } from './gate/scanShared.js';\nimport { MAX_PREFERENCE_NOTE_LENGTH, validateProvenanceText } from './preferences.js';\nimport { writeJsonAtomic } from './util.js';\n\n/**\n * Accepted audit findings (v0.12) — a per-machine store at `~/.skill-forge/accepted-findings.json`\n * (honors `SKILL_FORGE_HOME`) holding human-reviewed acknowledgements of `skill-forge audit`\n * findings. See `docs/accepted-findings-schema.md` for the full identity contract, the per-rule\n * `context` table, and the fail-open semantics summarized below.\n *\n * ## Identity: the fingerprint\n *\n * Each record is keyed by `fingerprint = sha256(severity \\0 rule \\0 target \\0 context)` —\n * {@link fingerprintFinding}. `context` is the offending line's text (or a rule-specific stable\n * token; see `docs/accepted-findings-schema.md`'s per-rule table) — the finding's LINE NUMBER is\n * deliberately excluded (it drifts on unrelated edits above/below), and SEVERITY is deliberately\n * included (so re-tuning a rule to a higher severity — e.g. the §9 `mcp-unpinned-npx` widening —\n * can never inherit an ack minted against the lower one). Any change to the offending content\n * produces a new fingerprint, so an edited or new-but-matching-pattern line is NOT suppressed by an\n * old ack — this is the deliberate \"fail open to re-reporting\" design, not a bug: the alternative\n * (keying on rule+path only) is the classic baseline trap, where accepting one benign line silently\n * suppresses every future line the same rule matches in that file.\n *\n * ## Hard boundary: advisory only, never a gate input\n *\n * Nothing in this store may influence a **gate verdict** (`add`/`scan`/`promote <id>`/`evolve`\n * re-gate). The gate judges a *candidate*; this store describes decisions about the *installed\n * set*. `runGate`/`gatePipeline.ts` never read this module — only `src/audit.ts` (the audit\n * report's active/accepted partition) and its consumers (`commands/audit.ts`, `commands/routine.ts`,\n * `commands/finding.ts`) do. This mirrors `src/auditState.ts`'s own advisory-only invariant.\n *\n * ## Fail-open contract\n *\n * {@link readAcceptedFindings} NEVER throws: a missing store, corrupt JSON, an unrecognized\n * `schemaVersion`, or an individual malformed record all degrade toward MORE reporting, never\n * less — every failure mode returns `{ records: [], notices: [...] }` or drops just the bad\n * record(s), and always says so via a notice. This is consulted from inside `audit`'s report-build\n * path (and, downstream, `add`/`scan`'s advisory), so a corrupt store must never break a run.\n */\n\nexport interface AcceptedFinding {\n /** 64 lowercase hex chars — sha256 of the identity components. See {@link fingerprintFinding}. */\n fingerprint: string;\n /** Snapshot at accept time — also a hash input (see the module doc's identity section). */\n severity: Severity;\n rule: string;\n target: string;\n /** '' is allowed only for the specific rules `docs/accepted-findings-schema.md`'s table permits. */\n context: string;\n /** Display snapshot of the finding's detail text at accept time — NOT a hash input. */\n detail: string;\n /** Mandatory human-supplied note on why this finding is accepted. 1-200 chars, no C0/C1 controls. */\n reason: string;\n /** ISO-8601 timestamp of when this record was written. */\n acceptedAt: string;\n /** Enum-of-one today — the human-door invariant, machine-checkable. See `docs/gate-policy.md`. */\n source: 'user';\n}\n\nexport interface AcceptedFindingsStore {\n schemaVersion: 1;\n records: AcceptedFinding[];\n}\n\n/** Rendered/matched fingerprint display length (first N hex chars) — `docs/accepted-findings-schema.md` §1.1. */\nexport const FINGERPRINT_DISPLAY_LENGTH = 12;\n\n/** Minimum prefix length `finding accept`/`finding revoke` will attempt to resolve — shorter is refused as too likely to collide. */\nexport const MIN_FINGERPRINT_PREFIX_LENGTH = 8;\n\nconst FINGERPRINT_HEX_RE = /^[0-9a-f]{64}$/;\nconst VALID_SEVERITIES: readonly Severity[] = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];\n// eslint-disable-next-line no-control-regex -- intentional: rejecting C0/C1 control characters\nconst CONTROL_CHAR_RE = /[\\u0000-\\u001F\\u007F-\\u009F]/;\n\nfunction isValidSeverity(value: unknown): value is Severity {\n return typeof value === 'string' && (VALID_SEVERITIES as readonly string[]).includes(value);\n}\n\n/** sha256 hex over the four identity components, NUL-separated. See the module doc's identity section for why line numbers are excluded and severity is included. */\nexport function fingerprintFinding(f: { severity: Severity; rule: string; target: string; context: string }): string {\n return createHash('sha256').update(`${f.severity}\\0${f.rule}\\0${f.target}\\0${f.context}`).digest('hex');\n}\n\nexport function getAcceptedFindingsPath(): string {\n return join(getHomeDir(), 'accepted-findings.json');\n}\n\n/**\n * Per-record repair check, mirroring `preferences.ts`'s `normalizePreferences` philosophy: a\n * record that fails ANY of these checks is dropped rather than trusted, because every one of\n * these fields is later rendered into a terminal/markdown report (control characters would smuggle\n * escape sequences) or used as an equality/matching key (a malformed fingerprint or severity must\n * never silently participate in matching). Returns a short reason string, or `undefined` when the\n * record is valid.\n */\nfunction invalidRecordReason(record: unknown): string | undefined {\n if (typeof record !== 'object' || record === null) return 'not an object';\n const r = record as Record<string, unknown>;\n if (typeof r.fingerprint !== 'string' || !FINGERPRINT_HEX_RE.test(r.fingerprint)) return 'invalid fingerprint';\n if (!isValidSeverity(r.severity)) return 'invalid severity';\n if (typeof r.rule !== 'string' || r.rule.length === 0 || CONTROL_CHAR_RE.test(r.rule)) return 'invalid rule';\n if (typeof r.target !== 'string' || r.target.length === 0 || CONTROL_CHAR_RE.test(r.target)) return 'invalid target';\n if (typeof r.context !== 'string' || CONTROL_CHAR_RE.test(r.context)) return 'invalid context';\n if (typeof r.detail !== 'string' || CONTROL_CHAR_RE.test(r.detail)) return 'invalid detail';\n if (\n typeof r.reason !== 'string' ||\n r.reason.length === 0 ||\n r.reason.length > MAX_PREFERENCE_NOTE_LENGTH ||\n CONTROL_CHAR_RE.test(r.reason)\n ) {\n return 'invalid reason';\n }\n if (typeof r.acceptedAt !== 'string' || r.acceptedAt.length === 0) return 'invalid acceptedAt';\n if (r.source !== 'user') return 'invalid source';\n return undefined;\n}\n\n/**\n * Reads the accepted-findings store. NEVER throws — see the module doc's fail-open contract.\n * Every failure mode (missing file, corrupt JSON, unrecognized envelope shape/schemaVersion, or an\n * individual malformed record) returns fewer or zero records, plus a notice explaining why —\n * failure always moves toward MORE reporting, never less.\n */\nexport function readAcceptedFindings(): { records: AcceptedFinding[]; notices: string[] } {\n const path = getAcceptedFindingsPath();\n if (!existsSync(path)) return { records: [], notices: [] };\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(path, 'utf-8'));\n } catch {\n return { records: [], notices: ['accepted-findings store unreadable (corrupt JSON) — nothing suppressed this run'] };\n }\n\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n (parsed as { schemaVersion?: unknown }).schemaVersion !== 1 ||\n !Array.isArray((parsed as { records?: unknown }).records)\n ) {\n return { records: [], notices: ['accepted-findings store unreadable (unrecognized shape or schema version) — nothing suppressed this run'] };\n }\n\n const rawRecords = (parsed as { records: unknown[] }).records;\n const records: AcceptedFinding[] = [];\n let dropped = 0;\n for (const raw of rawRecords) {\n if (invalidRecordReason(raw) === undefined) {\n records.push(raw as AcceptedFinding);\n } else {\n dropped++;\n }\n }\n\n const notices: string[] = [];\n if (dropped > 0) {\n notices.push(\n `${dropped} accepted-finding record(s) in the store failed validation and were dropped — whatever they described is reported as active again`\n );\n }\n\n return { records, notices };\n}\n\n/** Overwrites the store. Atomic + owner-only via the shared writer in util.ts. */\nexport function writeAcceptedFindings(records: AcceptedFinding[]): void {\n const store: AcceptedFindingsStore = { schemaVersion: 1, records };\n writeJsonAtomic(getAcceptedFindingsPath(), store);\n}\n\n/**\n * Validates `finding accept`'s mandatory `--reason`. Reuses `preferences.ts`'s\n * control-char/length validator (`validateProvenanceText`, capped at `MAX_PREFERENCE_NOTE_LENGTH`)\n * rather than a second copy — the field renders at the same kind of human-review moment\n * (`finding list`, the report's Accepted findings section) that `origin`/`note` do.\n */\nexport function validateAcceptReason(reason: string): void {\n if (reason.length === 0) {\n throw new UserError('--reason is required — a short note on why this finding is accepted (e.g. \"funnel genuinely needs root\")');\n }\n validateProvenanceText('reason', reason);\n}\n\n/** Every active `AuditFinding` (must carry a `fingerprint`) whose fingerprint starts with `prefix` (case-insensitive). */\nexport function resolveAmongFindings(prefix: string, findings: readonly AuditFinding[]): AuditFinding[] {\n const lower = prefix.toLowerCase();\n return findings.filter((f) => f.fingerprint !== undefined && f.fingerprint.startsWith(lower));\n}\n\n/** Every stored `AcceptedFinding` whose fingerprint starts with `prefix` (case-insensitive). */\nexport function resolveAmongRecords(prefix: string, records: readonly AcceptedFinding[]): AcceptedFinding[] {\n const lower = prefix.toLowerCase();\n return records.filter((r) => r.fingerprint.startsWith(lower));\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { verify as cryptoVerify } from 'node:crypto';\nimport { loadConfig } from './config.js';\n\n/**\n * Offline license-key verification for skill-forge Pro. No network calls —\n * the CLI must gate Pro features without phoning home.\n *\n * Key format: `skf1.<base64url payload>.<base64url signature>`\n * - `skf1` is a fixed prefix identifying the key scheme/version.\n * - payload is base64url-encoded JSON: `{ name, email, seats, exp }`, where\n * `exp` is an ISO date string (`YYYY-MM-DD`) the key is valid through.\n * - signature is an Ed25519 signature (via node:crypto) over the raw\n * decoded payload bytes, verified against the embedded PUBLIC_KEY_PEM.\n *\n * Keys are minted offline with `scripts/keygen.mjs sign` using the matching\n * private key, which is never committed to this repo (see keys/, gitignored).\n *\n * Beta pricing (0.x): the commercial license still governs Pro modules, but\n * the runtime gate SLEEPS while BETA_FREE is true — every Pro feature runs\n * for everyone, with a one-line notice unless a valid key is present. Flip\n * BETA_FREE to false at 1.0 to re-arm the lock; nothing else needs to change.\n * `proFeatureStatus()` is the shape callers should gate on: it folds\n * \"do we have a valid key\" and \"does the beta make that not matter\" into a\n * single `{ enabled, notice? }` result. `hasProLicense()` / `upgradeNotice()`\n * remain as-is for existing call sites until they're migrated to it.\n */\n\nconst LICENSE_PREFIX = 'skf1';\n\n/** Single source of truth for beta pricing. Flip to false at v1.0. */\nexport const BETA_FREE = true;\n\n// Ed25519 public key (SPKI/PEM), generated once via `scripts/keygen.mjs keypair`.\n// The matching private key lives outside this repo — see scripts/keygen.mjs.\nconst PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAWSVbcX9u70IsN1O5zS5z+AYtAUVz7ExT7h+1gT9x+wQ=\n-----END PUBLIC KEY-----\n`;\n\nexport interface LicensePayload {\n name: string;\n email: string;\n seats: number;\n exp: string;\n}\n\nexport type LicenseInvalidReason = 'missing' | 'malformed' | 'signature' | 'expired';\n\nexport type LicenseStatus =\n | { valid: true; payload: LicensePayload }\n | { valid: false; reason: LicenseInvalidReason };\n\n/** Reads the raw license key string. SKILL_FORGE_LICENSE (env) takes precedence over config.licenseKey. */\nexport function readLicenseKey(): string | undefined {\n const fromEnv = process.env.SKILL_FORGE_LICENSE?.trim();\n if (fromEnv) return fromEnv;\n return loadConfig().licenseKey?.trim() || undefined;\n}\n\nfunction isLicensePayload(value: unknown): value is LicensePayload {\n if (typeof value !== 'object' || value === null) return false;\n const p = value as Record<string, unknown>;\n return typeof p.name === 'string' && typeof p.email === 'string' && typeof p.seats === 'number' && typeof p.exp === 'string';\n}\n\n/**\n * Verifies a license key string. Pass no argument to check the default\n * sources (SKILL_FORGE_LICENSE env, then config.licenseKey). Verifies the\n * `skf1.<payload>.<signature>` shape, the Ed25519 signature against\n * PUBLIC_KEY_PEM, and that `exp` has not passed. Any tampering (payload or\n * signature) or expiry makes the key invalid.\n */\nexport function checkLicense(rawKey: string | undefined = readLicenseKey()): LicenseStatus {\n if (!rawKey) {\n return { valid: false, reason: 'missing' };\n }\n\n const parts = rawKey.trim().split('.');\n if (parts.length !== 3 || parts[0] !== LICENSE_PREFIX) {\n return { valid: false, reason: 'malformed' };\n }\n const [, payloadPart, signaturePart] = parts;\n\n const payloadBuffer = Buffer.from(payloadPart, 'base64url');\n const signature = Buffer.from(signaturePart, 'base64url');\n\n let payload: unknown;\n try {\n payload = JSON.parse(payloadBuffer.toString('utf-8'));\n } catch {\n return { valid: false, reason: 'malformed' };\n }\n if (!isLicensePayload(payload)) {\n return { valid: false, reason: 'malformed' };\n }\n\n // Ed25519 signs/verifies over raw bytes directly — algorithm must be null.\n let signatureValid: boolean;\n try {\n signatureValid = cryptoVerify(null, payloadBuffer, PUBLIC_KEY_PEM, signature);\n } catch {\n return { valid: false, reason: 'malformed' };\n }\n if (!signatureValid) {\n return { valid: false, reason: 'signature' };\n }\n\n const expMs = new Date(payload.exp).getTime();\n if (Number.isNaN(expMs) || expMs < Date.now()) {\n return { valid: false, reason: 'expired' };\n }\n\n return { valid: true, payload };\n}\n\n/** True if the default license sources resolve to a currently-valid license. */\nexport function hasProLicense(): boolean {\n return checkLicense().valid;\n}\n\n/** Short line replacing a locked Pro output — printed in place of the feature's normal output. */\nexport function upgradeNotice(feature: string): string {\n return `[skill-forge] Pro feature (${feature}) — set SKILL_FORGE_LICENSE or config.licenseKey to a valid license to unlock.`;\n}\n\nfunction invalidLicenseReasonText(reason: LicenseInvalidReason): string {\n switch (reason) {\n case 'malformed':\n return 'is malformed';\n case 'signature':\n return 'has an invalid signature';\n case 'expired':\n return 'has expired';\n default:\n return 'is invalid';\n }\n}\n\n/** Result shape for {@link proFeatureStatus}: whether a Pro feature should run, and an optional line to print alongside it. */\nexport interface ProFeatureStatus {\n enabled: boolean;\n notice?: string;\n}\n\n/**\n * Resolves whether a Pro feature should run right now, folding in the 0.x\n * beta-free policy. This is the shape call sites should consume going\n * forward — see the module docstring for the resolution table.\n *\n * `betaFree` defaults to {@link BETA_FREE} and is only a parameter so tests\n * can exercise the post-1.0 (locked) branch without touching the constant.\n */\nexport function proFeatureStatus(feature: string, betaFree: boolean = BETA_FREE): ProFeatureStatus {\n const status = checkLicense();\n if (status.valid) {\n return { enabled: true };\n }\n\n if (!betaFree) {\n return { enabled: false, notice: upgradeNotice(feature) };\n }\n\n const betaNotice = `Pro feature (${feature}) — free during the 0.x beta; will require a license at 1.0.`;\n if (status.reason === 'missing') {\n return { enabled: true, notice: betaNotice };\n }\n\n return {\n enabled: true,\n notice: `${betaNotice} (Note: your license key ${invalidLicenseReasonText(status.reason)} — ignored while Pro is free.)`,\n };\n}\n","import { execFileSync } from 'node:child_process';\nimport {\n closeSync,\n existsSync,\n mkdtempSync,\n openSync,\n readFileSync,\n readdirSync,\n readSync,\n rmSync,\n statSync,\n} from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { basename, dirname, extname, join, resolve } from 'node:path';\nimport { expandHome, isDir, stripQuotes } from '../util.js';\nimport { extractMcpCapabilities, type McpCapabilities } from './mcpCapabilities.js';\n\n/**\n * Stdlib-only structured profile of an external skill candidate: a skill dir, a single\n * SKILL.md, a plugin dir (.claude-plugin/plugin.json), an MCP config dir (.mcp.json /\n * mcp.json), or a .skill (zip) bundle. Fails loudly (throws) rather than guessing.\n */\n\nconst SPDX_HINTS: Record<string, string> = {\n mit: 'MIT (permissive)',\n apache: 'Apache-2.0 (permissive)',\n bsd: 'BSD (permissive)',\n isc: 'ISC (permissive)',\n unlicense: 'Unlicense (public domain)',\n cc0: 'CC0 (public domain)',\n 'mozilla public license': 'MPL (weak copyleft)',\n 'gnu general public': 'GPL (copyleft)',\n 'gnu affero': 'AGPL (copyleft)',\n 'creative commons attribution-sharealike': 'CC-BY-SA (copyleft)',\n 'creative commons attribution': 'CC-BY (attribution)',\n 'all rights reserved': 'All rights reserved (restrictive)',\n};\n\nconst RESOURCE_DIRS = ['scripts', 'references', 'reference', 'commands', 'hooks', 'assets', 'templates', 'sub-skills'];\n\nconst PYTHON_STDLIB = new Set([\n 'os', 'sys', 're', 'json', 'argparse', 'pathlib', 'tempfile', 'zipfile',\n 'subprocess', 'collections', 'datetime', 'math', 'itertools', 'typing',\n 'shutil', 'glob', 'hashlib', 'urllib', 'csv', 'io', 'functools', '__future__',\n]);\n\n/**\n * v0.5: the artifact this candidate should be gated/promoted as — 'mcp' when\n * an MCP config file or an MCP-server-shaped package.json is detected,\n * 'skill' otherwise (default). Orthogonal to `kind`, which selects which\n * profiler shape ran (skill dir / plugin / mcp config dir).\n */\nexport type ArtifactType = 'skill' | 'mcp';\n\nexport interface SkillProfile {\n kind: 'skill';\n inputPath: string;\n skillMd: string;\n name?: string;\n version?: string;\n validFrontmatter: boolean;\n description?: string;\n license: string;\n bodyLines: number;\n headerCount: number;\n topHeaders: string[];\n resources: Record<string, number>;\n mcpDependencies: string[];\n externalPythonDeps: string[];\n artifactType: ArtifactType;\n /** `metadata.rhize.extends` declarations (see gate/mapOverlap.ts's extends-aware overlap exemption). Empty when not declared. */\n extends: string[];\n}\n\nexport interface PluginProfile {\n kind: 'plugin';\n inputPath: string;\n name?: string;\n version?: string;\n description?: string;\n skills: string[];\n skillCount: number;\n commands: number;\n mcpServers: string[];\n artifactType: ArtifactType;\n}\n\nexport interface McpProfile {\n kind: 'mcp';\n inputPath: string;\n config: string;\n servers: string[];\n serverCount: number;\n artifactType: ArtifactType;\n /** v0.6: static, best-effort declared capabilities (tools/resources/prompts) — see mcpCapabilities.ts. */\n capabilities: McpCapabilities;\n}\n\nexport type Profile = SkillProfile | PluginProfile | McpProfile;\n\n/** Profiles a skill dir, SKILL.md, plugin dir, MCP config dir, or .skill archive. */\nexport function profileSkill(inputPath: string): Profile {\n const p = resolve(expandHome(inputPath));\n if (!existsSync(p)) {\n throw new Error(`path does not exist: ${p}`);\n }\n\n const stat = statSync(p);\n const kind = stat.isDirectory() ? classifyInput(p) : 'skill';\n\n if (kind === 'plugin') {\n return profilePlugin(p);\n }\n if (kind === 'mcp') {\n return profileMcp(p);\n }\n return profileSkillDir(p);\n}\n\n/**\n * Narrows an already-resolved `Profile` to a `SkillProfile`, throwing a\n * descriptive error otherwise. For a caller that has already profiled a\n * path (e.g. via `profileSkill()`) and needs the skill-specific shape to\n * proceed. `context` is appended to the error message (e.g. `at ${path}`,\n * `for the evolved proposal`). Shared by `commands/gatePipeline.ts`'s\n * `asProfile()` (its skill branch) and `evolve.ts`'s re-gate step.\n */\nexport function asSkillProfile(profile: Profile, context: string): SkillProfile {\n if (profile.kind !== 'skill') {\n throw new Error(`expected a skill candidate ${context}, found a ${profile.kind}`);\n }\n return profile;\n}\n\n/**\n * Profiles `candidatePath` and asserts the result matches `artifact`\n * ('mcp' expects kind 'mcp'; 'skill'/absent expects kind 'skill'),\n * narrowing the return type accordingly. Throws a descriptive error\n * otherwise, rather than guessing. Moved here (v0.7 simplify pass) from\n * `commands/gatePipeline.ts`, which still owns calling it as part of the\n * gate pipeline; `evolve.ts` also profiles a skill through this same\n * skill-branch logic via `asSkillProfile()` above.\n */\nexport function asProfile(candidatePath: string, artifact: ArtifactType): Profile {\n const profile = profileSkill(candidatePath);\n if (artifact === 'mcp') {\n if (profile.kind !== 'mcp') {\n throw new Error(`expected an MCP candidate at ${candidatePath} (--artifact mcp), found a ${profile.kind}`);\n }\n return profile;\n }\n return asSkillProfile(profile, `at ${candidatePath}`);\n}\n\n/** skill | plugin | mcp — by manifest/config presence (directories only). */\nfunction classifyInput(p: string): 'skill' | 'plugin' | 'mcp' {\n if (existsSync(join(p, '.claude-plugin', 'plugin.json'))) return 'plugin';\n if (detectArtifactType(p) === 'mcp') return 'mcp';\n return 'skill';\n}\n\nconst MCP_SDK_DEP = '@modelcontextprotocol/sdk';\nconst MCP_KEYWORD = 'mcp-server';\nconst MCP_BIN_PREFIX = /^mcp-/i;\n\n/** Reads package.json for detection purposes only — malformed/missing is not a signal either way. */\nfunction readPackageJsonSafe(root: string): Record<string, unknown> | undefined {\n const pkgPath = join(root, 'package.json');\n if (!existsSync(pkgPath)) return undefined;\n try {\n return JSON.parse(readFileSync(pkgPath, 'utf-8')) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n\n/** deps containing @modelcontextprotocol/sdk, keywords containing \"mcp-server\", or bin names like mcp-*. */\nfunction hasMcpPackageMarkers(pkg: Record<string, unknown>): boolean {\n for (const section of ['dependencies', 'devDependencies', 'peerDependencies'] as const) {\n const deps = pkg[section];\n if (deps && typeof deps === 'object' && MCP_SDK_DEP in (deps as Record<string, unknown>)) return true;\n }\n\n const keywords = pkg.keywords;\n if (Array.isArray(keywords) && keywords.some((k) => k === MCP_KEYWORD)) return true;\n\n const bin = pkg.bin;\n if (typeof bin === 'string') {\n if (MCP_BIN_PREFIX.test(basename(bin))) return true;\n } else if (bin && typeof bin === 'object') {\n if (Object.keys(bin as Record<string, unknown>).some((k) => MCP_BIN_PREFIX.test(k))) return true;\n }\n\n return false;\n}\n\n/** v0.5 artifact classification: an mcp config file, or an MCP-server-shaped package.json, at `root`. */\nfunction detectArtifactType(root: string): ArtifactType {\n if (existsSync(join(root, '.mcp.json')) || existsSync(join(root, 'mcp.json'))) return 'mcp';\n const pkg = readPackageJsonSafe(root);\n if (pkg && hasMcpPackageMarkers(pkg)) return 'mcp';\n return 'skill';\n}\n\ninterface ResolvedSkillMd {\n skillMd: string;\n root: string;\n tmpDir?: string;\n}\n\nfunction resolveSkillMd(inputPath: string): ResolvedSkillMd {\n let currentPath = inputPath;\n let tmpDir: string | undefined;\n\n const initialStat = statSync(currentPath);\n if (extname(currentPath) === '.skill' || (initialStat.isFile() && isZipFile(currentPath))) {\n tmpDir = mkdtempSync(join(tmpdir(), 'forge_'));\n extractZip(currentPath, tmpDir);\n currentPath = tmpDir;\n }\n\n const stat = statSync(currentPath);\n if (stat.isFile() && basename(currentPath) === 'SKILL.md') {\n return { skillMd: currentPath, root: dirname(currentPath), tmpDir };\n }\n if (stat.isDirectory()) {\n const direct = join(currentPath, 'SKILL.md');\n if (existsSync(direct)) {\n return { skillMd: direct, root: currentPath, tmpDir };\n }\n const matches = findSkillMdOneLevelDown(currentPath);\n if (matches.length === 1) {\n return { skillMd: matches[0], root: dirname(matches[0]), tmpDir };\n }\n if (matches.length > 1) {\n cleanupTmp(tmpDir);\n throw new Error(\n `multiple SKILL.md found under ${currentPath}; point at a single skill dir:\\n ${matches.join('\\n ')}`\n );\n }\n }\n cleanupTmp(tmpDir);\n throw new Error(`no SKILL.md found at ${currentPath}`);\n}\n\nfunction cleanupTmp(tmpDir?: string): void {\n if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });\n}\n\n/** Mirrors path.glob(\"<dir>/SKILL.md\") + path.glob(\"skills/<dir>/SKILL.md\") (plugin layout support). */\nfunction findSkillMdOneLevelDown(dir: string): string[] {\n const matches = new Set<string>();\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.isDirectory()) {\n const candidate = join(dir, entry.name, 'SKILL.md');\n if (existsSync(candidate)) matches.add(candidate);\n }\n }\n const skillsDir = join(dir, 'skills');\n if (isDir(skillsDir)) {\n for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {\n if (entry.isDirectory()) {\n const candidate = join(skillsDir, entry.name, 'SKILL.md');\n if (existsSync(candidate)) matches.add(candidate);\n }\n }\n }\n return Array.from(matches);\n}\n\nfunction isZipFile(p: string): boolean {\n try {\n const fd = openSync(p, 'r');\n const buf = Buffer.alloc(4);\n const bytesRead = readSync(fd, buf, 0, 4, 0);\n closeSync(fd);\n if (bytesRead < 4) return false;\n // ZIP local file header (PK\\x03\\x04), empty archive (PK\\x05\\x06), or spanned (PK\\x07\\x08)\n return buf[0] === 0x50 && buf[1] === 0x4b && (buf[2] === 0x03 || buf[2] === 0x05 || buf[2] === 0x07);\n } catch {\n return false;\n }\n}\n\nfunction extractZip(zipPath: string, destDir: string): void {\n try {\n execFileSync('unzip', ['-q', '-o', zipPath, '-d', destDir], { stdio: ['ignore', 'ignore', 'pipe'] });\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === 'ENOENT') {\n throw new Error(\n 'unsupported without unzip: .skill archives require the system `unzip` binary, which was not found on ' +\n 'PATH. Install unzip, or extract the archive yourself and point profileSkill at the resulting directory.'\n );\n }\n throw new Error(`failed to extract ${zipPath}: ${e.message ?? String(err)}`);\n }\n}\n\ninterface Frontmatter {\n validFrontmatter: boolean;\n name?: string;\n version?: string;\n license?: string;\n description?: string;\n /** `metadata.rhize.extends` — skill(s) this candidate declares itself a deliberate specialization/layering of (see gate/mapOverlap.ts). */\n extends?: string[];\n}\n\n/** Mirrors text.split(\"---\", 2)[1] — the block between the first two \"---\" delimiters. */\nfunction frontmatterBlock(text: string): string {\n const first = text.indexOf('---');\n const rest = text.slice(first + 3);\n const second = rest.indexOf('---');\n return second === -1 ? rest : rest.slice(0, second);\n}\n\nfunction extractSimpleField(block: string, key: string): string | undefined {\n const re = new RegExp(`^${key}:\\\\s*(.+)$`, 'm');\n const m = block.match(re);\n if (!m) return undefined;\n return stripQuotes(m[1].trim());\n}\n\nfunction extractDescription(block: string): string | undefined {\n const m = block.match(/^description:\\s*(.*)$/m);\n if (!m) return undefined;\n const first = m[1].trim();\n if (['>', '>-', '|', '|-', ''].includes(first)) {\n const lines = block.split('\\n');\n const idx = lines.findIndex((l) => /^description:/.test(l));\n if (idx === -1) return undefined;\n const buf: string[] = [];\n for (let i = idx + 1; i < lines.length; i++) {\n const l = lines[i];\n if (/^\\s+\\S/.test(l)) {\n buf.push(l.trim());\n } else if (l.trim() === '') {\n continue;\n } else {\n break;\n }\n }\n return buf.join(' ').trim();\n }\n return stripQuotes(first);\n}\n\n/**\n * Extracts `metadata.rhize.extends` from the frontmatter block — a flat `extends:` line\n * regardless of nesting depth, since this file's frontmatter fields are conventionally\n * unique-per-block (mirrors the `topics`/`stacks` convention already in use under\n * `metadata.rhize` across the `rhize-plugins` skills — see that repo's SKILL.md frontmatter).\n * Not a general YAML parser: supports an inline flow array (`extends: [a, \"b\"]`), a block\n * list (`extends:` followed by `- a` / `- b` lines), or a single bare scalar. Returns\n * `undefined` when the field is absent so `Frontmatter.extends` stays optional.\n */\nfunction extractExtends(block: string): string[] | undefined {\n const lines = block.split('\\n');\n const idx = lines.findIndex((l) => /^\\s*extends:\\s*(.*)$/.test(l));\n if (idx === -1) return undefined;\n const inline = lines[idx].match(/^\\s*extends:\\s*(.*)$/)![1].trim();\n\n if (inline.startsWith('[')) {\n const inner = inline.replace(/^\\[/, '').replace(/\\]\\s*$/, '');\n const items = inner\n .split(',')\n .map((s) => stripQuotes(s.trim()))\n .filter(Boolean);\n return items.length ? items : undefined;\n }\n\n if (inline === '') {\n const items: string[] = [];\n for (let i = idx + 1; i < lines.length; i++) {\n const line = lines[i];\n const m = line.match(/^\\s*-\\s*(.+)$/);\n if (m) {\n items.push(stripQuotes(m[1].trim()));\n } else if (line.trim() === '') {\n continue;\n } else {\n break;\n }\n }\n return items.length ? items : undefined;\n }\n\n return [stripQuotes(inline)];\n}\n\nfunction parseFrontmatter(text: string): Frontmatter {\n if (!text.startsWith('---\\n')) {\n return { validFrontmatter: false };\n }\n const block = frontmatterBlock(text);\n return {\n validFrontmatter: true,\n name: extractSimpleField(block, 'name'),\n version: extractSimpleField(block, 'version'),\n license: extractSimpleField(block, 'license'),\n description: extractDescription(block),\n extends: extractExtends(block),\n };\n}\n\nfunction detectLicense(root: string, fm: Frontmatter): string {\n if (fm.license) return fm.license;\n const names = ['LICENSE', 'LICENSE.md', 'LICENSE.txt', 'COPYING', 'license'];\n for (const name of names) {\n const p = join(root, name);\n if (existsSync(p) && statSync(p).isFile()) {\n const head = readFileSync(p, 'utf-8').slice(0, 2000).toLowerCase();\n for (const [hint, label] of Object.entries(SPDX_HINTS)) {\n if (head.includes(hint)) return label;\n }\n return 'present but unrecognized';\n }\n }\n return 'NONE STATED';\n}\n\nfunction walkFiles(dir: string): string[] {\n const out: string[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n out.push(...walkFiles(full));\n } else if (entry.isFile()) {\n out.push(full);\n }\n }\n return out;\n}\n\nfunction countResources(root: string): Record<string, number> {\n const resources: Record<string, number> = {};\n for (const d of RESOURCE_DIRS) {\n const dp = join(root, d);\n if (isDir(dp)) {\n resources[d] = walkFiles(dp).length;\n }\n }\n return resources;\n}\n\nfunction scanMcpAndImports(text: string, root: string): { mcpDeps: string[]; externalDeps: string[] } {\n const mcpRefRe = /mcp__[a-zA-Z0-9_]+/g;\n const mcpRefs = new Set(text.match(mcpRefRe) ?? []);\n const imports = new Set<string>();\n\n const scriptsDir = join(root, 'scripts');\n if (isDir(scriptsDir)) {\n for (const f of walkFiles(scriptsDir)) {\n if (!f.endsWith('.py')) continue;\n const ft = readFileSync(f, 'utf-8');\n for (const m of ft.match(mcpRefRe) ?? []) mcpRefs.add(m);\n const importRe = /^\\s*(?:import|from)\\s+([a-zA-Z0-9_]+)/gm;\n let m: RegExpExecArray | null;\n while ((m = importRe.exec(ft)) !== null) {\n imports.add(m[1]);\n }\n }\n }\n\n return {\n mcpDeps: Array.from(mcpRefs).sort(),\n externalDeps: Array.from(imports)\n .filter((i) => !PYTHON_STDLIB.has(i))\n .sort(),\n };\n}\n\nfunction profileSkillDir(inputPath: string): SkillProfile {\n const { skillMd, root, tmpDir } = resolveSkillMd(inputPath);\n try {\n const text = readFileSync(skillMd, 'utf-8');\n const fm = parseFrontmatter(text);\n const bodyLines = (text.match(/\\n/g)?.length ?? 0) + 1;\n const headers = Array.from(text.matchAll(/^#{1,3}\\s+(.+)$/gm)).map((m) => m[1]);\n const resources = countResources(root);\n const { mcpDeps, externalDeps } = scanMcpAndImports(text, root);\n\n return {\n kind: 'skill',\n inputPath,\n skillMd,\n name: fm.name,\n version: fm.version,\n validFrontmatter: fm.validFrontmatter,\n description: fm.description,\n license: detectLicense(root, fm),\n bodyLines,\n headerCount: headers.length,\n topHeaders: headers.slice(0, 12),\n resources,\n mcpDependencies: mcpDeps,\n externalPythonDeps: externalDeps,\n artifactType: detectArtifactType(root),\n extends: fm.extends ?? [],\n };\n } finally {\n cleanupTmp(tmpDir);\n }\n}\n\nfunction profilePlugin(p: string): PluginProfile {\n const manifestPath = join(p, '.claude-plugin', 'plugin.json');\n let manifest: Record<string, unknown>;\n try {\n manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));\n } catch (err) {\n throw new Error(`could not parse plugin.json under ${p}: ${(err as Error).message}`);\n }\n\n const skillsDir = join(p, 'skills');\n let skills: string[] = [];\n if (isDir(skillsDir)) {\n skills = readdirSync(skillsDir, { withFileTypes: true })\n .filter((d) => d.isDirectory() && existsSync(join(skillsDir, d.name, 'SKILL.md')))\n .map((d) => d.name)\n .sort();\n }\n\n const commandsDir = join(p, 'commands');\n const commands = isDir(commandsDir)\n ? readdirSync(commandsDir).filter((f) => f.endsWith('.md')).length\n : 0;\n\n const mc = (manifest.mcpServers ?? manifest.mcp_servers ?? {}) as Record<string, unknown>;\n const mcpServers = typeof mc === 'object' && mc !== null ? Object.keys(mc).sort() : [];\n\n return {\n kind: 'plugin',\n inputPath: p,\n name: manifest.name as string | undefined,\n version: manifest.version as string | undefined,\n description: manifest.description as string | undefined,\n skills,\n skillCount: skills.length,\n commands,\n mcpServers,\n artifactType: detectArtifactType(p),\n };\n}\n\nfunction profileMcp(p: string): McpProfile {\n const explicitConfig = existsSync(join(p, '.mcp.json'))\n ? join(p, '.mcp.json')\n : existsSync(join(p, 'mcp.json'))\n ? join(p, 'mcp.json')\n : undefined;\n\n if (explicitConfig) {\n let cfg: Record<string, unknown>;\n try {\n cfg = JSON.parse(readFileSync(explicitConfig, 'utf-8'));\n } catch (err) {\n throw new Error(`could not parse MCP config ${explicitConfig}: ${(err as Error).message}`);\n }\n\n const servers = (cfg.mcpServers ?? cfg.servers ?? {}) as Record<string, unknown>;\n const names = typeof servers === 'object' && servers !== null ? Object.keys(servers).sort() : [];\n\n return {\n kind: 'mcp',\n inputPath: p,\n config: explicitConfig,\n servers: names,\n serverCount: names.length,\n artifactType: 'mcp',\n capabilities: extractMcpCapabilities(p),\n };\n }\n\n // No .mcp.json/mcp.json — this is an MCP server package identified by its own\n // package.json markers (see hasMcpPackageMarkers / detectArtifactType).\n const pkgPath = join(p, 'package.json');\n let pkg: Record<string, unknown>;\n try {\n pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n } catch (err) {\n throw new Error(`could not parse package.json ${pkgPath}: ${(err as Error).message}`);\n }\n const name = typeof pkg.name === 'string' && pkg.name.length > 0 ? pkg.name : basename(p);\n\n return {\n kind: 'mcp',\n inputPath: p,\n config: pkgPath,\n servers: [name],\n serverCount: 1,\n artifactType: 'mcp',\n capabilities: extractMcpCapabilities(p),\n };\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { extname, join } from 'node:path';\nimport { readTextFile, walk } from './scanShared.js';\n\n/**\n * v0.6: static, best-effort MCP server capability profiling.\n *\n * STATIC ONLY — this module reads files as plain text and JSON. It never\n * dynamically imports, requires, spawns, or otherwise executes anything from\n * the profiled package to discover its tools/resources/prompts. Anything\n * that can't be determined from source text is left out of the result\n * rather than guessed at by running the server — see `declaredConfidence`.\n *\n * Signals parsed, in order of strength:\n * - source text of .ts/.tsx/.js/.jsx/.mjs/.cjs files (node_modules/.git/dist\n * skipped): MCP SDK registration call patterns —\n * server.tool(...)/server.registerTool(...)/server.resource(...)/server.prompt(...)\n * — plus a best-effort scan of setRequestHandler(List*RequestSchema, ...)\n * handler bodies for name/uri literals. Presence of either -> 'high'.\n * - a shipped .mcp.json / mcp.json manifest, if its declared server entries\n * carry an explicit tools/resources/prompts list (uncommon, but checked).\n * - package.json: bin names, keywords, an `@modelcontextprotocol/sdk`\n * dependency, or an `mcp` field — presence-only signal (no capability\n * names), used for 'partial' confidence when nothing more specific was\n * found in source text.\n */\n\nexport type McpTransport = 'stdio' | 'http' | 'unknown';\nexport type DeclaredConfidence = 'high' | 'partial' | 'none';\n\nexport interface McpCapabilities {\n tools: string[];\n resources: string[];\n prompts: string[];\n transport?: McpTransport;\n declaredConfidence: DeclaredConfidence;\n}\n\nconst SOURCE_EXT = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);\nconst MAX_ITEMS_PER_CATEGORY = 200;\n\n/** The three capability categories, always collected together as one set of buckets. */\ntype Category = 'tools' | 'resources' | 'prompts';\ntype Buckets = Record<Category, Set<string>>;\n\nfunction readJsonSafe(file: string): unknown {\n try {\n return JSON.parse(readFileSync(file, 'utf-8'));\n } catch {\n return undefined;\n }\n}\n\n// ---- package.json markers (presence-only signal; never yields capability names) ----\n\nconst MCP_SDK_DEP = '@modelcontextprotocol/sdk';\nconst MCP_KEYWORD = 'mcp-server';\nconst MCP_BIN_PREFIX = /^mcp-/i;\n\nfunction hasPackageJsonMcpMarkers(pkg: Record<string, unknown>): boolean {\n for (const section of ['dependencies', 'devDependencies', 'peerDependencies'] as const) {\n const deps = pkg[section];\n if (deps && typeof deps === 'object' && MCP_SDK_DEP in (deps as Record<string, unknown>)) return true;\n }\n\n const keywords = pkg.keywords;\n if (Array.isArray(keywords) && keywords.some((k) => k === MCP_KEYWORD)) return true;\n\n const bin = pkg.bin;\n if (typeof bin === 'string') {\n if (MCP_BIN_PREFIX.test(bin.split('/').pop() ?? bin)) return true;\n } else if (bin && typeof bin === 'object') {\n if (Object.keys(bin as Record<string, unknown>).some((k) => MCP_BIN_PREFIX.test(k))) return true;\n }\n\n if (pkg.mcp && typeof pkg.mcp === 'object') return true;\n\n return false;\n}\n\n// ---- .mcp.json / mcp.json manifest: declared tools/resources/prompts arrays, if present ----\n\nfunction collectDeclaredFromManifest(node: unknown, buckets: Buckets): void {\n if (Array.isArray(node)) {\n for (const item of node) collectDeclaredFromManifest(item, buckets);\n return;\n }\n if (!node || typeof node !== 'object') return;\n\n const obj = node as Record<string, unknown>;\n for (const [key, value] of Object.entries(obj)) {\n if ((key === 'tools' || key === 'resources' || key === 'prompts') && Array.isArray(value)) {\n const target = buckets[key];\n for (const item of value) {\n if (typeof item === 'string') {\n target.add(item);\n } else if (item && typeof item === 'object') {\n const name = (item as Record<string, unknown>).name ?? (item as Record<string, unknown>).uri;\n if (typeof name === 'string') target.add(name);\n }\n }\n } else {\n collectDeclaredFromManifest(value, buckets);\n }\n }\n}\n\n// ---- MCP SDK registration call patterns ----\n// Single pass over server.tool()/registerTool()/resource()/prompt(): the method name\n// (group 1) routes the first string arg (group 2) to its category — tool and registerTool\n// both feed `tools`.\n\nconst REGISTRATION_CALL_RE = /\\bserver\\.(tool|registerTool|resource|prompt)\\(\\s*['\"]([^'\"]+)['\"]/g;\nconst REGISTRATION_METHOD_CATEGORY: Record<string, Category> = {\n tool: 'tools',\n registerTool: 'tools',\n resource: 'resources',\n prompt: 'prompts',\n};\n\nfunction captureRegistrations(text: string, buckets: Buckets): void {\n REGISTRATION_CALL_RE.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = REGISTRATION_CALL_RE.exec(text)) !== null) {\n buckets[REGISTRATION_METHOD_CATEGORY[m[1]]].add(m[2]);\n }\n}\n\n// ---- best-effort setRequestHandler(List*RequestSchema, ...) body scan ----\n\nconst LIST_HANDLER_SCHEMAS: Record<string, Category> = {\n ListToolsRequestSchema: 'tools',\n ListResourcesRequestSchema: 'resources',\n ListPromptsRequestSchema: 'prompts',\n};\nconst SET_REQUEST_HANDLER_RE =\n /setRequestHandler\\(\\s*(ListToolsRequestSchema|ListResourcesRequestSchema|ListPromptsRequestSchema)\\s*,/g;\nconst NAME_OR_URI_FIELD_RE = /\\b(?:name|uri)\\s*:\\s*['\"]([^'\"]+)['\"]/g;\n\n/** Returns the text of the balanced `(...)` call starting at `openParenIndex`, or a capped slice if unbalanced. */\nfunction extractBalancedCall(text: string, openParenIndex: number): string {\n let depth = 0;\n for (let i = openParenIndex; i < text.length; i++) {\n const ch = text[i];\n if (ch === '(') depth++;\n else if (ch === ')') {\n depth--;\n if (depth === 0) return text.slice(openParenIndex, i + 1);\n }\n }\n return text.slice(openParenIndex, Math.min(text.length, openParenIndex + 20000));\n}\n\nfunction extractFromListHandlers(text: string, buckets: Buckets): void {\n SET_REQUEST_HANDLER_RE.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = SET_REQUEST_HANDLER_RE.exec(text)) !== null) {\n const openParenIdx = text.indexOf('(', m.index);\n if (openParenIdx === -1) continue;\n const body = extractBalancedCall(text, openParenIdx);\n const target = buckets[LIST_HANDLER_SCHEMAS[m[1]]];\n\n NAME_OR_URI_FIELD_RE.lastIndex = 0;\n let fieldMatch: RegExpExecArray | null;\n while ((fieldMatch = NAME_OR_URI_FIELD_RE.exec(body)) !== null) {\n target.add(fieldMatch[1]);\n }\n }\n}\n\n// ---- transport hints ----\n\nconst STDIO_TRANSPORT_RE = /StdioServerTransport/;\nconst HTTP_TRANSPORT_RE = /StreamableHTTPServerTransport|SSEServerTransport|WebSocketServerTransport/;\nconst MCP_SDK_IMPORT_RE = /@modelcontextprotocol\\/sdk/;\n\nexport function extractMcpCapabilities(packageDir: string): McpCapabilities {\n const buckets: Buckets = { tools: new Set(), resources: new Set(), prompts: new Set() };\n\n let hasPackageJsonMarker = false;\n const pkgPath = join(packageDir, 'package.json');\n if (existsSync(pkgPath)) {\n const pkg = readJsonSafe(pkgPath);\n if (pkg && typeof pkg === 'object' && !Array.isArray(pkg)) {\n hasPackageJsonMarker = hasPackageJsonMcpMarkers(pkg as Record<string, unknown>);\n }\n }\n\n for (const manifestName of ['.mcp.json', 'mcp.json']) {\n const manifestPath = join(packageDir, manifestName);\n if (!existsSync(manifestPath)) continue;\n const parsed = readJsonSafe(manifestPath);\n if (parsed !== undefined) {\n collectDeclaredFromManifest(parsed, buckets);\n }\n }\n\n let sawStdioTransport = false;\n let sawHttpTransport = false;\n let sawMcpSdkImport = false;\n\n for (const file of walk(packageDir)) {\n if (!SOURCE_EXT.has(extname(file))) continue;\n const text = readTextFile(file);\n if (text === undefined) continue;\n\n captureRegistrations(text, buckets);\n extractFromListHandlers(text, buckets);\n\n if (STDIO_TRANSPORT_RE.test(text)) sawStdioTransport = true;\n if (HTTP_TRANSPORT_RE.test(text)) sawHttpTransport = true;\n if (MCP_SDK_IMPORT_RE.test(text)) sawMcpSdkImport = true;\n }\n\n const transport: McpTransport | undefined = sawStdioTransport\n ? 'stdio'\n : sawHttpTransport\n ? 'http'\n : sawMcpSdkImport\n ? 'unknown'\n : undefined;\n\n const sortCap = (set: Set<string>): string[] => Array.from(set).sort().slice(0, MAX_ITEMS_PER_CATEGORY);\n const tools = sortCap(buckets.tools);\n const resources = sortCap(buckets.resources);\n const prompts = sortCap(buckets.prompts);\n\n const hasExplicitSignal = tools.length > 0 || resources.length > 0 || prompts.length > 0;\n const declaredConfidence: DeclaredConfidence = hasExplicitSignal\n ? 'high'\n : hasPackageJsonMarker\n ? 'partial'\n : 'none';\n\n return {\n tools,\n resources,\n prompts,\n transport,\n declaredConfidence,\n };\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { expandHome } from '../util.js';\nimport { suggestVerb, type OverlapResult, type SkillOverlapScore } from './overlap.js';\n\n/**\n * MCP overlap gate (v0.5) — ranks a candidate MCP server against the server\n * entries already present in the user's detected mcp config files, by name\n * similarity + same package/command. No canonical Python source exists for\n * this one (unlike gate/overlap.ts's skill overlap); it's new for v0.5.\n *\n * Returns the same `OverlapResult` shape gate/overlap.ts's scanOverlap()\n * returns (and reuses its score-tier suggestVerb()) so `report/render.ts`\n * renders an MCP candidate's overlap without any changes: `ranking[].skill`\n * holds the existing server's name, `ranking[].path` the config file it came\n * from.\n */\n\n/**\n * Minimal shape scanMcpOverlap needs to describe the candidate being added.\n * Deliberately decoupled from src/gate/profile.ts's McpProfile — callers\n * adapt whatever candidate profile they built (npm package.json, a local\n * dir's mcp.json, etc.) into this shape.\n */\nexport interface McpOverlapCandidate {\n /** Candidate server name (the config key it would be promoted under). */\n name: string;\n /** Launch command (e.g. \"npx\", \"node\", a binary path) — bin/entry hint. */\n command?: string;\n /** Launch args — used for command/package similarity (e.g. [\"-y\", \"@scope/pkg\"]). */\n args?: string[];\n /** npm package name, when the candidate is an npm-sourced MCP server. */\n packageName?: string;\n}\n\ninterface McpTargetServer {\n name: string;\n command?: string;\n args?: string[];\n}\n\n/** Reads one mcp config file's `mcpServers` (falling back to `servers`) map. Missing/malformed files yield no entries. */\nfunction readTargetServers(targetPath: string): McpTargetServer[] {\n const path = resolve(expandHome(targetPath));\n if (!existsSync(path)) return [];\n\n let cfg: Record<string, unknown>;\n try {\n cfg = JSON.parse(readFileSync(path, 'utf-8'));\n } catch {\n return [];\n }\n\n const servers = (cfg.mcpServers ?? cfg.servers ?? {}) as Record<string, unknown>;\n if (typeof servers !== 'object' || servers === null) return [];\n\n return Object.entries(servers).map(([name, raw]) => {\n const v = (raw ?? {}) as Record<string, unknown>;\n return {\n name,\n command: typeof v.command === 'string' ? v.command : undefined,\n args: Array.isArray(v.args) ? v.args.filter((a): a is string => typeof a === 'string') : undefined,\n };\n });\n}\n\nfunction tokenizeName(name: string): Set<string> {\n return new Set(name.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean));\n}\n\n/** Exact match = 1; else Jaccard token overlap on the name split by non-alphanumerics. */\nfunction nameSimilarity(a: string, b: string): number {\n if (a.toLowerCase() === b.toLowerCase()) return 1;\n const ta = tokenizeName(a);\n const tb = tokenizeName(b);\n if (ta.size === 0 || tb.size === 0) return 0;\n let inter = 0;\n for (const t of ta) if (tb.has(t)) inter += 1;\n const union = new Set([...ta, ...tb]).size;\n return union ? inter / union : 0;\n}\n\n/** Strips a trailing \"@version\" from a package spec without breaking a leading \"@scope/name\". */\nfunction packageBaseName(spec: string): string {\n const searchFrom = spec.startsWith('@') ? 1 : 0;\n const atIdx = spec.indexOf('@', searchFrom);\n return atIdx === -1 ? spec : spec.slice(0, atIdx);\n}\n\n/** Identifies the package/binary a launch command targets: the first non-flag arg for a package-runner command, else the command's basename (so a bare binary name matches a full path to the same binary). */\nfunction extractPackageSpec(command?: string, args: string[] = []): string | undefined {\n if (!command) return undefined;\n const base = command.split('/').pop() ?? command;\n if (['npx', 'npm', 'pnpm', 'yarn', 'uvx'].includes(base)) {\n return args.find((a) => !a.startsWith('-'));\n }\n return base;\n}\n\n/** 1 if candidate and target resolve to the same package/binary identity, else 0. */\nfunction commandSimilarity(\n candidate: McpOverlapCandidate,\n target: Pick<McpTargetServer, 'command' | 'args'>,\n): number {\n const candSpec = candidate.packageName ?? extractPackageSpec(candidate.command, candidate.args ?? []);\n const targetSpec = extractPackageSpec(target.command, target.args ?? []);\n if (!candSpec || !targetSpec) return 0;\n return packageBaseName(candSpec).toLowerCase() === packageBaseName(targetSpec).toLowerCase() ? 1 : 0;\n}\n\nfunction round3(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n\nfunction score(candidate: McpOverlapCandidate, target: McpTargetServer): number {\n return round3(0.6 * nameSimilarity(candidate.name, target.name) + 0.4 * commandSimilarity(candidate, target));\n}\n\n/**\n * Ranks `candidate` against every server entry found across `mcpTargetPaths`\n * (each file's `mcpServers` map). Unlike scanOverlap(), a target file that's\n * missing or empty isn't an error — a brand-new mcp config with zero servers\n * is the normal case for a first MCP install — so this returns an\n * empty-ranking result (`nearest: ''`, `nearestScore: 0`) instead of\n * throwing.\n */\nexport function scanMcpOverlap(candidate: McpOverlapCandidate, mcpTargetPaths: string[]): OverlapResult {\n const ranking: SkillOverlapScore[] = [];\n\n for (const targetPath of mcpTargetPaths) {\n const resolvedPath = resolve(expandHome(targetPath));\n for (const target of readTargetServers(targetPath)) {\n ranking.push({ skill: target.name, path: resolvedPath, score: score(candidate, target) });\n }\n }\n ranking.sort((a, b) => b.score - a.score);\n\n const top = ranking[0];\n return {\n candidate: candidate.name,\n nearest: top?.skill ?? '',\n nearestScore: top?.score ?? 0,\n suggestedVerb: suggestVerb(top?.score ?? 0),\n ranking,\n };\n}\n","import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { expandHome } from '../util.js';\n\n/**\n * Shared loader + types for the generated skill-map artifact produced by the\n * `rhize-plugins` repo's `scripts/build_skill_map.py` (schema:\n * `schemas/skill-map.schema.json` there — see that repo's\n * `.claude/plans/skill-map-graph-substrate.md`, Phase 4). skill-forge never\n * generates or writes this file; it only ever READS it as data.\n *\n * The map is deliberately treated as an *external, untrusted-shape* JSON\n * document here: at the time this loader was written the schema was still\n * being finalized in a parallel workstream, and skills roots are exactly the\n * kind of place a hostile or malformed file could land. Every field access\n * downstream (`skillMapDrift.ts`, `gate/mapOverlap.ts`) treats node/edge\n * fields as plain data — nothing from a loaded map is ever executed,\n * interpreted as a command, or passed to a shell. See those modules' own doc\n * comments for the specific never-execute invariant each enforces.\n */\n\nexport interface SkillMapNode {\n id: string;\n type: string;\n name?: string;\n description?: string;\n path?: string;\n contentHash?: string;\n /**\n * sha256 of a local `skill` node's file with the Rhize-injected `metadata.rhize`\n * frontmatter block textually removed (computed once, by the rhize-plugins compiler —\n * skill-forge never re-implements the stripping). Compared against the upstream\n * `external` node's `baselineHash` for the three-way drift matrix in `skillMapDrift.ts`.\n */\n contentHashNormalized?: string;\n /**\n * Upstream content hash as of the last human review (ingestion or re-baseline), recorded\n * as curated data in `rhize-plugins`' SOURCES.md and copied onto this `external` node by\n * the compiler. Never fetched or computed by skill-forge — see `skillMapDrift.ts`.\n */\n baselineHash?: string;\n /** Upstream location for `external` nodes referenced by a `fork-of` edge — a URL or a local path (fixtures/tests). Never executed. */\n url?: string;\n tags?: string[];\n [key: string]: unknown;\n}\n\nexport interface SkillMapEdge {\n from: string;\n to: string;\n type: string;\n source?: string;\n /**\n * Fork-of edges may carry drift-check metadata recorded at ingest time (mirrors\n * `provenance.ts`'s `driftCheck` field on a SOURCES.md entry). This is DATA ONLY —\n * no field of it is ever read as a command to run. skill-forge derives its own\n * drift-check behavior (contentHash compare) rather than trusting anything stored here.\n */\n driftCheck?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface SkillMap {\n schemaVersion?: string;\n nodes: SkillMapNode[];\n edges: SkillMapEdge[];\n}\n\n/** Reads and minimally validates a skill-map JSON file. Throws with a descriptive message on any failure — callers decide whether that's fatal or a skippable warning. */\nexport function loadSkillMap(path: string): SkillMap {\n const resolved = resolve(expandHome(path));\n let text: string;\n try {\n text = readFileSync(resolved, 'utf-8');\n } catch (err) {\n throw new Error(`could not read skill map at ${resolved}: ${(err as Error).message}`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (err) {\n throw new Error(`skill map at ${resolved} is not valid JSON: ${(err as Error).message}`);\n }\n\n if (typeof parsed !== 'object' || parsed === null) {\n throw new Error(`skill map at ${resolved}: expected a JSON object`);\n }\n const obj = parsed as Record<string, unknown>;\n if (!Array.isArray(obj.nodes) || !Array.isArray(obj.edges)) {\n throw new Error(`skill map at ${resolved}: expected \"nodes\" and \"edges\" arrays`);\n }\n\n // Bugfix (regression covered by test/mapOverlap.realmap.test.ts): the real generated\n // artifact (rhize-plugins' `scripts/build_skill_map.py`, and the resolved variant it\n // feeds) labels every node's kind with a `kind` field, not `type` — this loader (and by\n // extension `gate/mapOverlap.ts`'s `node.type !== 'skill'` filter) previously only ever\n // read `type`, which is absent on every real node. The gate silently matched nothing\n // against any real map. Normalize here so a real `kind`-only node is visible to every\n // downstream consumer without touching mapOverlap.ts's filter logic; a node that already\n // has both (or only `type`, e.g. hand-built test fixtures) is unaffected.\n const nodes = (obj.nodes as Array<Record<string, unknown>>).map((n) => ({\n ...n,\n type: typeof n.type === 'string' ? n.type : (n.kind as string | undefined),\n })) as SkillMapNode[];\n\n return {\n schemaVersion: typeof obj.schemaVersion === 'string' ? obj.schemaVersion : undefined,\n nodes,\n edges: obj.edges as SkillMapEdge[],\n };\n}\n\nexport function nodeById(map: SkillMap, id: string): SkillMapNode | undefined {\n return map.nodes.find((n) => n.id === id);\n}\n\nexport function edgesOfType(map: SkillMap, type: string): SkillMapEdge[] {\n return map.edges.filter((e) => e.type === type);\n}\n\n/**\n * Tags for a skill node: its own denormalized `tags` field (if the compiler emits one),\n * plus the names of any `tag` nodes reached via `topic-tag`/`stack-tag` edges FROM this\n * node — tolerant of either representation since the schema was still settling when this\n * was written.\n */\nexport function tagsForNode(map: SkillMap, node: SkillMapNode): string[] {\n const tags = new Set<string>(node.tags ?? []);\n for (const edge of map.edges) {\n if (edge.from !== node.id) continue;\n if (edge.type !== 'topic-tag' && edge.type !== 'stack-tag') continue;\n const tagNode = nodeById(map, edge.to);\n const label = tagNode?.name ?? tagNode?.id;\n if (label) tags.add(label);\n }\n return Array.from(tags);\n}\n","import type { Severity, SafetyFinding } from './scanShared.js';\nimport { tagsForNode, type SkillMap, type SkillMapNode } from './skillMap.js';\n\n/**\n * Ingest-gate overlap check against the generated `rhize-plugins` skill map (Phase 4 of\n * `.claude/plans/skill-map-graph-substrate.md`) — implements that marketplace's own\n * CLAUDE.md curation rule at the point of ingest: \"a new skill must close a gap, never\n * duplicate.\" This is deliberately a SEPARATE, lexical-only check from `gate/overlap.ts`'s\n * local skillsRoot overlap: `overlap.ts` ranks a candidate against skills already installed\n * on disk; this module ranks it against every `skill` node the map knows about across the\n * whole marketplace (including skills not installed in this particular skillsRoot).\n *\n * Scoring intentionally mirrors `gate/overlap.ts`'s Jaccard + containment formula and its\n * ABSORB/FORK/DEFER score tiers (0.45 / 0.2) for consistency — this module does not invent\n * a second scoring scale. No embeddings; pure token overlap.\n *\n * KNOWN LIMITATION — lexical, not semantic. Two skills can describe different OUTPUTS in\n * different vocabulary while claiming the same INVOCATION TRIGGER, and this check will not\n * see it. Measured case (rhize-plugins, 2026-08-23): `project-kickoff` (scaffolds business\n * context files) and `project-launcher` (produces a PRD and a code scaffold) both claimed\n * the trigger \"start a new project\", yet shared only 9 of 56/80 tokens and scored 0.134 —\n * correctly below REPORT_THRESHOLD. Because `containment` divides by the CANDIDATE's token\n * count, a longer, more detailed description also dilutes its own score. Neither is a bug to\n * tune away here: a trigger collision is a semantic property, and lowering the threshold to\n * catch it would flood the gate with unrelated pairs. Reviewers should read competing\n * `description:` trigger phrases by hand; this check is a duplicate-capability net, not a\n * trigger-collision detector.\n */\n\nconst STOPWORDS = new Set(\n `\na an the and or of to for in on with use used using when whether this that these those is are be\nyour you it its as at by from into via per also any all not no than then so if up out about\nskill skills user claude code based comprehensive enhanced practical including include includes\n`\n .split(/\\s+/)\n .filter(Boolean),\n);\n\n/** Same tiers as `gate/overlap.ts`'s VERB_TIERS — kept in sync manually since the two live in different license tiers (this module is MIT; overlap.ts is Pro). */\nconst STRONG_THRESHOLD = 0.45;\nconst REPORT_THRESHOLD = 0.2;\n\nexport interface MapOverlapCandidate {\n name?: string;\n description?: string;\n}\n\nexport interface MapOverlapMatch {\n skillId: string;\n name: string;\n score: number;\n}\n\nexport interface MapOverlapResult {\n /** All skill-node matches at or above REPORT_THRESHOLD, sorted by score descending. Empty when nothing meets the threshold. */\n matches: MapOverlapMatch[];\n /**\n * How many `skill` nodes the candidate was actually compared against. Reported so a caller\n * can tell \"the map loaded and 45 skills were checked\" apart from \"the map check never ran\"\n * — the two were previously indistinguishable, since a below-threshold result and a skipped\n * check both produced an empty `matches` and no finding.\n */\n compared: number;\n /**\n * Highest-scoring node REGARDLESS of REPORT_THRESHOLD. Purely informational — never graded,\n * never fed to `mapOverlapDecision`. Exists so the report can show the closest existing skill\n * and its score even when the gate is (correctly) staying quiet about it.\n */\n topMatch?: MapOverlapMatch;\n}\n\nfunction tokenize(text: string): Set<string> {\n const matches = text.toLowerCase().match(/[a-z][a-z0-9+.-]{2,}/g) ?? [];\n return new Set(matches.filter((t) => !STOPWORDS.has(t)));\n}\n\nfunction intersectSize(a: Set<string>, b: Set<string>): number {\n let n = 0;\n for (const x of a) if (b.has(x)) n += 1;\n return n;\n}\n\nfunction unionSize(a: Set<string>, b: Set<string>): number {\n const u = new Set(a);\n for (const x of b) u.add(x);\n return u.size;\n}\n\nfunction round3(n: number): number {\n return Math.round(n * 1000) / 1000;\n}\n\nfunction score(candidateTokens: Set<string>, otherTokens: Set<string>): number {\n if (candidateTokens.size === 0) return 0;\n const inter = intersectSize(candidateTokens, otherTokens);\n const union = unionSize(candidateTokens, otherTokens);\n const jaccard = union ? inter / union : 0;\n const containment = inter / candidateTokens.size;\n return round3(0.3 * jaccard + 0.7 * containment);\n}\n\nfunction nodeText(map: SkillMap, node: SkillMapNode): string {\n const tags = tagsForNode(map, node);\n return [node.name ?? '', node.description ?? '', ...tags].join(' ');\n}\n\n/**\n * Ranks `candidate` (the ingest gate's profiled name/description) against every `skill`\n * node in `map`. Returns every match at or above REPORT_THRESHOLD — not just the top one —\n * since a candidate can meaningfully overlap more than one existing skill (e.g. two\n * clusters of an already-fragmented capability).\n */\nexport function scanMapOverlap(candidate: MapOverlapCandidate, map: SkillMap): MapOverlapResult {\n const candidateTokens = tokenize(`${candidate.name ?? ''} ${candidate.description ?? ''}`);\n if (candidateTokens.size === 0) return { matches: [], compared: 0 };\n\n const matches: MapOverlapMatch[] = [];\n let compared = 0;\n let topMatch: MapOverlapMatch | undefined;\n for (const node of map.nodes) {\n if (node.type !== 'skill') continue;\n compared += 1;\n const otherTokens = tokenize(nodeText(map, node));\n const s = score(candidateTokens, otherTokens);\n const match: MapOverlapMatch = { skillId: node.id, name: node.name ?? node.id, score: s };\n if (!topMatch || s > topMatch.score) topMatch = match;\n if (s >= REPORT_THRESHOLD) {\n matches.push(match);\n }\n }\n matches.sort((a, b) => b.score - a.score);\n return { matches, compared, topMatch };\n}\n\n/**\n * Turns the top map-overlap match (if any) into a single gate finding, using the same\n * severity vocabulary `gate/safety.ts`/`gate/scanShared.ts` already use — so it flows\n * through the existing `verdictFor()` severity->verdict mapping instead of inventing a new\n * one. A match at/above STRONG_THRESHOLD (mirrors overlap.ts's ABSORB tier — \"this already\n * exists, don't re-ship it\") is severity HIGH, which `verdictFor` escalates to `block` under\n * the default `block-high` strictness: the candidate is held, not silently promoted. A\n * lesser match (mirrors the FORK tier) is MEDIUM, which surfaces as `warn` — hold is the\n * interactive default, but doesn't block outright. Below REPORT_THRESHOLD, no finding is\n * produced (the report already carries the full `matches` list for informational display\n * regardless of this).\n */\nexport function mapOverlapFinding(result: MapOverlapResult): SafetyFinding | undefined {\n const top = result.matches[0];\n if (!top) return undefined;\n\n const severity: Severity = top.score >= STRONG_THRESHOLD ? 'HIGH' : 'MEDIUM';\n return {\n severity,\n rule: 'skill-map-overlap',\n file: 'skill-map',\n detail: `candidate overlaps existing marketplace skill \"${top.name}\" (score ${top.score}) — see CLAUDE.md's curation rule: close the gap, don't duplicate`,\n context: top.skillId,\n };\n}\n\n/**\n * True when `match` is one of the skills `extendsDeclarations` (the candidate's\n * `metadata.rhize.extends` frontmatter) names as a deliberate specialization/layering target.\n * Matches on the bare skill name (`\"<skill-name>\"`) or a plugin-qualified id\n * (`\"<plugin>/<skill-name>\"`) — the two forms the extends-exemption's TASK spec allows.\n * `match.skillId` is treated as an opaque, possibly-prefixed identifier (e.g. a `skill:`\n * kind prefix); comparisons strip that prefix so both bare and qualified declarations can\n * still line up with it. Per-pair only: this never reasons about any match but the one\n * passed in, so declaring an extension of skill X never exempts overlap with skill Y.\n */\nfunction isDeclaredExtension(match: MapOverlapMatch, extendsDeclarations: string[]): boolean {\n const qualifiedId = match.skillId.replace(/^[a-z]+:/, '');\n const bareId = qualifiedId.split('/').pop() ?? qualifiedId;\n return extendsDeclarations.some((raw) => {\n const declared = raw.trim();\n if (!declared) return false;\n return declared === match.name || declared === bareId || declared === qualifiedId;\n });\n}\n\nexport interface MapOverlapDecision {\n /** Present when the top match is NOT a declared extension — feeds the gate's findings/verdict exactly as before. */\n finding?: SafetyFinding;\n /** Present when the top match IS a declared extension — informational only, must never affect the gate verdict. */\n informational?: string;\n}\n\n/**\n * `extends`-aware wrapper around `mapOverlapFinding`: when the top match is a skill the\n * candidate's frontmatter declares (`metadata.rhize.extends`) as a base it deliberately\n * extends, the overlap is expected — this downgrades that single pair from a HOLD-level\n * safety finding to an informational note the report surfaces but `verdictFor` never sees.\n * Overlap with any OTHER skill (a second match, or the same candidate matched against a\n * different skill map) is graded exactly as `mapOverlapFinding` always has — the exemption\n * is per declared pair, never global. `extendsDeclarations` defaults to `[]` so a candidate\n * with no `extends` frontmatter is byte-for-byte the pre-existing behavior.\n */\nexport function mapOverlapDecision(\n result: MapOverlapResult,\n extendsDeclarations: string[] = []\n): MapOverlapDecision {\n const top = result.matches[0];\n if (!top) return {};\n\n if (extendsDeclarations.length > 0 && isDeclaredExtension(top, extendsDeclarations)) {\n return {\n informational: `declared extension of \"${top.name}\" (score ${top.score}) — overlap expected; verify the layering claim in review`,\n };\n }\n\n return { finding: mapOverlapFinding(result) };\n}\n","import type { Profile, McpProfile } from '../gate/profile.js';\nimport type { OverlapResult } from '../gate/overlap.js';\nimport type { SafetyFinding as GateSafetyFinding, Verdict as GateVerdict } from '../gate/safety.js';\nimport { upgradeNotice } from '../license.js';\n\n/**\n * Report renderer — Step 3 of the gate pipeline (profile -> safety scan ->\n * overlap analysis -> report -> promote/reject). Renders the human-readable\n * decision box and a `--json` equivalent.\n */\n\nexport type SafetyVerdict = 'ALLOW' | 'CAUTION' | 'BLOCK' | 'UNKNOWN';\n\nexport interface SafetyFinding {\n severity?: string;\n rule?: string;\n message?: string;\n}\n\n/** Mirrors the verdict shape emitted by skill_safety.py (SkillSpector wrapper). */\nexport interface SafetyResult {\n verdict: SafetyVerdict;\n severity?: string;\n score?: number;\n recommendation?: string;\n findings?: SafetyFinding[];\n}\n\n/**\n * Aggregate result of the quarantine gate pipeline for a single skill\n * candidate — the input to renderReport / renderJson. `worthTaking` and\n * `leaveBehind` are placeholders: they're only populated once a human (or a\n * later ABSORB step) has actually decided what to extract, so they render as\n * pending until then.\n */\nexport interface MapOverlapReport {\n /** `checked` — a map was loaded and compared; `skipped` — a map was requested but unreadable; `not-requested` — no `--skill-map` was passed. */\n status: 'checked' | 'skipped' | 'not-requested';\n /** Number of `skill` nodes compared against (status `checked`). */\n compared?: number;\n /** Closest existing skill and its score, even when below the reporting threshold. */\n topMatch?: { skillId: string; name: string; score: number };\n /** Matches at or above the reporting threshold — the ones that actually produce a finding. */\n matches?: Array<{ skillId: string; name: string; score: number }>;\n /** Why the check was skipped (status `skipped`). */\n reason?: string;\n}\n\nexport interface GateResult {\n profile: Profile;\n safety: SafetyResult;\n overlap?: OverlapResult;\n /** True when overlap analysis was skipped for lack of a Pro license (as opposed to a missing skills root). */\n overlapLocked?: boolean;\n /**\n * Status of the marketplace-wide curation check (`--skill-map`), so \"the map was checked and\n * nothing crossed the threshold\" is distinguishable from \"the map was never checked\". Before\n * this existed both looked identical from the outside — an empty findings list — which is\n * exactly how a passing curation check got mistaken for a broken one.\n */\n mapOverlap?: MapOverlapReport;\n worthTaking?: string[];\n leaveBehind?: string[];\n /** Pro-feature beta/upgrade notices (see src/license.ts's proFeatureStatus) — printed above the terminal report, or carried as a JSON field for --json. */\n notices?: string[];\n /**\n * Audit-state context lines (src/auditState.ts), gathered once by `runGate` so every gate flow\n * gets them without each call site remembering to. Rendered BELOW the verdict and deliberately\n * omitted from `renderJson` — advisory text must never be mistakable for a gate result.\n */\n advisory?: string[];\n}\n\n/** Maps gate/safety.ts's pass|warn|block verdict to this module's ALLOW|CAUTION|BLOCK — shared by commands/gatePipeline.ts's runGate() and evolve.ts's re-gate step. */\nexport const SAFETY_VERDICT_MAP: Record<GateVerdict, SafetyVerdict> = {\n pass: 'ALLOW',\n warn: 'CAUTION',\n block: 'BLOCK',\n};\n\n/** Maps gate/safety.ts's {severity, rule, file, detail} findings into this module's {severity, rule, message} shape — shared by the same two call sites as SAFETY_VERDICT_MAP. */\nexport function mapSafetyFindings(findings: GateSafetyFinding[]): SafetyFinding[] {\n return findings.map((f) => ({ severity: f.severity, rule: f.rule, message: `${f.file}: ${f.detail}` }));\n}\n\nconst BORDER = '━'.repeat(55);\nconst LABEL_WIDTH = 20;\n\nfunction padLabel(text: string): string {\n return text.padEnd(LABEL_WIDTH);\n}\n\nfunction renderListField(labelText: string, items: string[] | undefined, placeholder: string): string {\n const head = `${padLabel(labelText)}: `;\n if (!items || items.length === 0) {\n return `${head}${placeholder}`;\n }\n const bullets = items.map((item) => ` - ${item}`).join('\\n');\n return `${head.trimEnd()}\\n${bullets}`;\n}\n\nfunction renderSafetyLine(safety: SafetyResult): string {\n const detail = [safety.severity, safety.score !== undefined ? `score ${safety.score}` : undefined]\n .filter((v): v is string => Boolean(v))\n .join(', ');\n const withDetail = detail ? `${safety.verdict} (${detail})` : safety.verdict;\n return safety.recommendation ? `${withDetail} — ${safety.recommendation}` : withDetail;\n}\n\nfunction renderFindingLine(finding: SafetyFinding): string {\n const severity = finding.severity ?? 'UNKNOWN';\n const rule = finding.rule ?? 'unknown-rule';\n return ` - [${severity}] ${rule}${finding.message ? `: ${finding.message}` : ''}`;\n}\n\n/**\n * Renders the safety findings list — severity, rule, and a one-line detail\n * per finding — so a blocked/cautioned skill shows *why* without needing\n * `--json`. Omitted entirely when there are no findings.\n */\nfunction renderFindingsLines(findings: SafetyFinding[] | undefined): string[] {\n if (!findings?.length) return [];\n return [`${padLabel('Findings')}:`, ...findings.map(renderFindingLine)];\n}\n\n/**\n * Header fields for the \"🔨 Forge: ...\" line — a skill profile has\n * name/version/license; an MCP profile (v0.5) has no such fields (it may\n * describe more than one server entry), so it renders the server name(s)\n * instead and 'n/a' for version/license. Plugin profiles have name/version\n * but no license (not currently reachable via add/scan's CLI routing).\n */\nfunction profileHeaderFields(profile: Profile): { name: string; version: string; license: string } {\n if (profile.kind === 'mcp') {\n const name = profile.serverCount === 1 ? profile.servers[0] : `${profile.serverCount} servers`;\n return { name, version: 'n/a', license: 'n/a' };\n }\n return {\n name: profile.name ?? 'unknown',\n version: profile.version ?? 'unknown',\n license: profile.kind === 'skill' ? profile.license : 'UNKNOWN',\n };\n}\n\nfunction pluralize(count: number, noun: string): string {\n return `${count} ${noun}${count === 1 ? '' : 's'}`;\n}\n\n/**\n * v0.6: one-line static capability summary for an MCP profile — tool/resource/prompt\n * counts plus `declaredConfidence`, so a candidate's declared surface is visible without\n * `--json`. Free tier (it's profiling, not the Pro overlap/decide pass) — always rendered\n * for `profile.kind === 'mcp'`, never gated on `overlapLocked`/`proEnabled`.\n */\nfunction renderCapabilitiesLine(profile: McpProfile): string {\n const { tools, resources, prompts, declaredConfidence } = profile.capabilities;\n const counts = [pluralize(tools.length, 'tool'), pluralize(resources.length, 'resource'), pluralize(prompts.length, 'prompt')].join(\n ', '\n );\n return `${padLabel('Capabilities')}: ${counts} (declaredConfidence: ${declaredConfidence})`;\n}\n\n/**\n * One line reporting the marketplace curation check (`--skill-map`). Always rendered when a map\n * was requested, including the quiet cases — a check that leaves no trace when it passes cannot\n * be told apart from one that never ran.\n */\nfunction renderMapOverlapLines(report: MapOverlapReport | undefined): string[] {\n if (!report || report.status === 'not-requested') return [];\n if (report.status === 'skipped') {\n return [`${padLabel('Marketplace overlap')}: skipped — ${report.reason ?? 'map unavailable'}`];\n }\n const top = report.topMatch;\n const flagged = report.matches?.length ?? 0;\n const closest = top ? `closest \"${top.name}\" (${top.score})` : 'no comparable skills';\n const verdict = flagged > 0 ? `${flagged} at/over threshold` : 'none over threshold';\n return [`${padLabel('Marketplace overlap')}: checked ${report.compared ?? 0} skills — ${closest}, ${verdict}`];\n}\n\n/** Renders the Step-3 terminal decision box for a single gate result. */\nexport function renderReport(result: GateResult): string {\n const { profile, overlap, overlapLocked, safety, worthTaking, leaveBehind } = result;\n const { name, version, license } = profileHeaderFields(profile);\n\n // Locked (no Pro license): the two overlap lines collapse into one short\n // upgrade notice instead of \"n/a\" placeholders for data we never computed.\n const overlapLines = overlapLocked\n ? [`${padLabel('Overlap analysis')}: ${upgradeNotice('overlap analysis')}`]\n : overlap\n ? [\n `${padLabel('Nearest existing skill')}: ${overlap.nearest} (overlap ${overlap.nearestScore})`,\n `${padLabel('Recommended verb')}: ${overlap.suggestedVerb}`,\n ]\n : [`${padLabel('Nearest existing skill')}: n/a — no skills root configured`, `${padLabel('Recommended verb')}: n/a`];\n\n const lines: string[] = [\n BORDER,\n `🔨 Forge: ${name} (v${version}, ${license})`,\n BORDER,\n `${padLabel('Artifact type')}: ${profile.artifactType}`,\n ...(profile.kind === 'mcp' ? [renderCapabilitiesLine(profile)] : []),\n ...overlapLines,\n ...renderMapOverlapLines(result.mapOverlap),\n renderListField(\"What's worth taking\", worthTaking, '(pending — not yet determined)'),\n renderListField('What to leave behind', leaveBehind, '(pending — not yet determined)'),\n `${padLabel('Safety verdict')}: ${renderSafetyLine(safety)}`,\n ...renderFindingsLines(safety.findings),\n BORDER,\n ];\n\n return lines.join('\\n');\n}\n\n/**\n * Renders a gate result as JSON for `--json` output.\n *\n * `advisory` is stripped deliberately: it is audit CONTEXT about the set the candidate is landing\n * in, not part of the gate's answer about the candidate, and leaving it in the payload would let\n * consumers start branching on it as though it were a result. It goes to stderr instead (see\n * printGateOutput).\n */\nexport function renderJson(result: GateResult): string {\n const { advisory: _advisory, ...payload } = result;\n return JSON.stringify(payload, null, 2);\n}\n\n/**\n * Prints a gate result the same way for `add`/`scan`/`evolve`: any Pro-feature\n * notices then the terminal report box normally, or a single parseable JSON\n * payload with `--json` (notices ride along as the payload's `notices` field\n * instead of being printed above it). Moved here (v0.7 simplify pass) from\n * `commands/gatePipeline.ts`, which re-exports it so `add.ts`/`scan.ts`'s\n * existing import path keeps working.\n */\nexport function printGateOutput(gateResult: GateResult, json?: boolean): void {\n if (!json) {\n for (const notice of gateResult.notices ?? []) console.log(`[skill-forge] ${notice}`);\n }\n console.log(json ? renderJson(gateResult) : renderReport(gateResult));\n\n // v0.11 audit advisory (src/auditState.ts): context about the set the candidate is landing in,\n // printed BELOW the verdict so it can never be read as part of it. In --json mode it goes to\n // stderr, keeping stdout exactly one parseable document — and it is deliberately NOT a field of\n // the JSON payload, so no consumer can start branching on advisory text as though it were a\n // gate result.\n const advisory = gateResult.advisory ?? [];\n if (advisory.length === 0) return;\n if (json) {\n for (const line of advisory) console.error(`[skill-forge] ${line}`);\n return;\n }\n // First line takes the label column; the rest align under it.\n console.log(`${padLabel('Audit context')}: ${advisory[0]}`);\n for (const line of advisory.slice(1)) console.log(`${' '.repeat(LABEL_WIDTH + 2)}${line}`);\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { randomUUID } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { getHomeDir } from './config.js';\nimport type { Verdict as SafetyVerdict } from './gate/safety.js';\nimport type { SourceType } from './install/resolve.js';\nimport type { ForgeVerb } from './provenance.js';\n\n/**\n * Pending-ingestion queue — persists every skill install decided by `add`\n * (promote or hold) to `<SKILL_FORGE_HOME>/queue.json`, so the `--ingest`\n * Claude handoff (or a future `queue` command) can pick the entry back up.\n * The entry shape here, `schemas/queue-entry.schema.json`, and\n * `docs/queue-schema.md` are the single source of truth for the pending-queue\n * entry spec — keep all three in sync if the shape ever changes.\n */\n\n/** Suggested forge verb on a queue entry — the five-verb matrix defined by provenance.ts's FORGE_VERBS. */\nexport type SuggestedVerb = ForgeVerb;\nexport type { SafetyVerdict, SourceType };\n\n/** What kind of candidate this queue entry is for. Absent on an entry means 'skill' (pre-v0.5 entries stay valid). */\nexport type ArtifactType = 'skill' | 'mcp';\n\n/** How this entry was produced, when it isn't a normal `add`-sourced external install. Absent means a normal entry. 'evolve' (v0.7): self-evolved via `skill-forge evolve` (SkillOpt-Sleep) and already re-gated at adopt time. */\nexport type QueueEntryOrigin = 'evolve';\n\nexport type QueueStatus = 'pending' | 'ingested' | 'dismissed';\n\n/**\n * Statically-extracted MCP server capability profile (v0.6) — tool/resource/prompt names\n * and transport hints parsed from a quarantined MCP candidate's package.json, manifest, and\n * source text (NEVER by executing the server). Only present on `artifactType: 'mcp'` entries;\n * absent means the entry predates v0.6 or profiling couldn't determine anything.\n */\nexport interface McpCapabilities {\n tools: string[];\n resources: string[];\n prompts: string[];\n /** Best-effort transport hint (e.g. 'stdio', 'http'), when determinable statically. */\n transport?: string;\n /** How confidently `tools`/`resources`/`prompts` were determined from static analysis alone. */\n declaredConfidence: 'high' | 'partial' | 'none';\n}\n\nexport interface OverlapTopEntry {\n skill: string;\n score: number;\n}\n\nexport interface QueueEntryGate {\n license: string | null;\n safetyVerdict: SafetyVerdict;\n safetyFindings: string[];\n /** Top overlap matches against the configured skill set; empty on the free tier. */\n overlapTop: OverlapTopEntry[];\n suggestedVerb: SuggestedVerb | null;\n}\n\nexport interface QueueEntry {\n id: string;\n source: string;\n sourceType: SourceType;\n /** Final path after promote; null while the candidate is only held in quarantine. */\n installedPath: string | null;\n quarantinePath: string;\n gate: QueueEntryGate;\n status: QueueStatus;\n createdAt: string;\n /** 'skill' | 'mcp'; optional — absent means 'skill' (pre-v0.5 entries don't have this field). */\n artifactType?: ArtifactType;\n /** Statically-extracted MCP capability profile; optional — absent means pre-v0.6 or undetermined. */\n capabilities?: McpCapabilities;\n /** How this entry was produced; optional — absent means a normal `add`-sourced entry. See QueueEntryOrigin. */\n origin?: QueueEntryOrigin;\n}\n\nexport interface Queue {\n version: 1;\n entries: QueueEntry[];\n}\n\n/** Input for a new entry — `id` and `createdAt` are generated by `appendQueueEntry`. */\nexport type NewQueueEntry = Omit<QueueEntry, 'id' | 'createdAt'>;\n\nexport function getQueuePath(): string {\n return join(getHomeDir(), 'queue.json');\n}\n\n/** Reads the queue from disk, defaulting to an empty queue when the file doesn't exist yet. */\nexport function readQueue(): Queue {\n const path = getQueuePath();\n if (!existsSync(path)) {\n return { version: 1, entries: [] };\n }\n const raw = readFileSync(path, 'utf-8');\n const parsed = JSON.parse(raw) as Partial<Queue>;\n return { version: 1, entries: parsed.entries ?? [] };\n}\n\n/**\n * Writes the queue atomically: serialize to a temp file in the same directory (so the rename\n * below is same-filesystem and therefore atomic), then `renameSync` it over the real path. A\n * reader can never observe a partially-written queue.json, and a crash mid-write leaves the\n * previous queue.json intact plus an orphaned temp file rather than a corrupted one. The temp\n * file is cleaned up on any write failure.\n */\nfunction writeQueue(queue: Queue): void {\n const home = getHomeDir();\n mkdirSync(home, { recursive: true }); // recursive mkdir is a no-op when the dir exists\n const finalPath = getQueuePath();\n const tempPath = join(home, `.queue.json.tmp-${randomUUID()}`);\n try {\n writeFileSync(tempPath, JSON.stringify(queue, null, 2) + '\\n', 'utf-8');\n renameSync(tempPath, finalPath);\n } catch (err) {\n rmSync(tempPath, { force: true });\n throw err;\n }\n}\n\n/** A count that also says whether the underlying store could actually be read. */\nexport interface CountResult {\n count: number;\n readable: boolean;\n}\n\n/**\n * Number of entries still awaiting a decide pass, reporting unreadable rather than 0 on a malformed\n * queue.json. Shared by `status`/`guide`/`routine`, which are all diagnostics — each must survive\n * a broken queue well enough to report everything else, and each previously inlined this with its\n * own try/catch, letting the counts drift apart.\n */\nexport function pendingQueueCount(): CountResult {\n try {\n return { count: readQueue().entries.filter((e) => e.status === 'pending').length, readable: true };\n } catch {\n // A MISSING queue.json is not an error — readQueue returns an empty queue for that. Reaching\n // here means the file exists and is corrupt, which must not be reported as \"0 pending\": the\n // queue records already-promoted skills still owed a decide-pass verb, so a silent 0 makes\n // unvetted installed skills vanish from exactly the commands meant to surface them.\n return { count: 0, readable: false };\n }\n}\n\n/** Appends a new entry (generating `id`/`createdAt`) and persists the queue. Returns the full entry. */\nexport function appendQueueEntry(entry: NewQueueEntry): QueueEntry {\n const queue = readQueue();\n const existingIds = new Set(queue.entries.map((e) => e.id));\n const full: QueueEntry = {\n ...entry,\n id: generateId(existingIds),\n createdAt: new Date().toISOString(),\n };\n queue.entries.push(full);\n writeQueue(queue);\n return full;\n}\n\n/** Applies a partial update to the entry with `id` (e.g. a status transition) and persists the queue. */\nexport function updateQueueEntry(id: string, patch: Partial<Omit<QueueEntry, 'id' | 'createdAt'>>): QueueEntry {\n const queue = readQueue();\n const index = queue.entries.findIndex((e) => e.id === id);\n if (index === -1) {\n throw new Error(`no queue entry with id: ${id}`);\n }\n const updated: QueueEntry = { ...queue.entries[index], ...patch };\n queue.entries[index] = updated;\n writeQueue(queue);\n return updated;\n}\n\n/** Short unique slug (8 hex chars from a UUID), collision-checked against ids already in the queue. */\nfunction generateId(existingIds: Set<string>): string {\n let id: string;\n do {\n id = randomUUID().split('-')[0];\n } while (existingIds.has(id));\n return id;\n}\n","import { spawnSync, type StdioOptions } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Generic ingestion handoff — after a skill is promoted (or held), optionally\n * launches a coding agent to run the deep decide/absorb pass described in the\n * bundled `assets/ingest-prompt.md`. Deliberately agent-neutral: nothing here\n * assumes Claude specifically (see `docs/queue-schema.md` for the pending\n * queue this hands off into).\n *\n * SECURITY: `config.handoffCommand` (and the built-in agent-detection\n * fallback) are **argv-style arrays**, not shell strings. `runHandoff` always\n * calls `spawnSync(bin, args, ...)` with no `shell` option, so a token like\n * `\"; rm -rf /\"` is passed to the child process as a single literal argv\n * element — it is never parsed by a shell. Do not add `shell: true` here, and\n * do not build the command by joining tokens into one string.\n */\n\n/** Minimal config shape this module needs — kept structural so it doesn't require editing src/config.ts's Config type. */\nexport interface HandoffConfig {\n /**\n * Optional argv-style command template, e.g.\n * `[\"claude\", \"-p\", \"read {prompt} and process the skill at {path}\"]`.\n * `{path}` is replaced with the absolute installed-skill path; `{prompt}`\n * is replaced with the absolute path to the bundled ingest-prompt.md.\n * An empty/absent array falls through to the detection chain below.\n */\n handoffCommand?: string[];\n}\n\nexport interface HandoffCommand {\n /** argv (index 0 is the binary) to spawn; empty when nothing runnable was found. */\n argv: string[];\n /** How this command was resolved. */\n source: 'template' | 'detected' | 'none';\n}\n\nexport interface HandoffDeps {\n /** Checks whether a binary is available on PATH. Overridable for tests; defaults to a real `which`/`where` check. */\n isOnPath?: (bin: string) => boolean;\n /**\n * Which bundled `assets/` file to hand off — passed through to `resolvePromptPath()`.\n * Defaults to `'ingest-prompt.md'` (unchanged for `add --ingest`). `skill-forge audit`'s\n * `--handoff` passes `'curation-prompt.md'` instead (v0.8).\n */\n promptAsset?: string;\n}\n\nexport interface RunHandoffOptions extends HandoffDeps {\n /** stdio for the spawned process. Defaults to 'inherit' (the handoff is an interactive agent session). Tests can override to capture output. */\n stdio?: StdioOptions;\n /** Overrides argv[0]-execution for tests; defaults to node:child_process spawnSync. */\n spawn?: typeof spawnSync;\n /**\n * Sink for the \"no agent available\" / \"binary not found on PATH\" print-only messages this\n * function itself emits (NOT the spawned child's own stdio, which `stdio` above controls).\n * Defaults to `console.log`. `skill-forge audit --json --handoff` passes `console.error` here\n * so a print-only message can never land on stdout after the one JSON document already\n * written there (v0.8 fix) — `add --ingest`'s call site is unaffected, still defaulting to\n * `console.log`.\n */\n log?: (message: string) => void;\n}\n\nexport interface RunHandoffResult {\n /** Whether a process was actually spawned (successfully or not). */\n ran: boolean;\n /** The resolved command, for callers that want to log/inspect it. */\n command: HandoffCommand;\n /** The message printed to the user in print-only mode or when the resolved binary was missing. */\n printed?: string;\n /** Set when spawning failed (e.g. ENOENT). */\n error?: string;\n}\n\n/** Binaries checked, in order, when no `handoffCommand` template is configured. */\nconst KNOWN_AGENT_BINARIES = ['claude', 'codex', 'cursor-agent', 'windsurf', 'opencode', 'gemini'];\n\n/**\n * The generic ingest instruction used in handoff argv templates — `{prompt}` and `{path}` are\n * replaced by `substitute()`. Shared by the detected-binary fallback below, the per-agent\n * templates in src/agents.ts, and `skill-forge init`'s best-effort fallback.\n */\nexport const HANDOFF_INSTRUCTION = 'Read {prompt} and follow its instructions for the skill installed at {path}';\n\n/**\n * Absolute path to a bundled handoff prompt asset (default `ingest-prompt.md`; `skill-forge\n * audit --handoff` passes `curation-prompt.md` — v0.8), resolved relative to this module so it\n * works both from source (tests run against `src/`) and from the built package (`dist/`).\n *\n * tsup's `publicDir` copies the *contents* of `assets/` into `dist/`\n * (flattening one level), so the built layout is `dist/<assetName>`\n * sitting next to the bundled `dist/cli.js` — see tsup.config.ts. The dev/\n * test layout is the repo's own `assets/<assetName>`, one level up from\n * `src/`.\n */\nexport function resolvePromptPath(assetName: string = 'ingest-prompt.md'): string {\n const here = dirname(fileURLToPath(import.meta.url));\n const builtCandidate = join(here, assetName);\n if (existsSync(builtCandidate)) return builtCandidate;\n return join(here, '..', 'assets', assetName);\n}\n\nfunction defaultIsOnPath(bin: string): boolean {\n const finder = process.platform === 'win32' ? 'where' : 'which';\n const result = spawnSync(finder, [bin], { stdio: 'ignore' });\n return result.status === 0;\n}\n\n/** Replaces `{path}` and `{prompt}` tokens in each argv element; unknown `{word}` tokens are left as-is. */\nfunction substitute(template: string[], vars: Record<string, string>): string[] {\n return template.map((token) => token.replace(/\\{(\\w+)\\}/g, (match, key: string) => vars[key] ?? match));\n}\n\n/**\n * Resolves the handoff command via a fallback chain:\n * 1. `config.handoffCommand` template, with `{path}`/`{prompt}` substituted.\n * 2. The first known agent binary found on PATH, invoked generically with\n * the ingest prompt.\n * 3. Nothing runnable (`source: 'none'`) — the caller should print\n * instructions instead of spawning anything.\n */\nexport function buildHandoffCommand(config: HandoffConfig, installedPath: string, deps: HandoffDeps = {}): HandoffCommand {\n const isOnPath = deps.isOnPath ?? defaultIsOnPath;\n const promptPath = resolvePromptPath(deps.promptAsset);\n const vars = { path: installedPath, prompt: promptPath };\n\n if (config.handoffCommand && config.handoffCommand.length > 0) {\n return { argv: substitute(config.handoffCommand, vars), source: 'template' };\n }\n\n for (const bin of KNOWN_AGENT_BINARIES) {\n if (isOnPath(bin)) {\n return { argv: substitute([bin, '-p', HANDOFF_INSTRUCTION], vars), source: 'detected' };\n }\n }\n\n return { argv: [], source: 'none' };\n}\n\n/** The message shown when there's nothing runnable to spawn (print-only mode, or a resolved binary that turned out missing). */\nfunction printOnlyMessage(installedPath: string, promptAsset?: string): string {\n const promptPath = resolvePromptPath(promptAsset);\n return (\n '[skill-forge] no agent handoff available — run one yourself:\\n' +\n ` Prompt: ${promptPath}\\n` +\n ` Skill path: ${installedPath}`\n );\n}\n\n/**\n * Executes the resolved handoff command WITHOUT a shell (`spawnSync(bin,\n * args, ...)` — no `shell: true`), so argv-style templates can never be\n * shell-interpreted. Falls back to printing the command for the user to run\n * manually in print-only mode, or if the resolved binary isn't actually on\n * PATH at spawn time (ENOENT).\n */\nexport function runHandoff(config: HandoffConfig, installedPath: string, options: RunHandoffOptions = {}): RunHandoffResult {\n const command = buildHandoffCommand(config, installedPath, options);\n const spawn = options.spawn ?? spawnSync;\n const log = options.log ?? console.log;\n\n if (command.source === 'none' || command.argv.length === 0) {\n const printed = printOnlyMessage(installedPath, options.promptAsset);\n log(printed);\n return { ran: false, command, printed };\n }\n\n const [bin, ...args] = command.argv;\n const result = spawn(bin, args, { stdio: options.stdio ?? 'inherit' });\n const error = result.error as NodeJS.ErrnoException | undefined;\n\n if (error?.code === 'ENOENT') {\n const printed = printOnlyMessage(installedPath, options.promptAsset);\n log(`[skill-forge] ${bin} not found on PATH — run this yourself:`);\n log(printed);\n return { ran: false, command, printed, error: error.message };\n }\n if (error) {\n console.error(`[skill-forge] failed to launch handoff: ${error.message}`);\n return { ran: false, command, error: error.message };\n }\n\n return { ran: true, command };\n}\n","import { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { createInterface, type Interface } from 'node:readline/promises';\nimport {\n AGENTS,\n agentCliPresence,\n detectAgentRoot,\n detectMcpConfigFiles,\n type Agent,\n type CliPresence,\n type DetectedMcpConfig,\n type DetectedRoot,\n} from '../agents.js';\nimport { getConfigPath, loadConfig, saveConfig, type Config } from '../config.js';\nimport { HANDOFF_INSTRUCTION } from '../handoff.js';\nimport { auditCommand } from './audit.js';\nimport type { AuditCommandOptions } from './audit.js';\n\nexport interface InitOptions {\n /** Non-interactive: use all detected agents as gate targets, first as the default target. */\n defaults?: boolean;\n /** Print detection results only — no writes. */\n list?: boolean;\n /** Overrides for tests; default to the real process cwd / home dir. */\n cwd?: string;\n home?: string;\n /**\n * When explicitly `false`, suppresses the interactive \"Run the skills & MCP audit now?\" tail\n * offer entirely — no question is asked and this run of `init` never calls `runAudit` itself.\n * Set only by `maybeOfferInit`'s call from `auditCommand` (v0.8 fix): that caller is already\n * about to run the single audit itself once `init()` returns, so init's own tail-audit offer\n * must stay silent — otherwise the audit (and its interactive business-profile Q&A) runs\n * twice. Direct `skill-forge init` (default/omitted, i.e. `true`) is unaffected.\n */\n offerAudit?: boolean;\n /**\n * Opt out of v0.11 PATH narrowing: keep every detected root pre-selected regardless of whether\n * any agent that maps to it has its CLI on PATH. Wired to `--all-agents`.\n */\n allAgents?: boolean;\n /** Override for tests; defaults to the real `process.env.PATH`. */\n pathEnv?: string;\n}\n\nexport interface InitDeps {\n /** Injected audit runner — init's final phase (v0.8) is `skill-forge audit`. Defaults to the real `auditCommand`; overridable so init tests don't have to run a full audit end-to-end. */\n runAudit?: (options?: AuditCommandOptions) => Promise<void>;\n}\n\ninterface DetectedAgent extends DetectedRoot {\n agent: Agent;\n /** Whether this agent's CLI is on PATH — see `agentCliPresence`. Drives the default selection. */\n presence: CliPresence;\n}\n\ninterface DetectedMcpTarget extends DetectedMcpConfig {\n agent: Agent;\n presence: CliPresence;\n}\n\n/**\n * One distinct skills root, plus every detected agent that resolves to it.\n *\n * Many agents share a root by design — 18 of the 73 entries in src/agents.ts use `.agents/skills`\n * as their project root — so a per-AGENT menu shows the same path up to 18 times, and toggling one\n * entry off changes nothing about what gets gated. Selection, the written `skillsRoots`, and the\n * default-target menu are all keyed on the PATH for that reason; the agent list survives only as\n * the label and as the informational `config.agents` id->root map.\n */\ninterface RootGroup {\n root: string;\n /** Every scope this root was matched under — both when cwd === home makes project and global collide. */\n scopes: Set<'project' | 'global'>;\n members: DetectedAgent[];\n}\n\n/** The MCP-config-file analog of `RootGroup` — same duplicate-path problem, same fix. */\ninterface McpGroup {\n path: string;\n scopes: Set<'project' | 'global'>;\n members: DetectedMcpTarget[];\n}\n\nfunction groupByRoot(detected: DetectedAgent[]): RootGroup[] {\n const byRoot = new Map<string, RootGroup>();\n for (const d of detected) {\n const existing = byRoot.get(d.root);\n if (existing) {\n existing.members.push(d);\n existing.scopes.add(d.scope);\n } else {\n byRoot.set(d.root, { root: d.root, scopes: new Set([d.scope]), members: [d] });\n }\n }\n return [...byRoot.values()];\n}\n\nfunction groupMcpByPath(detected: DetectedMcpTarget[]): McpGroup[] {\n const byPath = new Map<string, McpGroup>();\n for (const d of detected) {\n const existing = byPath.get(d.path);\n if (existing) {\n existing.members.push(d);\n existing.scopes.add(d.scope);\n } else {\n byPath.set(d.path, { path: d.path, scopes: new Set([d.scope]), members: [d] });\n }\n }\n return [...byPath.values()];\n}\n\n/**\n * True when at least one agent sharing this path has its CLI on PATH — i.e. there is positive\n * evidence somebody actually uses this root. Groups where every member is `absent`/`unknown` are\n * still listed, just not pre-selected.\n */\nfunction hasInstalledMember(group: { members: { presence: CliPresence }[] }): boolean {\n return group.members.some((m) => m.presence === 'installed');\n}\n\n/**\n * Label for one grouped path. Names the agents whose CLI is on PATH first, because on a shared\n * root those are the ones that justify gating it: `on PATH: Codex, Gemini CLI, OpenCode (+16 other\n * agents share this root)`. Falls back to naming the agents themselves when none are confirmed.\n */\nfunction describeGroup(members: { agent: Agent; presence: CliPresence }[], scopes: Set<'project' | 'global'>): string {\n const scope = [...scopes].join('+');\n const installed = members.filter((m) => m.presence === 'installed').map((m) => m.agent.displayName);\n const others = members.length - installed.length;\n\n if (installed.length > 0) {\n const share = others > 0 ? ` (+${others} other agent${others === 1 ? '' : 's'} share this path)` : '';\n return `on PATH: ${installed.join(', ')} (${scope})${share}`;\n }\n\n const names = members.map((m) => m.agent.displayName);\n const shown = names.slice(0, 3).join(', ');\n const more = names.length > 3 ? `, +${names.length - 3} more` : '';\n const suffix = members.every((m) => m.presence === 'absent') ? 'CLI not on PATH' : 'no verified CLI name to check';\n const who = names.length === 1 ? names[0] : `${names.length} agents: ${shown}${more}`;\n return `${who} (${scope}) — ${suffix}`;\n}\n\nfunction detectAgents(cwd: string, home: string, pathEnv?: string): DetectedAgent[] {\n const found: DetectedAgent[] = [];\n for (const agent of AGENTS) {\n const hit = detectAgentRoot(agent, cwd, home);\n if (hit) found.push({ agent, ...hit, presence: agentCliPresence(agent, pathEnv) });\n }\n return found;\n}\n\n/**\n * Probes every known agent's `mcpConfig` locations (see src/agents.ts) and returns every MCP\n * config file that exists on disk. Unlike `detectAgents`, an agent can contribute more than one\n * hit (project and global config can both exist and both matter as promotion targets).\n */\nfunction detectMcpTargets(cwd: string, home: string, pathEnv?: string): DetectedMcpTarget[] {\n const found: DetectedMcpTarget[] = [];\n for (const agent of AGENTS) {\n for (const hit of detectMcpConfigFiles(agent, cwd, home)) {\n found.push({ agent, ...hit, presence: agentCliPresence(agent, pathEnv) });\n }\n }\n return found;\n}\n\nfunction printDetectionResults(detected: DetectedAgent[]): void {\n console.log(`[skill-forge] init — agent detection (${AGENTS.length} known agents)`);\n if (detected.length === 0) {\n console.log(' none detected');\n return;\n }\n for (const d of detected) {\n console.log(` ${d.agent.displayName} (${d.agent.id}) — ${d.scope}: ${d.root}${presenceNote(d.presence)}`);\n }\n const distinct = new Set(detected.map((d) => d.root)).size;\n if (distinct < detected.length) {\n console.log(` → ${detected.length} agents share ${distinct} distinct skills root(s); gating is per root.`);\n }\n const installed = detected.filter((d) => d.presence === 'installed').length;\n if (installed > 0 && installed < detected.length) {\n console.log(` → ${installed} of ${detected.length} have their CLI on PATH; only those roots are pre-selected (--all-agents to keep all).`);\n }\n}\n\n/** Suffix disclosing why an entry is (or isn't) treated as installed. `unknown` stays silent per-line — the menu says it. */\nfunction presenceNote(presence: CliPresence): string {\n if (presence === 'installed') return ' [CLI on PATH]';\n if (presence === 'absent') return ' [CLI not on PATH]';\n return '';\n}\n\nfunction printMcpDetectionResults(detectedMcp: DetectedMcpTarget[]): void {\n if (detectedMcp.length === 0) return;\n console.log('\\n[skill-forge] init — MCP config detection');\n for (const d of detectedMcp) {\n console.log(` ${d.agent.displayName} (${d.agent.id}) — ${d.scope}: ${d.path}${presenceNote(d.presence)}`);\n }\n const distinct = new Set(detectedMcp.map((d) => d.path)).size;\n if (distinct < detectedMcp.length) {\n console.log(` → ${detectedMcp.length} entries resolve to ${distinct} distinct config file(s).`);\n }\n}\n\nfunction handoffTemplateFor(agent: Agent): string[] {\n // Best-effort fallback when we don't have a confirmed CLI invocation: use the agent id as\n // the binary name. May not match the real CLI for lesser-known agents — defaultHandoffTemplate\n // should be preferred whenever it's set.\n return agent.defaultHandoffTemplate ?? [agent.id, '-p', HANDOFF_INSTRUCTION];\n}\n\nfunction buildConfig(\n selected: RootGroup[],\n defaultTargetEntry: RootGroup | undefined,\n handoffAgent: Agent | undefined,\n selectedMcp: McpGroup[] = [],\n): Config {\n const existing = loadConfig();\n return {\n ...existing,\n // Skill-root selections are only overwritten when this run actually offered a choice —\n // an mcp-only run (no skill roots detected) must not blank out an existing skillsRoots/agents.\n // Both lists are one entry per DISTINCT path (see RootGroup); `agents` stays per-agent because\n // it is an id->root map, where several ids legitimately point at the same root.\n skillsRoots: selected.length > 0 ? selected.map((s) => s.root) : existing.skillsRoots,\n agents:\n selected.length > 0\n ? selected.flatMap((s) => s.members.map((m) => ({ id: m.agent.id, skillsRoot: s.root })))\n : existing.agents,\n defaultTarget: defaultTargetEntry?.root ?? existing.defaultTarget,\n handoffCommand: handoffAgent ? handoffTemplateFor(handoffAgent) : existing.handoffCommand,\n mcpTargets: selectedMcp.length > 0 ? selectedMcp.map((m) => m.path) : existing.mcpTargets,\n };\n}\n\nfunction printSummary(config: Config): void {\n console.log(`\\n[skill-forge] wrote config to ${getConfigPath()}`);\n console.log(` gate targets: ${config.skillsRoots.join(', ')}`);\n if (config.defaultTarget) console.log(` default target: ${config.defaultTarget}`);\n if (config.handoffCommand?.length) console.log(` handoff command: ${config.handoffCommand.join(' ')}`);\n if (config.mcpTargets?.length) console.log(` mcp targets: ${config.mcpTargets.join(', ')}`);\n}\n\n/**\n * True for the rejection `readline/promises` raises when a real terminal sends EOF mid-question\n * (Ctrl+D): `AbortError` / `ABORT_ERR`. It's a user cancelling, not a bug, so it must not reach\n * cli.ts's \"unexpected error ... re-run with DEBUG=skill-forge\" branch.\n *\n * Not covered by test/init.test.ts: this rejection is a TTY keypress behavior, and `withFakeTty`'s\n * PassThrough can't produce it — ENDING that stream leaves the pending `question()` unsettled\n * forever rather than aborting, so such a test hangs to timeout instead of asserting. Verified\n * manually against a pty (`script -q /dev/null node dist/cli.js init` with stdin closed): before\n * this guard it printed `skill-forge: unexpected error: Aborted with Ctrl+D`, after it prints\n * `[skill-forge] init cancelled.` and exits 0.\n */\nfunction isPromptAbort(err: unknown): boolean {\n return err instanceof Error && (err.name === 'AbortError' || (err as NodeJS.ErrnoException).code === 'ABORT_ERR');\n}\n\n/** Parses a 1-based menu answer into a zero-based index, or undefined when it isn't a listed number. */\nfunction parseChoice(answer: string, max: number): number | undefined {\n if (!/^\\d+$/.test(answer)) return undefined; // strict: \"6, 14\" / \"3x\" / \"0x2\" are answers we must reject, not coerce\n const n = Number(answer);\n return n >= 1 && n <= max ? n - 1 : undefined;\n}\n\nconst SELECTION_HELP = 'enter listed numbers (\"2\", \"1 3\", \"2-5\"), \"all\", \"none\", or press Enter to confirm';\n\n/**\n * Parses a multi-select answer: whitespace/comma-separated numbers and `a-b` ranges, in any mix.\n * Every token that isn't a listed number is returned in `invalid` so the caller can SAY SO —\n * silently ignoring an unparsed answer is what made a typed selection look like it was accepted\n * when it wasn't.\n */\nfunction parseSelection(answer: string, max: number): { indices: number[]; invalid: string[] } {\n const indices: number[] = [];\n const invalid: string[] = [];\n for (const token of answer.split(/[\\s,]+/).filter(Boolean)) {\n const range = /^(\\d+)-(\\d+)$/.exec(token);\n if (range) {\n const from = Number(range[1]);\n const to = Number(range[2]);\n if (from >= 1 && to >= from && to <= max) {\n for (let i = from; i <= to; i++) indices.push(i - 1);\n } else {\n invalid.push(token);\n }\n continue;\n }\n const idx = parseChoice(token, max);\n if (idx === undefined) invalid.push(token);\n else indices.push(idx);\n }\n return { indices, invalid };\n}\n\n/**\n * Shared toggle loop for the two multi-selects. Everything is pre-selected; each answer toggles\n * the numbers it names, `all`/`none` set the whole set at once, and Enter confirms.\n */\nasync function promptToggleSet<T>(\n rl: Interface,\n header: string,\n items: T[],\n render: (item: T, index: number, mark: string) => void,\n preselect: (item: T) => boolean = () => true,\n): Promise<T[]> {\n const chosen = new Set(items.map((_, i) => i).filter((i) => preselect(items[i])));\n for (;;) {\n console.log(`\\n${header}`);\n items.forEach((item, i) => render(item, i, chosen.has(i) ? '[x]' : '[ ]'));\n const answer = (await rl.question('> ')).trim();\n if (answer === '') break;\n\n const lowered = answer.toLowerCase();\n if (lowered === 'a' || lowered === 'all') {\n items.forEach((_, i) => chosen.add(i));\n continue;\n }\n if (lowered === 'n' || lowered === 'none') {\n chosen.clear();\n continue;\n }\n\n const { indices, invalid } = parseSelection(answer, items.length);\n for (const idx of indices) {\n if (chosen.has(idx)) chosen.delete(idx);\n else chosen.add(idx);\n }\n if (invalid.length > 0) {\n console.log(` (ignored: ${invalid.join(', ')} — ${SELECTION_HELP})`);\n }\n }\n return items.filter((_, i) => chosen.has(i));\n}\n\nfunction selectionHeader(kind: string, narrowed: boolean): string {\n const preset = narrowed\n ? `roots of agents found on PATH pre-selected; the rest are listed but off`\n : 'all detected entries pre-selected';\n return `${kind} (${preset} — ${SELECTION_HELP}):`;\n}\n\nasync function promptMultiSelect(rl: Interface, groups: RootGroup[], narrow: boolean): Promise<RootGroup[]> {\n return promptToggleSet(\n rl,\n selectionHeader('Gate targets', narrow),\n groups,\n (g, i, mark) => {\n console.log(` ${mark} ${i + 1}. ${g.root}`);\n console.log(` ${describeGroup(g.members, g.scopes)}`);\n },\n (g) => !narrow || hasInstalledMember(g),\n );\n}\n\n/** Same UX as `promptMultiSelect`, offering detected MCP config files as `mcpTargets` candidates. */\nasync function promptMcpMultiSelect(rl: Interface, groups: McpGroup[], narrow: boolean): Promise<McpGroup[]> {\n return promptToggleSet(\n rl,\n selectionHeader('MCP targets', narrow),\n groups,\n (g, i, mark) => {\n console.log(` ${mark} ${i + 1}. ${g.path}`);\n console.log(` ${describeGroup(g.members, g.scopes)}`);\n },\n (g) => !narrow || hasInstalledMember(g),\n );\n}\n\n/**\n * Asks for one listed number, RE-ASKING on anything unparseable. The previous implementation\n * fell back to option 1 on an unrecognized answer, so a mistyped or multi-value answer silently\n * wrote a default target the user never chose.\n */\nasync function promptSingleChoice(\n rl: Interface,\n prompt: string,\n max: number,\n onEmpty: 'default' | 'skip',\n): Promise<number | undefined> {\n for (;;) {\n const answer = (await rl.question(prompt)).trim();\n if (answer === '') return onEmpty === 'default' ? 0 : undefined;\n const idx = parseChoice(answer, max);\n if (idx !== undefined) return idx;\n console.log(` (not a listed choice — enter a single number from 1 to ${max}${onEmpty === 'skip' ? ', or press Enter to skip' : ''})`);\n }\n}\n\nasync function promptDefaultTarget(rl: Interface, selected: RootGroup[]): Promise<RootGroup> {\n console.log('\\nDefault promotion target:');\n selected.forEach((g, i) => {\n console.log(` ${i + 1}. ${g.root}`);\n console.log(` ${describeGroup(g.members, g.scopes)}`);\n });\n const idx = await promptSingleChoice(rl, `Choose [1-${selected.length}] (default 1): `, selected.length, 'default');\n return selected[idx ?? 0];\n}\n\nasync function promptHandoffAgent(rl: Interface, detected: DetectedAgent[], narrow: boolean): Promise<Agent | undefined> {\n // Deliberately NOT grouped by root: each entry here is a distinct CLI binary, so two agents\n // sharing a skills root are still two different handoff choices. Narrowing only REORDERS —\n // agents whose CLI was found on PATH float to the top, and everything detected stays reachable,\n // because a handoff command for an agent we have no verified binary name for is still a valid\n // thing to want.\n const ordered = narrow\n ? [...detected].sort((a, b) => Number(b.presence === 'installed') - Number(a.presence === 'installed'))\n : detected;\n\n console.log('\\nHandoff agent (used to hand off follow-up prompts after promotion; optional):');\n ordered.forEach((d, i) => {\n const note =\n d.presence === 'installed'\n ? ' — found on PATH'\n : d.agent.defaultHandoffTemplate\n ? ' (CLI not on PATH)'\n : ' (no confirmed CLI — best-effort)';\n console.log(` ${i + 1}. ${d.agent.displayName}${note}`);\n });\n const idx = await promptSingleChoice(rl, `Choose [1-${ordered.length}] or Enter to skip: `, ordered.length, 'skip');\n return idx === undefined ? undefined : ordered[idx].agent;\n}\n\n/**\n * `skill-forge init` — setup wizard. Probes all known agents (see src/agents.ts), lets the\n * user pick which detected skill roots become gate targets, a default promotion target, an\n * optional handoff agent, and which detected MCP config files become `mcpTargets`, then writes\n * the result via saveConfig.\n */\nexport async function init(options: InitOptions = {}, deps: InitDeps = {}): Promise<void> {\n const runAudit = deps.runAudit ?? auditCommand;\n const cwd = options.cwd ?? process.cwd();\n const home = options.home ?? homedir();\n const detected = detectAgents(cwd, home, options.pathEnv);\n const detectedMcp = detectMcpTargets(cwd, home, options.pathEnv);\n\n if (options.list) {\n printDetectionResults(detected);\n printMcpDetectionResults(detectedMcp);\n return;\n }\n\n if (detected.length === 0 && detectedMcp.length === 0) {\n console.log('[skill-forge] No known agent skill directories or MCP config files were found.');\n console.log(' Install a coding agent (Claude Code, Codex, Cursor, ...) or create a skills root, then re-run `skill-forge init`.');\n return;\n }\n\n const rootGroups = groupByRoot(detected);\n const mcpGroups = groupMcpByPath(detectedMcp);\n\n // v0.11 narrowing: a skills directory existing proves nothing about which of the (up to 18)\n // agents sharing it you actually use, so the default selection is the roots where at least one\n // agent's CLI is on PATH. Disabled by --all-agents, and self-disabling when it would select\n // nothing at all (no verified CLI anywhere on this machine) — narrowing must never turn a\n // working detection into an empty config.\n const narrowable = rootGroups.some(hasInstalledMember) || mcpGroups.some(hasInstalledMember);\n const narrow = !options.allAgents && narrowable;\n if (!options.allAgents && !narrowable && (rootGroups.length > 0 || mcpGroups.length > 0)) {\n console.log(\n '[skill-forge] No detected agent CLI was found on PATH — keeping every detected target selected.',\n );\n }\n\n if (options.defaults) {\n const selectedRoots = narrow ? rootGroups.filter(hasInstalledMember) : rootGroups;\n const selectedMcpTargets = narrow ? mcpGroups.filter(hasInstalledMember) : mcpGroups;\n const config = buildConfig(selectedRoots, selectedRoots[0], undefined, selectedMcpTargets);\n saveConfig(config);\n printSummary(config);\n // Non-interactive final phase (v0.8): report written, no profile Q&A, no foundation, no handoff.\n await runAudit({ yes: true });\n return;\n }\n\n if (!process.stdin.isTTY) {\n console.log('[skill-forge] Not an interactive terminal — run with --defaults for non-interactive setup.');\n return;\n }\n\n printDetectionResults(detected);\n printMcpDetectionResults(detectedMcp);\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n let runAuditNow = false;\n try {\n const selected = rootGroups.length > 0 ? await promptMultiSelect(rl, rootGroups, narrow) : [];\n const selectedMcp = mcpGroups.length > 0 ? await promptMcpMultiSelect(rl, mcpGroups, narrow) : [];\n if (selected.length === 0 && selectedMcp.length === 0) {\n console.log('[skill-forge] No targets selected — aborting, nothing written.');\n return;\n }\n const target = selected.length > 0 ? await promptDefaultTarget(rl, selected) : undefined;\n const handoffAgent = detected.length > 0 ? await promptHandoffAgent(rl, detected, narrow) : undefined;\n const config = buildConfig(selected, target, handoffAgent, selectedMcp);\n saveConfig(config);\n printSummary(config);\n\n if (options.offerAudit !== false) {\n const answer = (await rl.question('\\nRun the skills & MCP audit now? [Y/n] ')).trim().toLowerCase();\n runAuditNow = answer === '' || answer === 'y' || answer === 'yes';\n }\n } catch (err) {\n if (!isPromptAbort(err)) throw err;\n // Ctrl+D / closed stdin at any prompt: a cancel, not a crash. Anything already saved before\n // this point (the config write happens above) stays written and was reported by printSummary.\n console.log('\\n[skill-forge] init cancelled.');\n return;\n } finally {\n // Closed BEFORE any audit run below — auditCommand's own interactive prompts open a fresh\n // readline interface over the same stdin, and two concurrent interfaces would race on it.\n rl.close();\n }\n\n if (runAuditNow) {\n await runAudit();\n }\n}\n\nexport interface OfferInitOptions {\n json?: boolean;\n yes?: boolean;\n /**\n * Set by `auditCommand`'s own call (v0.8 fix): if the user accepts this offer, the `init` run\n * it launches must not ALSO offer/run its own tail audit — `auditCommand` is already the\n * single audit run that will happen once `init()` returns here.\n */\n suppressAuditOffer?: boolean;\n}\n\n/**\n * Prints a one-line offer to run `skill-forge init` when no config.json exists yet, and (in an\n * interactive terminal) asks whether to run it now. Never fires for automation: no-ops entirely\n * when stdin isn't a TTY, or `--json`/`--yes` was passed, so scripted invocations never see this\n * output or block on a prompt. Called at startup by add/scan/list/status.\n */\nexport async function maybeOfferInit(options: OfferInitOptions = {}): Promise<void> {\n if (options.json || options.yes || !process.stdin.isTTY) return;\n if (existsSync(getConfigPath())) return;\n\n console.log(\n '[skill-forge] No config found — run `skill-forge init` to detect your agents and set gate targets ' +\n '(init finishes with a `skill-forge audit` health check over what it just configured).'\n );\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n let answer = '';\n try {\n answer = (await rl.question('Run `skill-forge init` now? [y/N] ')).trim().toLowerCase();\n } finally {\n rl.close();\n }\n if (answer === 'y' || answer === 'yes') {\n await init(options.suppressAuditOffer ? { offerAudit: false } : {});\n }\n}\n","import { statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { HANDOFF_INSTRUCTION } from './handoff.js';\nimport { isDir, resolveBinaryOnPath } from './util.js';\n\n/** True if `p` exists and is a regular file (false on any stat error). Mirrors util.ts's isDir. */\nfunction isFile(p: string): boolean {\n try {\n return statSync(p).isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * A known agent-skill ecosystem: where it looks for skills, and (optionally) how to hand a\n * prompt off to it once a skill has been promoted.\n *\n * Path data (`projectRoot` / `globalRoot`) is verified against the vercel-labs/skills CLI's own\n * source of truth, `src/agents.ts`\n * (raw.githubusercontent.com/vercel-labs/skills/main/src/agents.ts, cross-checked via the\n * GitHub contents API 2026-07-11) — not just its README, which can drift from the code. All 73\n * entries below were diffed programmatically against that file and matched exactly (id,\n * displayName, skillsDir, and the default/no-env-override form of globalSkillsDir). A handful of\n * agents resolve their global root via an overridable env var upstream (e.g. Amp/OpenCode/Goose/\n * Devin/Universal/Replit via `XDG_CONFIG_HOME`, Codex via `CODEX_HOME`, Claude Code via\n * `CLAUDE_CONFIG_DIR`) — the tilde path here is upstream's default when that var is unset.\n * Entries marked `verified: false` could not be confirmed against that source and should be\n * treated as best-effort, never invented.\n */\n/**\n * Where an agent reads its MCP server configuration from, if known (added in v0.5 for MCP\n * gating). Each entry is verified against that agent's official docs/source — see the citation\n * comment above the entry in AGENTS below — and `mcpConfig` is left absent entirely on agents we\n * couldn't confirm a path for; never invented.\n */\nexport interface McpConfigLocation {\n /** Project-relative MCP config file path (relative to cwd), e.g. `.mcp.json`. */\n project?: string;\n /** User-home MCP config file path, `~`-relative, e.g. `~/.claude.json`. */\n global?: string;\n /**\n * File format when it isn't plain JSON with a top-level `mcpServers` key. v0.5's MCP promote\n * only WRITES json-shaped targets (see the v0.5 contract) — `format: 'toml'` targets are\n * detect/report-only.\n */\n format?: 'toml';\n /** True once the path(s) above were confirmed against this agent's official docs (cited in a comment on this entry). */\n verified: boolean;\n}\n\nexport interface Agent {\n id: string;\n displayName: string;\n /** Project-relative skills root (relative to cwd), e.g. `.claude/skills`. */\n projectRoot?: string;\n /** User-home skills root, `~`-relative, e.g. `~/.claude/skills`. Absent = no global install path (project-only agent). */\n globalRoot?: string;\n /** False if this entry's paths are inferred rather than confirmed against the vercel-labs/skills source — never invented, always disclosed. */\n verified: boolean;\n /**\n * Best-effort argv template for handing a prompt to this agent's CLI, e.g.\n * `['claude', '-p', '{prompt}']`. Substituted by `src/handoff.ts` — never shell-parsed, so\n * each element is a literal argv token. Undefined where we don't have a confirmed CLI binary\n * name (IDE-only agents, or agents we couldn't verify an invocation for) — callers should\n * fall back to something explicit rather than guess.\n */\n defaultHandoffTemplate?: string[];\n /**\n * Command name(s) this agent installs on PATH, e.g. `['claude']`. Used by `skill-forge init`\n * (v0.11) to pre-select only the skills roots of agents that are actually installed — 18 of the\n * entries below share `.agents/skills`, so a directory existing proves nothing about which of\n * them you use.\n *\n * Held to the same evidence bar as `projectRoot`/`globalRoot`: populated ONLY where the binary\n * name is confirmed (today, exactly the agents with a `defaultHandoffTemplate`, whose argv[0] IS\n * that binary — cited on each entry). Absent means \"unknown\", never \"not installed\", and init\n * says so rather than hiding the root. Guessing here would be worse than leaving it out: many\n * plausible ids (`pi`, `bob`, `eve`, `mux`, `continue`, `crush`) collide with unrelated\n * executables, so a wrong guess would report an agent as installed on the strength of someone\n * else's binary.\n */\n cliBinaries?: string[];\n /** Where this agent reads its MCP server config from. Absent when unverified — see McpConfigLocation. */\n mcpConfig?: McpConfigLocation;\n /**\n * True if either the project or global skills root already exists on disk — i.e. this agent\n * looks installed. `cwd`/`home` default to the real process cwd / home dir; pass overrides in\n * tests to probe against temp dirs instead of the real filesystem.\n */\n detect(cwd?: string, home?: string): boolean;\n}\n\ninterface AgentSpec {\n id: string;\n displayName: string;\n projectRoot?: string;\n globalRoot?: string;\n verified?: boolean;\n defaultHandoffTemplate?: string[];\n cliBinaries?: string[];\n mcpConfig?: McpConfigLocation;\n}\n\n/** Resolves a project-relative skills root against `cwd`, or undefined if the agent has none. */\nfunction projectPath(root: string | undefined, cwd: string): string | undefined {\n return root ? resolve(cwd, root) : undefined;\n}\n\n/** Resolves a `~`-relative global skills root against `home`, or undefined if the agent has none. */\nfunction globalPath(root: string | undefined, home: string): string | undefined {\n if (!root) return undefined;\n if (root === '~') return home;\n if (root.startsWith('~/')) return join(home, root.slice(2));\n return root;\n}\n\nfunction makeAgent(spec: AgentSpec): Agent {\n const agent: Agent = {\n id: spec.id,\n displayName: spec.displayName,\n projectRoot: spec.projectRoot,\n globalRoot: spec.globalRoot,\n verified: spec.verified ?? true,\n defaultHandoffTemplate: spec.defaultHandoffTemplate,\n cliBinaries: spec.cliBinaries,\n mcpConfig: spec.mcpConfig,\n detect: (cwd?: string, home?: string): boolean => detectAgentRoot(agent, cwd, home) !== undefined,\n };\n return agent;\n}\n\n/**\n * Resolves the absolute filesystem path for one of an agent's roots, without probing for\n * existence. `cwd`/`home` default to the real process cwd / home dir.\n */\nexport function resolveAgentRoot(\n agent: Agent,\n scope: 'project' | 'global',\n cwd: string = process.cwd(),\n home: string = homedir(),\n): string | undefined {\n return scope === 'project' ? projectPath(agent.projectRoot, cwd) : globalPath(agent.globalRoot, home);\n}\n\n/**\n * Whether this agent's CLI could be found on PATH — the signal `skill-forge init` narrows its\n * default selection with. Three-state on purpose:\n *\n * - `'installed'` — a `cliBinaries` name resolved to an executable on PATH.\n * - `'absent'` — we know this agent's binary name and it is NOT on PATH.\n * - `'unknown'` — no verified binary name for this agent (see `Agent#cliBinaries`), so PATH\n * proves nothing either way. Never treated as `'absent'`.\n *\n * Look-up only — `resolveBinaryOnPath` never executes what it finds.\n */\nexport type CliPresence = 'installed' | 'absent' | 'unknown';\n\nexport function agentCliPresence(agent: Agent, pathEnv: string | undefined = process.env.PATH): CliPresence {\n if (!agent.cliBinaries || agent.cliBinaries.length === 0) return 'unknown';\n return agent.cliBinaries.some((bin) => resolveBinaryOnPath(bin, pathEnv) !== undefined) ? 'installed' : 'absent';\n}\n\n/** A skills root that exists on disk for an agent: which scope matched, and its absolute path. */\nexport interface DetectedRoot {\n scope: 'project' | 'global';\n root: string;\n}\n\n/**\n * Probes an agent's skills roots on disk — project first, then global — and returns the first\n * that exists, or undefined when neither does. Single source of truth for agent detection:\n * `Agent#detect` and `skill-forge init` both resolve through it. `cwd`/`home` default to the\n * real process cwd / home dir; pass overrides in tests to probe temp dirs instead.\n */\nexport function detectAgentRoot(\n agent: Agent,\n cwd: string = process.cwd(),\n home: string = homedir(),\n): DetectedRoot | undefined {\n for (const scope of ['project', 'global'] as const) {\n const root = resolveAgentRoot(agent, scope, cwd, home);\n if (root && isDir(root)) return { scope, root };\n }\n return undefined;\n}\n\n/**\n * Resolves the absolute filesystem path for one of an agent's MCP config files, without probing\n * for existence. Returns undefined when the agent has no `mcpConfig`, or no path for that scope.\n * `cwd`/`home` default to the real process cwd / home dir.\n */\nexport function resolveMcpConfigPath(\n agent: Agent,\n scope: 'project' | 'global',\n cwd: string = process.cwd(),\n home: string = homedir(),\n): string | undefined {\n if (!agent.mcpConfig) return undefined;\n return scope === 'project' ? projectPath(agent.mcpConfig.project, cwd) : globalPath(agent.mcpConfig.global, home);\n}\n\n/** An MCP config file that exists on disk for an agent: which scope it is, and its absolute path. */\nexport interface DetectedMcpConfig {\n scope: 'project' | 'global';\n path: string;\n}\n\n/**\n * Probes an agent's MCP config file locations on disk and returns every one that exists as a\n * file. Unlike `detectAgentRoot` (which returns only the first matching skills root), both\n * project and global MCP config files can legitimately coexist and both matter as promotion\n * targets — e.g. a project `.mcp.json` and a user-scope `~/.claude.json` — so this returns all\n * matches, not just the first. `cwd`/`home` default to the real process cwd / home dir.\n */\nexport function detectMcpConfigFiles(\n agent: Agent,\n cwd: string = process.cwd(),\n home: string = homedir(),\n): DetectedMcpConfig[] {\n if (!agent.mcpConfig) return [];\n const found: DetectedMcpConfig[] = [];\n for (const scope of ['project', 'global'] as const) {\n const path = resolveMcpConfigPath(agent, scope, cwd, home);\n if (path && isFile(path)) found.push({ scope, path });\n }\n return found;\n}\n\n/** Full agent matrix, in the order the vercel-labs/skills README lists them. */\nexport const AGENTS: Agent[] = (\n [\n { id: 'aider-desk', displayName: 'AiderDesk', projectRoot: '.aider-desk/skills', globalRoot: '~/.aider-desk/skills' },\n { id: 'amp', displayName: 'Amp', projectRoot: '.agents/skills', globalRoot: '~/.config/agents/skills' },\n { id: 'replit', displayName: 'Replit', projectRoot: '.agents/skills', globalRoot: '~/.config/agents/skills' },\n { id: 'universal', displayName: 'Universal', projectRoot: '.agents/skills', globalRoot: '~/.config/agents/skills' },\n { id: 'antigravity', displayName: 'Antigravity', projectRoot: '.agents/skills', globalRoot: '~/.gemini/antigravity/skills' },\n {\n id: 'antigravity-cli',\n displayName: 'Antigravity CLI',\n projectRoot: '.agents/skills',\n globalRoot: '~/.gemini/antigravity-cli/skills',\n },\n { id: 'astrbot', displayName: 'AstrBot', projectRoot: 'data/skills', globalRoot: '~/.astrbot/data/skills' },\n { id: 'autohand-code', displayName: 'Autohand Code CLI', projectRoot: '.autohand/skills', globalRoot: '~/.autohand/skills' },\n { id: 'augment', displayName: 'Augment', projectRoot: '.augment/skills', globalRoot: '~/.augment/skills' },\n { id: 'bob', displayName: 'IBM Bob', projectRoot: '.bob/skills', globalRoot: '~/.bob/skills' },\n {\n id: 'claude-code',\n displayName: 'Claude Code',\n projectRoot: '.claude/skills',\n globalRoot: '~/.claude/skills',\n // `claude -p \"<prompt>\"` runs one non-interactive turn and exits. Verified 2026-07-11:\n // https://code.claude.com/docs/en/headless\n defaultHandoffTemplate: ['claude', '-p', HANDOFF_INSTRUCTION],\n cliBinaries: ['claude'],\n // Project scope writes to `.mcp.json` at the project root (shared via version control).\n // User scope writes to a top-level `mcpServers` key in `~/.claude.json` (not exclusively\n // an MCP file — it's Claude Code's broader state file — but it's the correct read/write\n // target for user-scoped servers). Verified 2026-07-12: https://code.claude.com/docs/en/mcp\n mcpConfig: { project: '.mcp.json', global: '~/.claude.json', verified: true },\n },\n { id: 'openclaw', displayName: 'OpenClaw', projectRoot: 'skills', globalRoot: '~/.openclaw/skills' },\n { id: 'cline', displayName: 'Cline', projectRoot: '.agents/skills', globalRoot: '~/.agents/skills' },\n { id: 'dexto', displayName: 'Dexto', projectRoot: '.agents/skills', globalRoot: '~/.agents/skills' },\n { id: 'kimi-code-cli', displayName: 'Kimi Code CLI', projectRoot: '.agents/skills', globalRoot: '~/.agents/skills' },\n { id: 'loaf', displayName: 'Loaf', projectRoot: '.agents/skills', globalRoot: '~/.agents/skills' },\n { id: 'warp', displayName: 'Warp', projectRoot: '.agents/skills', globalRoot: '~/.agents/skills' },\n { id: 'zed', displayName: 'Zed', projectRoot: '.agents/skills', globalRoot: '~/.agents/skills' },\n {\n id: 'codearts-agent',\n displayName: 'CodeArts Agent',\n projectRoot: '.codeartsdoer/skills',\n globalRoot: '~/.codeartsdoer/skills',\n },\n { id: 'codebuddy', displayName: 'CodeBuddy', projectRoot: '.codebuddy/skills', globalRoot: '~/.codebuddy/skills' },\n { id: 'codemaker', displayName: 'Codemaker', projectRoot: '.codemaker/skills', globalRoot: '~/.codemaker/skills' },\n { id: 'codestudio', displayName: 'Code Studio', projectRoot: '.codestudio/skills', globalRoot: '~/.codestudio/skills' },\n {\n id: 'codex',\n displayName: 'Codex',\n projectRoot: '.agents/skills',\n globalRoot: '~/.codex/skills',\n // `codex exec \"<prompt>\"` runs one non-interactive task and prints the final message.\n // Verified 2026-07-11: https://developers.openai.com/codex/noninteractive\n defaultHandoffTemplate: ['codex', 'exec', HANDOFF_INSTRUCTION],\n cliBinaries: ['codex'],\n // TOML, not JSON: `[mcp_servers.<name>]` tables. User config at `~/.codex/config.toml`;\n // project config at `.codex/config.toml` (only honored for trusted projects). Verified\n // 2026-07-12: https://learn.chatgpt.com/docs/extend/mcp?surface=cli — detect/report-only\n // per the v0.5 contract (promote only writes JSON targets).\n mcpConfig: { project: '.codex/config.toml', global: '~/.codex/config.toml', format: 'toml', verified: true },\n },\n {\n id: 'command-code',\n displayName: 'Command Code',\n projectRoot: '.commandcode/skills',\n globalRoot: '~/.commandcode/skills',\n },\n { id: 'continue', displayName: 'Continue', projectRoot: '.continue/skills', globalRoot: '~/.continue/skills' },\n { id: 'cortex', displayName: 'Cortex Code', projectRoot: '.cortex/skills', globalRoot: '~/.snowflake/cortex/skills' },\n { id: 'crush', displayName: 'Crush', projectRoot: '.crush/skills', globalRoot: '~/.config/crush/skills' },\n {\n id: 'cursor',\n displayName: 'Cursor',\n projectRoot: '.agents/skills',\n globalRoot: '~/.cursor/skills',\n // Both scopes use a top-level `mcpServers` key. Verified 2026-07-12:\n // https://cursor.com/docs/context/mcp (\"Create `.cursor/mcp.json` in your project ...\";\n // \"Create `~/.cursor/mcp.json` in your home directory ...\").\n mcpConfig: { project: '.cursor/mcp.json', global: '~/.cursor/mcp.json', verified: true },\n },\n {\n id: 'deepagents',\n displayName: 'Deep Agents',\n projectRoot: '.agents/skills',\n globalRoot: '~/.deepagents/agent/skills',\n },\n { id: 'devin', displayName: 'Devin for Terminal', projectRoot: '.devin/skills', globalRoot: '~/.config/devin/skills' },\n { id: 'droid', displayName: 'Droid', projectRoot: '.factory/skills', globalRoot: '~/.factory/skills' },\n { id: 'eve', displayName: 'Eve', projectRoot: 'agent/skills' },\n { id: 'firebender', displayName: 'Firebender', projectRoot: '.agents/skills', globalRoot: '~/.firebender/skills' },\n { id: 'forgecode', displayName: 'ForgeCode', projectRoot: '.forge/skills', globalRoot: '~/.forge/skills' },\n {\n id: 'gemini-cli',\n displayName: 'Gemini CLI',\n projectRoot: '.agents/skills',\n globalRoot: '~/.gemini/skills',\n // `gemini -p \"<prompt>\"` executes a single turn headlessly and exits. Verified 2026-07-11:\n // https://google-gemini.github.io/gemini-cli/docs/cli/headless.html\n defaultHandoffTemplate: ['gemini', '-p', HANDOFF_INSTRUCTION],\n cliBinaries: ['gemini'],\n // `mcpServers` key inside `settings.json` (shared with other CLI settings, not an\n // MCP-exclusive file, but the correct read/write target). User config at\n // `~/.gemini/settings.json`, project config at `.gemini/settings.json`. Verified\n // 2026-07-12: https://google-gemini.github.io/gemini-cli/docs/tools/mcp-server.html\n mcpConfig: { project: '.gemini/settings.json', global: '~/.gemini/settings.json', verified: true },\n },\n {\n id: 'github-copilot',\n displayName: 'GitHub Copilot',\n projectRoot: '.agents/skills',\n globalRoot: '~/.copilot/skills',\n // Copilot CLI (matches the `~/.copilot` skills convention above, not the VS Code\n // extension — which uses a different `servers`-keyed `.vscode/mcp.json` and isn't covered\n // by this entry). User config `~/.copilot/mcp-config.json` (`mcpServers` key), repo-level\n // config `.github/mcp.json`. Verified 2026-07-12:\n // https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers\n mcpConfig: { project: '.github/mcp.json', global: '~/.copilot/mcp-config.json', verified: true },\n },\n { id: 'goose', displayName: 'Goose', projectRoot: '.goose/skills', globalRoot: '~/.config/goose/skills' },\n { id: 'hermes-agent', displayName: 'Hermes Agent', projectRoot: '.hermes/skills', globalRoot: '~/.hermes/skills' },\n {\n id: 'inference-sh',\n displayName: 'inference.sh',\n projectRoot: '.inferencesh/skills',\n globalRoot: '~/.inferencesh/skills',\n },\n { id: 'jazz', displayName: 'Jazz', projectRoot: '.jazz/skills', globalRoot: '~/.jazz/skills' },\n { id: 'junie', displayName: 'Junie', projectRoot: '.junie/skills', globalRoot: '~/.junie/skills' },\n { id: 'iflow-cli', displayName: 'iFlow CLI', projectRoot: '.iflow/skills', globalRoot: '~/.iflow/skills' },\n { id: 'kilo', displayName: 'Kilo Code', projectRoot: '.kilocode/skills', globalRoot: '~/.kilocode/skills' },\n { id: 'kiro-cli', displayName: 'Kiro CLI', projectRoot: '.kiro/skills', globalRoot: '~/.kiro/skills' },\n { id: 'kode', displayName: 'Kode', projectRoot: '.kode/skills', globalRoot: '~/.kode/skills' },\n { id: 'lingma', displayName: 'Lingma', projectRoot: '.lingma/skills', globalRoot: '~/.lingma/skills' },\n { id: 'mcpjam', displayName: 'MCPJam', projectRoot: '.mcpjam/skills', globalRoot: '~/.mcpjam/skills' },\n { id: 'mistral-vibe', displayName: 'Mistral Vibe', projectRoot: '.vibe/skills', globalRoot: '~/.vibe/skills' },\n { id: 'moxby', displayName: 'Moxby', projectRoot: '.moxby/skills', globalRoot: '~/.moxby/skills' },\n { id: 'mux', displayName: 'Mux', projectRoot: '.mux/skills', globalRoot: '~/.mux/skills' },\n {\n id: 'opencode',\n displayName: 'OpenCode',\n projectRoot: '.agents/skills',\n globalRoot: '~/.config/opencode/skills',\n // `opencode run \"<prompt>\"` runs one non-interactive prompt outside the TUI.\n // Verified 2026-07-11: https://opencode.ai/docs/cli/\n defaultHandoffTemplate: ['opencode', 'run', HANDOFF_INSTRUCTION],\n cliBinaries: ['opencode'],\n },\n { id: 'openhands', displayName: 'OpenHands', projectRoot: '.openhands/skills', globalRoot: '~/.openhands/skills' },\n { id: 'ona', displayName: 'Ona', projectRoot: '.ona/skills', globalRoot: '~/.ona/skills' },\n { id: 'pi', displayName: 'Pi', projectRoot: '.pi/skills', globalRoot: '~/.pi/agent/skills' },\n { id: 'qoder', displayName: 'Qoder', projectRoot: '.qoder/skills', globalRoot: '~/.qoder/skills' },\n { id: 'qoder-cn', displayName: 'Qoder CN', projectRoot: '.qoder/skills', globalRoot: '~/.qoder-cn/skills' },\n { id: 'qwen-code', displayName: 'Qwen Code', projectRoot: '.qwen/skills', globalRoot: '~/.qwen/skills' },\n { id: 'reasonix', displayName: 'Reasonix', projectRoot: '.reasonix/skills', globalRoot: '~/.reasonix/skills' },\n { id: 'rovodev', displayName: 'Rovo Dev', projectRoot: '.rovodev/skills', globalRoot: '~/.rovodev/skills' },\n { id: 'roo', displayName: 'Roo Code', projectRoot: '.roo/skills', globalRoot: '~/.roo/skills' },\n {\n id: 'tabnine-cli',\n displayName: 'Tabnine CLI',\n projectRoot: '.tabnine/agent/skills',\n globalRoot: '~/.tabnine/agent/skills',\n },\n { id: 'terramind', displayName: 'Terramind', projectRoot: '.terramind/skills', globalRoot: '~/.terramind/skills' },\n { id: 'tinycloud', displayName: 'Tinycloud', projectRoot: '.tinycloud/skills', globalRoot: '~/.tinycloud/skills' },\n { id: 'trae', displayName: 'Trae', projectRoot: '.trae/skills', globalRoot: '~/.trae/skills' },\n { id: 'trae-cn', displayName: 'Trae CN', projectRoot: '.trae/skills', globalRoot: '~/.trae-cn/skills' },\n {\n id: 'windsurf',\n displayName: 'Windsurf',\n projectRoot: '.windsurf/skills',\n globalRoot: '~/.codeium/windsurf/skills',\n // `mcpServers` key; global only — no project-scoped MCP config is documented. Verified\n // 2026-07-12: https://docs.windsurf.com/windsurf/cascade/mcp\n mcpConfig: { global: '~/.codeium/windsurf/mcp_config.json', verified: true },\n },\n { id: 'zcode', displayName: 'ZCode', projectRoot: '.zcode/skills', globalRoot: '~/.zcode/skills' },\n { id: 'zencoder', displayName: 'Zencoder', projectRoot: '.zencoder/skills', globalRoot: '~/.zencoder/skills' },\n { id: 'zenflow', displayName: 'Zenflow', projectRoot: '.zencoder/skills', globalRoot: '~/.zencoder/skills' },\n { id: 'neovate', displayName: 'Neovate', projectRoot: '.neovate/skills', globalRoot: '~/.neovate/skills' },\n { id: 'pochi', displayName: 'Pochi', projectRoot: '.pochi/skills', globalRoot: '~/.pochi/skills' },\n { id: 'promptscript', displayName: 'PromptScript', projectRoot: '.agents/skills' },\n { id: 'adal', displayName: 'AdaL', projectRoot: '.adal/skills', globalRoot: '~/.adal/skills' },\n ] satisfies AgentSpec[]\n).map(makeAgent);\n\n/** Looks up a known agent by its `--agent` id (e.g. `claude-code`). */\nexport function getAgent(id: string): Agent | undefined {\n return AGENTS.find((a) => a.id === id);\n}\n","import { existsSync } from 'node:fs';\nimport { writeNewFile } from '../util.js';\nimport { dirname, join, resolve as resolvePath } from 'node:path';\nimport { createInterface, type Interface } from 'node:readline/promises';\nimport { auditConfiguredSet, renderAuditMarkdown, scaffoldFoundationSkill, type AuditReport } from '../audit.js';\nimport { summarizeAudit, writeAuditState } from '../auditState.js';\nimport { getConfigPath, getHomeDir, loadConfig, saveConfig, type BusinessProfile, type Config } from '../config.js';\nimport { logOutcome } from '../decision.js';\nimport { UserError } from '../errors.js';\nimport { readAcceptedFindings } from '../findings.js';\nimport { runHandoff } from '../handoff.js';\nimport { maybeOfferInit } from './init.js';\n\n/** Bundled agent-side curation prompt handed off by `--handoff` — see assets/curation-prompt.md and src/handoff.ts's `resolvePromptPath`. */\nconst CURATION_PROMPT_ASSET = 'curation-prompt.md';\n\nexport interface AuditCommandOptions {\n json?: boolean;\n yes?: boolean;\n /** Overrides the default `~/.skill-forge/reports/audit-<ISO-ts>.md` path. */\n report?: string;\n /** Scaffold a business-foundation skill after the audit — same effect as answering yes to the interactive prompt. */\n foundation?: boolean;\n /** Hand the report off to a coding agent after the audit — same effect as answering yes to the interactive prompt. */\n handoff?: boolean;\n}\n\nfunction splitCsv(s: string): string[] {\n return s\n .split(',')\n .map((x) => x.trim())\n .filter(Boolean);\n}\n\n/** Asks `prompt` and returns the trimmed, lowercased answer — the mechanical part shared by this file's four y/n prompts; each call site keeps its own exact comparison/default logic. */\nasync function askAnswer(rl: Interface, prompt: string): Promise<string> {\n return (await rl.question(prompt)).trim().toLowerCase();\n}\n\n/**\n * Interactive business-profile capture — offers to reuse an existing stored profile (never\n * re-invents one silently), warns against entering credentials/confidential data before asking\n * anything, and shows a summarized confirm before returning. Returns `existing` unchanged if the\n * user declines to reuse-or-replace it (including on a fresh capture the user doesn't confirm).\n */\nasync function captureBusinessProfile(rl: Interface, existing: BusinessProfile | undefined): Promise<BusinessProfile | undefined> {\n console.log('\\n[skill-forge] Business profile — grounds the business-foundation skill scaffold and the curation handoff prompt.');\n console.log(\n '[skill-forge] Do not enter credentials, API keys, tokens, or confidential customer data — this is stored in plain text in config.json.'\n );\n\n if (existing) {\n const keep = await askAnswer(rl, `Reuse the existing business profile (captured ${existing.updatedAt})? [Y/n] `);\n if (keep === '' || keep === 'y' || keep === 'yes') return existing;\n }\n\n const businessName = (await rl.question('Business name (optional, Enter to skip): ')).trim() || undefined;\n const industry = (await rl.question('Industry (optional, Enter to skip): ')).trim() || undefined;\n const audiences = splitCsv(await rl.question('Audiences (comma-separated, optional): '));\n const workflows = splitCsv(await rl.question('Workflows (comma-separated, optional): '));\n const constraints = splitCsv(await rl.question('Constraints (comma-separated, optional): '));\n\n const profile: BusinessProfile = { businessName, industry, audiences, workflows, constraints, updatedAt: new Date().toISOString() };\n\n console.log('\\n[skill-forge] Business profile summary:');\n console.log(` Business name: ${profile.businessName ?? '(none)'}`);\n console.log(` Industry: ${profile.industry ?? '(none)'}`);\n console.log(` Audiences: ${profile.audiences.join(', ') || '(none)'}`);\n console.log(` Workflows: ${profile.workflows.join(', ') || '(none)'}`);\n console.log(` Constraints: ${profile.constraints.join(', ') || '(none)'}`);\n const confirm = await askAnswer(rl, 'Save this business profile? [Y/n] ');\n if (confirm === 'n' || confirm === 'no') return existing;\n\n return profile;\n}\n\nfunction resolveReportPath(reportOption: string | undefined): string {\n if (reportOption) return resolvePath(reportOption);\n const iso = new Date().toISOString().replace(/[:.]/g, '-');\n return join(getHomeDir(), 'reports', `audit-${iso}.md`);\n}\n\n\nfunction printTerminalSummary(report: AuditReport, reportPath: string): void {\n const { summary } = report;\n console.log('[skill-forge] audit complete');\n console.log(` skill roots: ${summary.rootCount} skills: ${summary.skillCount} MCP targets: ${summary.mcpTargetCount}`);\n console.log(\n ` findings: ${summary.findingCounts.CRITICAL} CRITICAL, ${summary.findingCounts.HIGH} HIGH, ` +\n `${summary.findingCounts.MEDIUM} MEDIUM, ${summary.findingCounts.LOW} LOW`\n );\n console.log(` accepted: ${summary.acceptedCount} (suppressed from counts; see report)`);\n console.log(` overlap clusters: ${summary.overlapClusterCount} evolve-eligible: ${summary.evolveEligibleCount}`);\n console.log(` report -> ${reportPath}`);\n for (const notice of report.notices) console.log(` [notice] ${notice}`);\n console.log(\n ' next steps: `skill-forge add <source>` to install something new; ' +\n '`skill-forge evolve <skill-dir>` to refine an evolve-eligible skill.'\n );\n}\n\nasync function maybeScaffoldFoundation(\n config: Config,\n report: AuditReport,\n options: AuditCommandOptions,\n rl: Interface | undefined\n): Promise<Config> {\n let wantFoundation = options.foundation ?? false;\n\n if (!wantFoundation && rl && config.businessProfile && report.opportunities.foundation.available) {\n const answer = await askAnswer(rl, '\\nCreate a business-foundation skill now? [y/N] ');\n wantFoundation = answer === 'y' || answer === 'yes';\n }\n if (!wantFoundation) return config;\n\n if (!config.businessProfile) {\n throw new UserError(\n '--foundation requires a business profile — run `skill-forge audit` interactively first to capture one.'\n );\n }\n\n const targetRoot = config.defaultTarget ?? config.skillsRoots[0];\n if (!targetRoot) {\n throw new UserError('cannot scaffold a foundation skill: no skills root configured.');\n }\n\n const foundationPath = await scaffoldFoundationSkill(config, config.businessProfile, targetRoot);\n const updated: Config = { ...config, foundationSkillPath: foundationPath };\n saveConfig(updated);\n logOutcome(`[skill-forge] scaffolded business-foundation skill -> ${foundationPath}`, options.json);\n return updated;\n}\n\nasync function maybeHandoff(config: Config, reportPath: string, options: AuditCommandOptions, rl: Interface | undefined): Promise<void> {\n let wantHandoff = options.handoff ?? false;\n\n if (!wantHandoff && rl) {\n const answer = await askAnswer(rl, '\\nHand the curation report to a coding agent now? [y/N] ');\n wantHandoff = answer === 'y' || answer === 'yes';\n }\n if (!wantHandoff) return;\n\n // In --json mode, stdout must stay exactly one JSON document (already printed above) — route\n // runHandoff's own print-only messaging (no agent found / configured binary missing) to\n // stderr instead of its default stdout sink (v0.8 fix).\n runHandoff(config, reportPath, { promptAsset: CURATION_PROMPT_ASSET, log: options.json ? console.error : console.log });\n}\n\n/**\n * `skill-forge audit` (alias `doctor`) — a re-runnable, doctor-style health check over the\n * configured skill/MCP set: inventory, hygiene/safety findings, and consolidation/refinement\n * opportunities (src/audit.ts). Requires a real, persisted config — never silently audits an\n * invented default. `--foundation`/`--handoff` are separate, explicit opt-ins: `--yes`/`--json`\n * never imply either, matching the plan's \"agent handoff and foundation writes are opt-in\" hard\n * invariant.\n */\nexport async function auditCommand(options: AuditCommandOptions = {}): Promise<void> {\n // suppressAuditOffer: true — if this offers and runs `init`, init must NOT also offer/run its\n // own tail audit (that would run the whole interactive profile Q&A + report a second time);\n // this function is already the single audit run that happens once init returns (v0.8 fix).\n await maybeOfferInit({ json: options.json, yes: options.yes, suppressAuditOffer: true });\n\n if (!existsSync(getConfigPath())) {\n throw new UserError(\n 'no skill-forge config found — run `skill-forge init` first (audit requires a configured skill/MCP set; it never audits an invented default).'\n );\n }\n\n let config = loadConfig();\n const interactive = !options.json && !options.yes && Boolean(process.stdin.isTTY);\n\n const rl = interactive ? createInterface({ input: process.stdin, output: process.stdout }) : undefined;\n try {\n if (rl) {\n const profile = await captureBusinessProfile(rl, config.businessProfile);\n if (profile !== config.businessProfile) {\n config = { ...config, businessProfile: profile };\n saveConfig(config);\n }\n }\n\n // v0.12: load human-accepted findings and fold any store-read notices (missing/corrupt store,\n // dropped records) straight into the returned report — the engine itself stays pure (only\n // takes the already-loaded `records`, see AuditOptions.accepted's doc comment).\n const { records: accepted, notices: storeNotices } = readAcceptedFindings();\n const report = await auditConfiguredSet(config, { accepted });\n if (storeNotices.length > 0) report.notices = [...report.notices, ...storeNotices];\n\n const reportPath = resolveReportPath(options.report);\n writeNewFile(reportPath, renderAuditMarkdown(report), '--report');\n\n // v0.11: leave a machine-readable residue so add/scan/status/guide/routine can cite this run.\n // Advisory only — nothing derived from it may change a gate verdict (see src/auditState.ts).\n // A failure here must not lose the report that was just written successfully.\n try {\n writeAuditState(summarizeAudit(report, reportPath));\n } catch (err) {\n console.error(`[skill-forge] wrote ${reportPath}, but recording audit state failed: ${(err as Error).message}`);\n }\n\n if (options.json) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n printTerminalSummary(report, reportPath);\n }\n\n config = await maybeScaffoldFoundation(config, report, options, rl);\n await maybeHandoff(config, reportPath, options, rl);\n } finally {\n rl?.close();\n }\n}\n","import { createInterface } from 'node:readline/promises';\nimport type { Verdict } from './gate/safety.js';\n\n/**\n * Shared promote/hold/reject decision helpers — the same decision flow\n * `add`/`scan` use (`docs/gate-policy.md`'s \"Decision flow\" section),\n * reused by `evolve.ts` for its re-gate step. Moved here (v0.7 simplify\n * pass) from `commands/add.ts`, which imports these instead of defining\n * its own copies.\n */\n\nexport type Decision = 'promote' | 'hold' | 'reject';\n\n/** Prints a decision-outcome line: stdout normally, stderr in --json mode so stdout stays a single parseable JSON payload. */\nexport function logOutcome(message: string, json: boolean | undefined): void {\n if (json) console.error(message);\n else console.log(message);\n}\n\n/** --yes/--json honors the verdict: a block is rejected (nonzero exit); pass/warn is promoted. */\nexport function yesDecision(verdict: Verdict): Decision {\n return verdict === 'block' ? 'reject' : 'promote';\n}\n\n/**\n * Prompts interactively for promote/hold/reject, defaulting the suggested\n * answer to the verdict. `subject` names what's being decided in the\n * prompt text (e.g. `\"this skill\"`, `\"this evolution proposal\"`) so each\n * caller keeps its own exact prompt wording.\n */\nexport async function promptDecision(verdict: Verdict, subject: string): Promise<Decision> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const defaultChoice: Decision = verdict === 'block' ? 'reject' : verdict === 'warn' ? 'hold' : 'promote';\n const prompt = `Promote, hold, or reject ${subject}? [promote/hold/reject] (default: ${defaultChoice}) `;\n try {\n // Iterate lines via the async-iterator protocol rather than looping on\n // rl.question(): question() attaches a one-shot 'line' listener per call,\n // and on piped (non-TTY) input where several answer lines arrive in a\n // single chunk, a line can be emitted — and silently dropped — between\n // one question() resolving and the next one attaching its listener. The\n // async iterator queues lines internally, so no answer is ever lost.\n process.stdout.write(prompt);\n for await (const raw of rl) {\n const answer = raw.trim().toLowerCase() || defaultChoice;\n if (answer === 'p' || answer === 'promote') return 'promote';\n if (answer === 'h' || answer === 'hold') return 'hold';\n if (answer === 'r' || answer === 'reject') return 'reject';\n process.stdout.write(`Please answer promote, hold, or reject (or p/h/r).\\n${prompt}`);\n }\n // stdin closed without a valid answer — fail safe to the verdict's default.\n return defaultChoice;\n } finally {\n rl.close();\n }\n}\n","import { loadConfig } from '../config.js';\nimport { resolve as resolveSource, type ArtifactType } from '../install/resolve.js';\nimport { quarantine } from '../install/quarantine.js';\nimport { reject } from '../install/promote.js';\nimport { printGateOutput, runGate, resolveMcpCandidatePath } from './gatePipeline.js';\nimport { maybeOfferInit } from './init.js';\n\nexport interface ScanOptions {\n json?: boolean;\n /** Gate as a skill (default) or an MCP server — v0.5. */\n artifact?: ArtifactType;\n /** Path to a generated rhize-plugins skill map; reports existing-capability overlap against it (skill artifacts only). */\n skillMap?: string;\n}\n\n/**\n * `skill-forge scan <source>` — run the safety gate against a skill (or, with\n * `--artifact mcp`, an MCP server — v0.5) without installing it into a target\n * skills root/MCP config: quarantine-install for inspection, gate + report,\n * then always clean up the quarantine sandbox regardless of verdict. Exits\n * nonzero when the safety verdict is \"block\". `--json` prints the same\n * gate-result payload shape as `add`'s.\n */\nexport async function scan(source: string, options: ScanOptions = {}): Promise<void> {\n await maybeOfferInit({ json: options.json });\n\n const config = loadConfig();\n const artifact: ArtifactType = options.artifact ?? 'skill';\n // Same resolution chain as `add`, so scan and add rank overlap against the same root.\n // Only required for skill artifacts — MCP overlap ranks against config.mcpTargets instead.\n const skillsRoot = config.defaultTarget ?? config.skillsRoots[0];\n if (artifact === 'skill' && !skillsRoot) {\n throw new Error('no skills root configured; set skillsRoots in the config');\n }\n\n const resolved = resolveSource(source, artifact);\n const entry = await quarantine(resolved, config.quarantineDir);\n\n try {\n let candidatePath: string;\n if (artifact === 'mcp') {\n candidatePath = resolveMcpCandidatePath(entry);\n } else {\n if (entry.skillDirs.length === 0) {\n console.error(`[skill-forge] no SKILL.md found in installed source: ${source}`);\n process.exitCode = 1;\n return;\n }\n if (entry.skillDirs.length > 1) {\n console.error(\n `[skill-forge] ${entry.skillDirs.length} skills found in ${source}; scanning the first:\\n` +\n entry.skillDirs.map((d) => ` - ${d}`).join('\\n')\n );\n }\n candidatePath = entry.skillDirs[0];\n }\n\n const { safety, gateResult } = await runGate(candidatePath, skillsRoot ?? '', {\n strictness: config.strictness,\n artifact,\n mcpTargets: config.mcpTargets ?? [],\n skillMapPath: options.skillMap,\n });\n\n if (artifact === 'mcp' && gateResult.profile.kind === 'mcp' && gateResult.profile.serverCount > 1) {\n console.error(\n `[skill-forge] ${gateResult.profile.serverCount} MCP servers found in ${source}; scanning the first — ` +\n gateResult.profile.servers.join(', ')\n );\n }\n\n printGateOutput(gateResult, options.json);\n\n if (safety.verdict === 'block') {\n process.exitCode = 1;\n }\n } finally {\n reject(entry);\n }\n}\n","import { join } from 'node:path';\nimport { loadConfig } from '../config.js';\nimport { findSkillDirs, listQuarantineIds } from '../install/quarantine.js';\nimport { readFrontmatter } from '../gate/overlap.js';\nimport { maybeOfferInit } from './init.js';\n\n/**\n * `skill-forge list` — list skills currently held in quarantine (installed\n * but not yet promoted or rejected via `add`).\n */\nexport async function list(): Promise<void> {\n await maybeOfferInit();\n\n const config = loadConfig();\n const { quarantineDir } = config;\n\n const ids = listQuarantineIds(quarantineDir);\n\n if (ids.length === 0) {\n console.log(`[skill-forge] quarantine dir: ${quarantineDir}`);\n console.log(' (empty — nothing held)');\n return;\n }\n\n console.log(`[skill-forge] ${ids.length} held in quarantine (${quarantineDir}):`);\n for (const id of ids) {\n const skillDirs = findSkillDirs(join(quarantineDir, id));\n if (skillDirs.length === 0) {\n console.log(` ${id} (no SKILL.md found)`);\n continue;\n }\n const names = skillDirs.map((dir) => {\n try {\n return readFrontmatter(join(dir, 'SKILL.md')).name;\n } catch {\n return dir;\n }\n });\n console.log(` ${id} -> ${names.join(', ')}`);\n }\n}\n","import { existsSync } from 'node:fs';\nimport { STALE_AUDIT_DAYS, auditAgeDays, formatAuditFreshness, readAuditState, type AuditState } from '../auditState.js';\nimport { getConfigPath, loadConfig } from '../config.js';\nimport { UserError } from '../errors.js';\nimport { pendingQueueCount } from '../queue.js';\nimport { heldCount } from './held.js';\n\n/**\n * `skill-forge guide [topic]` — in-session orientation: what this CLI does, what state THIS\n * machine is currently in, and the one command that most likely comes next.\n *\n * Deliberately different from the two help surfaces that already exist. Commander's `--help`\n * lists flags but can't say what to do; `status` prints configuration but not what it means or\n * what follows. `guide` is the \"I'm mid-session, orient me\" answer, which is also the shape an\n * agent needs when it lands in a repo and has to decide whether to gate, ingest, or refine.\n *\n * Hard constraints: read-only and NON-INTERACTIVE — no prompts (so it's safe inside an agent\n * turn), no `maybeOfferInit`, and no writes anywhere. It never invents a config: with no\n * config.json it says so and points at `init`, rather than describing `defaultConfig()`'s\n * placeholder paths as if they were real settings.\n */\n\ninterface Topic {\n name: string;\n summary: string;\n body: string[];\n}\n\n/**\n * Topic pages stay deliberately short and point at the authoritative doc rather than restating\n * flag lists — a second copy of the flag reference here would drift from `--help` within a\n * release. Anything longer than this belongs in README.md or docs/.\n */\nconst TOPICS: Topic[] = [\n {\n name: 'pipeline',\n summary: 'how a candidate travels from source to installed skill',\n body: [\n 'Every candidate — a skills.sh slug, a git URL, a local path, or an npm MCP server —',\n 'goes through the same stages, and NOTHING is written to a skills root until you promote:',\n '',\n ' 1. resolve fetch the source into quarantine (~/.skill-forge/quarantine)',\n ' 2. profile read what it declares: name, description, files, MCP capabilities',\n ' 3. safety static deny-pattern scan (never executes the candidate)',\n ' 4. overlap score it against what you already have',\n ' 5. report one verdict: promote / hold / reject',\n ' 6. promote copy into the target skills root, record provenance in SOURCES.md',\n '',\n '`skill-forge add <source>` runs all six. `skill-forge scan <source>` stops after 5 —',\n 'same gate, no install. See README \"add\" and docs/gate-policy.md.',\n ],\n },\n {\n name: 'verbs',\n summary: 'the five decision verbs used after a candidate clears the gate',\n body: [\n 'The gate decides whether something is SAFE. It does not decide whether it is WORTH',\n 'having — that judgment is an agent/human pass over the queue, recorded as one verb:',\n '',\n ' DEFER adopt as-is; no absorb/fork judgment needed',\n ' ABSORB take the useful part into a skill you already have, drop the rest',\n ' FORK keep your own version; the candidate diverges too much to merge',\n ' REJECT do not adopt',\n ' WATCH not now, but track the upstream for changes',\n '',\n 'The verb lands in the skill\\'s SOURCES.md ledger, which is what `watch` later checks',\n 'for drift. See docs/forge-workflow.md for the full decision matrix.',\n ],\n },\n {\n name: 'queue',\n summary: 'the pending-decision queue and how it gets drained',\n body: [\n 'Promoting a candidate appends an entry to ~/.skill-forge/queue.json — a reminder that a',\n 'human/agent decide pass still owes this skill a verb. The gate is code; the verb is',\n 'judgment, so they are separate steps on purpose.',\n '',\n ' skill-forge ingest hand every pending entry to your agent with the prompt',\n ' skill-forge queue close <id> --status ingested|dismissed',\n '',\n 'Entry paths are validated against your quarantine dir and configured roots before any',\n 'handoff — an entry pointing somewhere else is reported, not silently included.',\n 'Schema: schemas/queue-entry.schema.json + docs/queue-schema.md.',\n ],\n },\n {\n name: 'mcp',\n summary: 'gating MCP servers, not just skills',\n body: [\n 'MCP server candidates run through the same six stages as skills. Classification is',\n 'automatic from a source\\'s .mcp.json / MCP-shaped package.json; force it with',\n '`--artifact mcp` (required for a bare npm package name, which is otherwise ambiguous).',\n '',\n 'Extra guarantees specific to MCP: an npm candidate is `npm pack`ed and EXTRACTED, never',\n 'installed and never scripted; capabilities are read statically from source text, never by',\n 'running the server; promotion writes env var NAMES with empty values, never values copied',\n 'from a candidate\\'s examples; and an unpinned `npx` launch spec is auto-pinned or refused.',\n '',\n 'Codex-style TOML configs are detect-and-report only — promote writes JSON targets.',\n ],\n },\n {\n name: 'refine',\n summary: 'fixing an installed skill without editing it in place',\n body: [\n 'When an installed skill misbehaves, capture the gap instead of hand-editing it:',\n '',\n ' skill-forge refine capture expected-vs-actual + a scoped patch',\n ' skill-forge refine list what has been captured so far',\n ' skill-forge refine patterns gaps seen in more than one project',\n ' skill-forge refine promote <id> fold a proven pattern into the base skill',\n ' skill-forge refine which <skill> which override files apply, in what order',\n '',\n 'Capture only ever writes PROJECT-scope override artifacts — the base SKILL.md is touched',\n 'only by `promote`, and only for a pattern that has recurred in a second, different',\n 'project. `evolve` is the other direction: whole-skill, automated, then re-gated.',\n ],\n },\n {\n name: 'maintenance',\n summary: 'commands that operate on the set you already have',\n body: [\n ' skill-forge audit health check over everything configured: inventory, hygiene,',\n ' safety findings, overlap clusters, consolidation opportunities',\n ' skill-forge organize capability registry + dependency graph across every root',\n ' skill-forge watch drift check against each SOURCES.md ledger (--offline to skip',\n ' all network checks; a ledger\\'s stored command is never executed)',\n ' skill-forge find discover skills via skills.sh',\n '',\n 'audit is the one to reach for first — it is re-runnable, read-only by default, and',\n 'writes its report 0600 to ~/.skill-forge/reports/.',\n ],\n },\n {\n name: 'pro',\n summary: 'which features are Pro, and what that means during the 0.x beta',\n body: [\n 'Pro features: overlap analysis (skill + MCP), the pending-ingestion queue and `ingest`,',\n 'provenance/`watch`, `organize`, `evolve`, and `refine`.',\n '',\n 'During the 0.x beta they all run for everyone — each prints a one-line notice, which a',\n 'valid license key suppresses. Everything else (quarantine, profiling, the full safety',\n 'scan, promotion, MCP promotion, `audit`, `find`, `init`) is MIT and free permanently.',\n ],\n },\n];\n\n/** Read-only snapshot of what this machine currently has. Every field is cheap to compute. */\ninterface GuideState {\n configured: boolean;\n configPath: string;\n gateTargets: number;\n defaultTarget?: string;\n quarantined: number;\n /** False when the quarantine dir exists but cannot be listed — reported, never shown as 0. */\n quarantineReadable: boolean;\n pendingQueue: number;\n /** False when queue.json exists but is corrupt. */\n queueReadable: boolean;\n hasBusinessProfile: boolean;\n foundationSkillPath?: string;\n audit?: AuditState;\n}\n\nfunction readState(): GuideState {\n const configPath = getConfigPath();\n const configured = existsSync(configPath);\n const base = {\n configured,\n configPath,\n gateTargets: 0,\n quarantined: 0,\n quarantineReadable: true,\n pendingQueue: 0,\n queueReadable: true,\n hasBusinessProfile: false,\n audit: readAuditState(),\n };\n if (!configured) return base;\n\n const config = loadConfig();\n // Both helpers degrade to 0 on an unreadable quarantine dir or a malformed queue.json — never\n // abort the one command a confused user is most likely to run.\n const quarantined = heldCount(config.quarantineDir);\n const pending = pendingQueueCount();\n\n return {\n ...base,\n gateTargets: config.skillsRoots.length,\n defaultTarget: config.defaultTarget,\n quarantined: quarantined.count,\n quarantineReadable: quarantined.readable,\n pendingQueue: pending.count,\n queueReadable: pending.readable,\n hasBusinessProfile: config.businessProfile !== undefined,\n foundationSkillPath: config.foundationSkillPath,\n };\n}\n\n/** The single most useful next command, in priority order — unfinished work first. */\nfunction nextStep(state: GuideState): { command: string; why: string } {\n if (!state.configured) {\n return { command: 'skill-forge init', why: 'no config yet — detect your agents and set gate targets' };\n }\n if (state.pendingQueue > 0) {\n const n = state.pendingQueue;\n return { command: 'skill-forge ingest', why: `${n} promoted item${n === 1 ? '' : 's'} still awaiting a decide-pass verb` };\n }\n if (state.quarantined > 0) {\n const n = state.quarantined;\n return {\n command: 'skill-forge list',\n why: `${n} candidate${n === 1 ? '' : 's'} held in quarantine — finish each with \\`promote <id>\\` or \\`reject <id>\\``,\n };\n }\n if (!state.audit) {\n return { command: 'skill-forge audit', why: 'no audit on record — start with a health check of what you already have' };\n }\n const age = auditAgeDays(state.audit);\n if (age !== undefined && age >= STALE_AUDIT_DAYS) {\n return { command: 'skill-forge audit', why: `last audit was ${age} days ago — your installed set has probably moved since` };\n }\n return { command: 'skill-forge find <query>', why: 'nothing pending — discover a skill, then `add` it to run the gate' };\n}\n\nfunction printState(state: GuideState): void {\n console.log('\\nWhere you are:');\n if (!state.configured) {\n console.log(` config: none yet (${state.configPath})`);\n } else {\n console.log(` config: ${state.configPath}`);\n console.log(` gate targets: ${state.gateTargets}${state.defaultTarget ? ` (default: ${state.defaultTarget})` : ''}`);\n console.log(` quarantined: ${state.quarantineReadable ? state.quarantined : 'UNREADABLE'}`);\n console.log(` queue: ${state.queueReadable ? `${state.pendingQueue} pending` : 'UNREADABLE (queue.json is corrupt)'}`);\n console.log(` last audit: ${formatAuditFreshness(state.audit)}`);\n if (state.foundationSkillPath) console.log(` foundation: ${state.foundationSkillPath}`);\n else if (state.hasBusinessProfile) console.log(' foundation: business profile captured, no foundation skill scaffolded');\n }\n}\n\nexport async function guide(topic?: string): Promise<void> {\n if (topic !== undefined) {\n const found = TOPICS.find((t) => t.name === topic.toLowerCase());\n if (!found) {\n throw new UserError(\n `unknown guide topic \"${topic}\" — available topics: ${TOPICS.map((t) => t.name).join(', ')}`,\n );\n }\n console.log(`[skill-forge] guide: ${found.name} — ${found.summary}\\n`);\n for (const line of found.body) console.log(line);\n return;\n }\n\n const state = readState();\n const next = nextStep(state);\n\n console.log('[skill-forge] the supply-chain gate for agent skills');\n console.log('');\n console.log('Skills and MCP servers are third-party code that your agent will read and act on.');\n console.log('skill-forge quarantines a candidate, scans it, shows you what it overlaps with, and');\n console.log('installs it only when you say so — then keeps track of what you installed and why.');\n\n printState(state);\n\n console.log('\\nSuggested next step:');\n console.log(` ${next.command}`);\n console.log(` ${next.why}`);\n\n console.log('\\nTopics — `skill-forge guide <topic>`:');\n for (const t of TOPICS) {\n console.log(` ${t.name.padEnd(12)} ${t.summary}`);\n }\n console.log('\\n`skill-forge --help` lists every command and flag; `skill-forge status` prints raw config.');\n}\n","import { existsSync, realpathSync } from 'node:fs';\nimport { basename, join, resolve as resolvePath, sep } from 'node:path';\nimport { loadConfig, type Config } from '../config.js';\nimport { UserError } from '../errors.js';\nimport { findSkillDirs, listQuarantineIds, type QuarantineEntry } from '../install/quarantine.js';\nimport { promote, reject } from '../install/promote.js';\nimport { fingerprintDirs } from '../install/fingerprint.js';\nimport { promoteMcp, type McpServerProfile } from '../install/promoteMcp.js';\nimport { resolveMcpCandidateDetail } from '../install/mcpCandidate.js';\nimport { appendProvenance } from '../provenance.js';\nimport { appendQueueEntry, type CountResult } from '../queue.js';\nimport { readFrontmatter, suggestVerbCode } from '../gate/overlap.js';\nimport { formatSafetyFinding } from '../gate/scanShared.js';\nimport { printGateOutput, runGate, resolveMcpCandidatePath } from './gatePipeline.js';\nimport { logOutcome, promptDecision, yesDecision } from '../decision.js';\n\n/**\n * `skill-forge promote <id>` / `skill-forge reject <id>` — finish a decision that was HELD.\n *\n * Before v0.11 \"hold\" was a dead end: `promote()`/`reject()` were reachable only from inside a\n * single `add`/`scan` run, so answering \"hold\" parked the sandbox in the quarantine dir with no\n * command that could ever finish the decision. `list` showed it; nothing could act on it.\n *\n * `promote` RE-GATES the held sandbox rather than promoting it blind. The original run's gate\n * result was never persisted, so the alternative would be writing a queue entry with a fabricated\n * `gate` record — and a hold may be days old, with the ruleset (or your installed set, which\n * overlap scores against) since changed. Re-scanning is static and cheap, so the honest answer is\n * to just run the gate again and show it.\n */\n\nconst PRO_FEATURE_LABEL = 'overlap analysis, the provenance ledger, and the pending-ingestion queue';\n\n/**\n * Resolves a held id to its sandbox, refusing anything that escapes the configured quarantine dir.\n *\n * The id is untrusted CLI input used to build a path that then gets DELETED or MOVED, so\n * `../../foo`, an absolute path, or a symlinked sandbox must never resolve outside the quarantine\n * root. Enforced by rejecting any id that isn't a bare path segment, then canonicalizing both\n * sides with `realpathSync` and requiring a true path-segment prefix match (comparing against\n * `root + sep` stops `/q-evil` from passing as a child of `/q`).\n */\nfunction resolveHeldPath(quarantineDir: string, id: string): string {\n if (id === '' || id !== basename(id) || id === '.' || id === '..') {\n throw new UserError(`invalid quarantine id \"${id}\" — pass a single id exactly as printed by \\`skill-forge list\\``);\n }\n const candidate = join(quarantineDir, id);\n if (!existsSync(candidate)) {\n throw new UserError(`no held entry with id \"${id}\" — run \\`skill-forge list\\` to see what is in quarantine`);\n }\n\n const realRoot = realpathSync(resolvePath(quarantineDir));\n const realCandidate = realpathSync(candidate);\n if (!realCandidate.startsWith(realRoot + sep)) {\n throw new UserError(`refusing to act on \"${id}\": it resolves outside the quarantine dir (${realRoot})`);\n }\n return realCandidate;\n}\n\n/**\n * Rebuilds the `QuarantineEntry` promote/reject need from a sandbox already on disk. The original\n * `ResolvedSource` only ever existed in memory during the `add` run, so it is reconstructed as the\n * local sandbox path — accurate for what promote/reject do with it, and never presented as the\n * original upstream (provenance below records `quarantine:<id>` rather than inventing a URL).\n */\nfunction heldEntry(id: string, quarantinePath: string): QuarantineEntry {\n return {\n id,\n quarantinePath,\n skillDirs: findSkillDirs(quarantinePath),\n source: { type: 'local', value: quarantinePath },\n };\n}\n\n/** What `reject <id>` accepts — deliberately narrow, so no caller can believe it honors --force/--json. */\nexport interface RejectHeldOptions {\n /** Skip the confirmation prompt. */\n yes?: boolean;\n}\n\nexport interface HeldOptions extends RejectHeldOptions {\n /** Skills root to promote into (defaults to config defaultTarget, then skillsRoots[0]). */\n target?: string;\n /** Overwrite an existing same-name skill dir / MCP server entry in the target. */\n force?: boolean;\n /** Print the re-gate result as JSON instead of the terminal report. */\n json?: boolean;\n /** MCP config file to promote an MCP candidate into (defaults to config.mcpTargets[0], then <cwd>/.mcp.json). */\n mcpTarget?: string;\n}\n\nfunction resolveTargetRoot(config: Config, target: string | undefined): string {\n const root = target ?? config.defaultTarget ?? config.skillsRoots[0];\n if (!root) {\n throw new UserError('no target skills root configured; pass --target <dir> or run `skill-forge init`');\n }\n return root;\n}\n\n/** Names of the skills inside a held sandbox, for the confirmation text. Falls back to the dir name. */\nfunction heldSkillNames(skillDirs: string[]): string[] {\n return skillDirs.map((dir) => {\n try {\n return readFrontmatter(join(dir, 'SKILL.md')).name;\n } catch {\n return basename(dir);\n }\n });\n}\n\n/**\n * True when a held sandbox looks like an MCP candidate rather than a skill: `resolveMcpCandidatePath`\n * resolves to a directory carrying an `.mcp.json`/`mcp.json`, or an npm-packed `package/` dir.\n * Static probe only — nothing in the sandbox is executed.\n */\nfunction looksLikeMcpSandbox(quarantinePath: string): boolean {\n const candidate = resolveMcpCandidatePath({ quarantinePath } as QuarantineEntry);\n return ['.mcp.json', 'mcp.json', 'package.json'].some((f) => existsSync(join(candidate, f)));\n}\n\n/**\n * The MCP half of `promote <id>`: re-gate the held server candidate, then write its entry into an\n * MCP config target. Mirrors `add`'s MCP promote path exactly — env values are never copied from\n * the candidate, the target is backed up first, and an unpinned npx spec is auto-pinned or refused.\n */\nasync function promoteHeldMcp(\n id: string,\n entry: QuarantineEntry,\n config: Config,\n options: HeldOptions\n): Promise<void> {\n const candidatePath = resolveMcpCandidatePath(entry);\n\n logOutcome(`[skill-forge] re-gating held MCP entry ${id}`, options.json);\n const { safety, gateResult, proEnabled } = await runGate(candidatePath, '', {\n proFeatureLabel: PRO_FEATURE_LABEL,\n strictness: config.strictness,\n artifact: 'mcp',\n mcpTargets: config.mcpTargets ?? [],\n });\n printGateOutput(gateResult, options.json);\n\n const { profile, overlap } = gateResult;\n if (profile.kind !== 'mcp') {\n throw new Error(`expected an MCP candidate, found a ${profile.kind}`);\n }\n\n const decision = options.yes ? yesDecision(safety.verdict) : await promptDecision(safety.verdict, `held entry ${id}`);\n if (decision === 'hold') {\n logOutcome('[skill-forge] still held — quarantine sandbox left in place.', options.json);\n return;\n }\n if (decision === 'reject') {\n reject(entry);\n logOutcome(`[skill-forge] rejected — removed ${entry.quarantinePath}`, options.json);\n return;\n }\n\n const detail = resolveMcpCandidateDetail(profile);\n const serverProfile: McpServerProfile = {\n name: detail.name,\n version: detail.version,\n packageName: detail.packageName,\n envVars: detail.env ? Object.keys(detail.env) : undefined,\n candidateEntry: detail.command ? { command: detail.command, args: detail.args, env: detail.env } : undefined,\n };\n const mcpTargetPath = options.mcpTarget ?? config.mcpTargets?.[0] ?? join(process.cwd(), '.mcp.json');\n\n const result = promoteMcp(entry, serverProfile, mcpTargetPath, { force: options.force });\n reject(entry); // promoteMcp never touches the sandbox — clean it up now the entry is written\n\n logOutcome(`[skill-forge] promoted -> ${result.target} (server \"${result.serverName}\")`, options.json);\n if (result.autoPinned) {\n logOutcome(`[skill-forge] pinned launch spec ${result.autoPinned.from} -> ${result.autoPinned.to}`, options.json);\n }\n if (result.backupPath) logOutcome(`[skill-forge] backed up existing config -> ${result.backupPath}`, options.json);\n if (result.envVarsToFill.length > 0) {\n logOutcome(`[skill-forge] fill in these env vars before use: ${result.envVarsToFill.join(', ')}`, options.json);\n }\n\n if (!proEnabled) return;\n appendQueueEntry({\n source: `quarantine:${id}`,\n sourceType: 'local',\n installedPath: result.target,\n quarantinePath: entry.quarantinePath,\n gate: {\n license: null,\n safetyVerdict: safety.verdict,\n safetyFindings: safety.findings.map(formatSafetyFinding),\n overlapTop: overlap ? overlap.ranking.slice(0, 3).map((r) => ({ skill: r.skill, score: r.score })) : [],\n suggestedVerb: overlap ? suggestVerbCode(overlap.nearestScore) : null,\n },\n status: 'pending',\n artifactType: 'mcp',\n capabilities: profile.capabilities,\n });\n logOutcome('[skill-forge] queued for the decide pass — run `skill-forge ingest`.', options.json);\n}\n\n/** `skill-forge promote <id>` — re-gate a held sandbox, then promote / hold / reject it. */\nexport async function promoteHeld(id: string, options: HeldOptions = {}): Promise<void> {\n const config = loadConfig();\n const quarantinePath = resolveHeldPath(config.quarantineDir, id);\n const entry = heldEntry(id, quarantinePath);\n\n // A held MCP candidate has no SKILL.md anywhere — `add --artifact mcp` holds the same way `add`\n // does, so refusing here would leave MCP entries in exactly the dead end this command exists to\n // close (reject was their only exit).\n if (entry.skillDirs.length === 0) {\n if (looksLikeMcpSandbox(quarantinePath)) {\n await promoteHeldMcp(id, entry, config, options);\n return;\n }\n throw new UserError(\n `held entry \"${id}\" contains neither a SKILL.md nor an MCP manifest — use \\`skill-forge reject ${id}\\` to discard it`\n );\n }\n\n const targetRoot = resolveTargetRoot(config, options.target);\n const candidatePath = entry.skillDirs[0];\n // Captured before the re-gate and verified inside promote(). This is the case the check exists\n // for: a hold can sit for days, so the gap between \"scanned\" and \"installed\" is wide open.\n const gatedFingerprint = fingerprintDirs(entry.skillDirs).digest;\n\n logOutcome(`[skill-forge] re-gating held entry ${id} (${heldSkillNames(entry.skillDirs).join(', ')})`, options.json);\n const { safety, gateResult, proEnabled } = await runGate(candidatePath, targetRoot, {\n proFeatureLabel: PRO_FEATURE_LABEL,\n strictness: config.strictness,\n });\n printGateOutput(gateResult, options.json);\n\n const { profile, overlap } = gateResult;\n if (profile.kind !== 'skill') {\n // Unreachable in practice — narrows the Profile|McpProfile union, same guard add.ts uses.\n throw new Error(`expected a skill candidate, found a ${profile.kind}`);\n }\n\n const decision = options.yes ? yesDecision(safety.verdict) : await promptDecision(safety.verdict, `held entry ${id}`);\n\n if (decision === 'hold') {\n logOutcome('[skill-forge] still held — quarantine sandbox left in place.', options.json);\n return;\n }\n if (decision === 'reject') {\n reject(entry);\n logOutcome(`[skill-forge] rejected — removed ${quarantinePath}`, options.json);\n return;\n }\n\n const result = promote(entry, targetRoot, { force: options.force, expectedFingerprint: gatedFingerprint });\n logOutcome(`[skill-forge] promoted ${result.promoted.length} skill dir(s):`, options.json);\n for (const promotedPath of result.promoted) logOutcome(` ${promotedPath}`, options.json);\n\n if (!proEnabled) return;\n\n // ONE ledger entry for the whole quarantine entry, matching add.ts. A sandbox can hold several\n // skill dirs and `promote()` moves all of them, but only skillDirs[0] was gated and named — a\n // per-dir loop would file every one of them under the first skill's name and verdict.\n try {\n const ledgerPath = appendProvenance(\n {\n name: profile.name ?? id,\n source: `quarantine:${id}`,\n verb: 'DEFER',\n target: targetRoot,\n took: `promoted from quarantine after an earlier hold (${result.promoted.length} skill dir${result.promoted.length === 1 ? '' : 's'})`,\n notes: `re-gated at promote time: safety=${safety.verdict}`,\n },\n targetRoot\n );\n logOutcome(`[skill-forge] provenance recorded -> ${ledgerPath}`, options.json);\n } catch (err) {\n // Bookkeeping must never erase a successful promote — name the target so it can be fixed by hand.\n console.error(`[skill-forge] promoted into ${targetRoot}, but recording provenance failed: ${(err as Error).message}`);\n }\n\n appendQueueEntry({\n source: `quarantine:${id}`,\n sourceType: 'local',\n installedPath: result.promoted[0] ?? null,\n quarantinePath,\n gate: {\n license: profile.license ?? null,\n safetyVerdict: safety.verdict,\n safetyFindings: safety.findings.map(formatSafetyFinding),\n overlapTop: overlap ? overlap.ranking.slice(0, 3).map((r) => ({ skill: r.skill, score: r.score })) : [],\n suggestedVerb: overlap ? suggestVerbCode(overlap.nearestScore) : null,\n },\n status: 'pending',\n artifactType: 'skill',\n });\n logOutcome('[skill-forge] queued for the decide pass — run `skill-forge ingest`.', options.json);\n}\n\n/** `skill-forge reject <id>` — discard a held sandbox without promoting anything. */\nexport async function rejectHeld(id: string, options: RejectHeldOptions = {}): Promise<void> {\n const config = loadConfig();\n const quarantinePath = resolveHeldPath(config.quarantineDir, id);\n const entry = heldEntry(id, quarantinePath);\n const names = heldSkillNames(entry.skillDirs);\n\n console.log(`[skill-forge] reject held entry ${id}`);\n if (names.length > 0) console.log(` skills: ${names.join(', ')}`);\n console.log(` this DELETES ${quarantinePath}`);\n\n if (!options.yes) {\n const decision = await promptDecision('block', `held entry ${id}`);\n if (decision !== 'reject') {\n console.log('[skill-forge] left in quarantine — nothing deleted.');\n return;\n }\n }\n\n reject(entry);\n console.log(`[skill-forge] rejected — removed ${quarantinePath}`);\n}\n\n/**\n * Shared by `status`/`guide`/`routine`: how many sandboxes await a decision. Never throws — but\n * reports `readable: false` instead of a bare 0, since a MISSING quarantine dir already returns []\n * without throwing, so reaching the catch means the directory exists and cannot be listed.\n */\nexport function heldCount(quarantineDir: string): CountResult {\n try {\n return { count: listQuarantineIds(quarantineDir).length, readable: true };\n } catch {\n return { count: 0, readable: false };\n }\n}\n","import { createInterface } from 'node:readline/promises';\nimport { getConfigPath, loadConfig, saveConfig } from '../config.js';\nimport { UserError } from '../errors.js';\nimport {\n acceptProposal,\n proposePreference,\n rejectProposal,\n setPreference,\n unsetPreference,\n type PreferenceEntry,\n type PreferenceProposal,\n} from '../preferences.js';\n\n/**\n * `skill-forge config …` — read and write the open-ended preferences store (src/preferences.ts).\n *\n * The split that matters: `set`/`unset` are for a human at a keyboard and take effect at once,\n * while `propose` is the agent-facing door and only ever queues something for `review`. Nothing\n * an agent says about you becomes an effective preference without a human accepting it, and the\n * proposal listing labels every value as untrusted text so a reviewer reads it as a claim rather\n * than as an instruction.\n */\n\nexport interface ConfigCommandOptions {\n json?: boolean;\n origin?: string;\n note?: string;\n /** `review` only: accept/reject the whole queue without prompting (required when non-interactive). */\n acceptAll?: boolean;\n rejectAll?: boolean;\n}\n\nfunction formatEntry(key: string, entry: PreferenceEntry): string {\n const origin = entry.origin ? `, origin: ${entry.origin}` : '';\n const note = entry.note ? ` — ${entry.note}` : '';\n return ` ${key} = ${entry.value}\\n (${entry.source}, updated ${entry.updatedAt}${origin})${note}`;\n}\n\nfunction formatProposal(index: number, proposal: PreferenceProposal): string {\n const origin = proposal.origin ? `, origin: ${proposal.origin}` : '';\n const note = proposal.note ? ` — ${proposal.note}` : '';\n return ` ${index}. ${proposal.key} = ${proposal.value}\\n (proposed ${proposal.proposedAt}${origin})${note}`;\n}\n\nexport async function configList(options: ConfigCommandOptions = {}): Promise<void> {\n const config = loadConfig();\n const preferences = config.preferences ?? {};\n const proposals = config.proposedPreferences ?? [];\n\n if (options.json) {\n console.log(JSON.stringify({ configPath: getConfigPath(), preferences, proposedPreferences: proposals }, null, 2));\n return;\n }\n\n console.log(`[skill-forge] preferences (${getConfigPath()})`);\n const keys = Object.keys(preferences).sort();\n if (keys.length === 0) console.log(' (none set — `skill-forge config set <key> <value>`)');\n for (const key of keys) console.log(formatEntry(key, preferences[key]));\n\n if (proposals.length > 0) {\n console.log(`\\n[skill-forge] ${proposals.length} pending proposal(s) — untrusted until you accept them:`);\n proposals.forEach((p, i) => console.log(formatProposal(i + 1, p)));\n console.log('\\n review with `skill-forge config review`');\n }\n}\n\nexport async function configGet(key: string, options: ConfigCommandOptions = {}): Promise<void> {\n const entry = loadConfig().preferences?.[key];\n if (!entry) {\n throw new UserError(`no preference named \"${key}\" — \\`skill-forge config list\\` shows what is set`);\n }\n if (options.json) {\n console.log(JSON.stringify({ key, ...entry }, null, 2));\n return;\n }\n console.log(entry.value);\n}\n\nexport async function configSet(key: string, value: string, options: ConfigCommandOptions = {}): Promise<void> {\n const config = loadConfig();\n const updated = setPreference(config, key, value, { origin: options.origin ?? 'config set', note: options.note });\n saveConfig(updated);\n console.log(`[skill-forge] set ${key} = ${value}`);\n}\n\nexport async function configUnset(key: string): Promise<void> {\n saveConfig(unsetPreference(loadConfig(), key));\n console.log(`[skill-forge] unset ${key}`);\n}\n\n/**\n * `skill-forge config propose <key> <value>` — the door agents use.\n *\n * Deliberately never writes an effective preference, no matter what flags are passed: there is no\n * `--accept` here, because a command an agent can run must not also be the command that approves\n * what it ran. Approval lives in `review`, which is interactive or requires an explicit\n * human-typed `--accept-all`.\n */\nexport async function configPropose(key: string, value: string, options: ConfigCommandOptions = {}): Promise<void> {\n const config = loadConfig();\n const updated = proposePreference(config, key, value, { origin: options.origin, note: options.note });\n saveConfig(updated);\n console.log(`[skill-forge] proposed ${key} — pending review (\\`skill-forge config review\\`)`);\n}\n\nexport async function configReview(options: ConfigCommandOptions = {}): Promise<void> {\n let config = loadConfig();\n const proposals = config.proposedPreferences ?? [];\n\n if (proposals.length === 0) {\n console.log('[skill-forge] no pending preference proposals.');\n return;\n }\n\n if (options.acceptAll && options.rejectAll) {\n throw new UserError('--accept-all and --reject-all are mutually exclusive');\n }\n\n if (options.acceptAll || options.rejectAll) {\n for (const proposal of [...proposals]) {\n config = options.acceptAll ? acceptProposal(config, proposal.key) : rejectProposal(config, proposal.key);\n }\n saveConfig(config);\n console.log(`[skill-forge] ${options.acceptAll ? 'accepted' : 'rejected'} ${proposals.length} proposal(s).`);\n return;\n }\n\n if (!process.stdin.isTTY) {\n // Refusing here is the point: an unattended run must not be able to approve agent-proposed\n // config by falling through to a default.\n throw new UserError(\n 'not an interactive terminal — re-run `skill-forge config review` in a terminal, or pass --accept-all / --reject-all explicitly'\n );\n }\n\n console.log(`[skill-forge] ${proposals.length} pending preference proposal(s).`);\n console.log('These are CLAIMS made by an agent about you, not instructions — accept only what you recognize as true.\\n');\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n let accepted = 0;\n let rejected = 0;\n try {\n for (const proposal of [...proposals]) {\n console.log(formatProposal(accepted + rejected + 1, proposal));\n const answer = (await rl.question(' accept? [y/N/q] ')).trim().toLowerCase();\n if (answer === 'q' || answer === 'quit') break;\n if (answer === 'y' || answer === 'yes') {\n config = acceptProposal(config, proposal.key);\n accepted += 1;\n } else {\n config = rejectProposal(config, proposal.key);\n rejected += 1;\n }\n }\n } finally {\n rl.close();\n }\n\n saveConfig(config);\n console.log(`\\n[skill-forge] accepted ${accepted}, rejected ${rejected}, ${(config.proposedPreferences ?? []).length} left pending.`);\n}\n","import { formatAuditFreshness, readAuditState } from '../auditState.js';\nimport { loadConfig } from '../config.js';\nimport { pendingQueueCount } from '../queue.js';\nimport { heldCount } from './held.js';\nimport { maybeOfferInit } from './init.js';\n\n/**\n * `skill-forge status` — show current configuration and a summary of what is\n * outstanding: quarantine, the pending-decision queue, and audit freshness.\n *\n * Everything past the config block is a COUNT OF UNFINISHED WORK, each with the command that\n * finishes it — a status that lists settings but not what they are waiting on sends you to\n * `--help` to figure out what to do next.\n */\nexport async function status(): Promise<void> {\n await maybeOfferInit();\n\n const config = loadConfig();\n // Both helpers degrade to 0 rather than throwing: `status` is a diagnostic, so it has to survive\n // a broken quarantine dir or a malformed queue.json well enough to show the rest.\n const held = heldCount(config.quarantineDir);\n const pending = pendingQueueCount();\n\n console.log('[skill-forge] status');\n console.log(` skillsRoots: ${config.skillsRoots.join(', ')}`);\n if (config.defaultTarget) console.log(` defaultTarget: ${config.defaultTarget}`);\n if (config.mcpTargets?.length) console.log(` mcpTargets: ${config.mcpTargets.join(', ')}`);\n console.log(` quarantineDir: ${config.quarantineDir}`);\n console.log(` strictness: ${config.strictness}`);\n console.log(` last audit: ${formatAuditFreshness(readAuditState())}`);\n console.log(\n ` held in quarantine: ${held.readable ? held.count : 'UNREADABLE (quarantine dir cannot be listed)'}` +\n (held.count > 0 ? ' (`skill-forge promote <id>` / `reject <id>`)' : '')\n );\n console.log(\n ` queue: ${pending.readable ? `${pending.count} pending` : 'UNREADABLE (queue.json is corrupt)'}` +\n (pending.count > 0 ? ' (`skill-forge ingest`)' : '')\n );\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { createInterface } from 'node:readline/promises';\nimport { createHash } from 'node:crypto';\nimport { cpSync, existsSync, lstatSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { basename, dirname, join, resolve as resolvePath, sep } from 'node:path';\nimport { UserError } from './errors.js';\nimport { loadConfig } from './config.js';\nimport { proFeatureStatus } from './license.js';\nimport { profileSkill, asSkillProfile } from './gate/profile.js';\nimport type { SkillProfile } from './gate/profile.js';\nimport { scanSafety } from './gate/safety.js';\nimport type { SafetyResult } from './gate/safety.js';\nimport { formatSafetyFinding } from './gate/scanShared.js';\nimport { SAFETY_VERDICT_MAP, mapSafetyFindings, printGateOutput } from './report/render.js';\nimport type { GateResult } from './report/render.js';\nimport { logOutcome, promptDecision, yesDecision } from './decision.js';\nimport { appendProvenance } from './provenance.js';\nimport { appendQueueEntry } from './queue.js';\nimport { defaultExec } from './install/quarantine.js';\nimport type { ExecFn } from './install/quarantine.js';\n\n/**\n * `skill-forge evolve` (v0.7, Pro) — orchestrates microsoft/SkillOpt's\n * `skillopt-sleep` CLI to propose a self-evolution of an already-installed\n * skill (harvest recent sessions -> generate a replacement `SKILL.md`/\n * `CLAUDE.md` -> stage it, never auto-adopted), then re-gates the STAGED\n * PROPOSAL — never the live skill — through the same static safety ruleset\n * `add`/`scan` use, before a promote/hold/reject decision.\n *\n * `skillopt-sleep` is never installed by this module (detect-don't-install:\n * absence prints `pip install skillopt` guidance and refuses) and every\n * invocation of it goes through argv arrays with no shell, mirroring\n * `install/quarantine.ts`'s subprocess discipline. Nothing from a staged\n * proposal is ever executed — the re-gate step only reads\n * `proposed_SKILL.md`/`proposed_CLAUDE.md` as text.\n *\n * The entire command is Pro (unlike `add`, which is free with only specific\n * sub-features Pro-gated): self-evolution reuses the provenance ledger and\n * pending-ingestion queue, and is a power-user workflow layered on the free\n * gate. Free during the 0.x beta via `proFeatureStatus()`, same as every\n * other Pro feature — see src/license.ts.\n *\n * `staging_dir` (from skillopt-sleep's own JSON output) is untrusted input:\n * it's validated to be strictly contained under the expected sandbox\n * (`<project>/.skillopt-sleep/staging/`) before anything reads, copies, or\n * deletes it — mirroring `install/quarantine.ts`'s tar-member path-traversal\n * guard. The staged proposal is further treated as untrusted: a symlink in\n * place of a proposed file is refused, and the exact bytes re-gated are\n * hash-pinned and re-verified immediately before `adopt` runs, so a mutation\n * between the scan and the adopt (TOCTOU) is caught rather than silently\n * adopted.\n */\n\nconst SKILLOPT_BIN = 'skillopt-sleep';\nconst PRO_FEATURE_LABEL = 'self-evolution via SkillOpt-Sleep (evolve) — re-gating, provenance, and the pending-ingestion queue';\n\nexport interface EvolveOptions {\n /** Project dir passed to skillopt-sleep as --project. Defaults to cwd. */\n project?: string;\n /** Use skillopt-sleep's `dry-run` subcommand (report only, nothing staged) instead of `run`. */\n dryRun?: boolean;\n /** skillopt-sleep backend. Defaults to \"mock\" (offline, no session data leaves the machine). */\n backend?: string;\n /** Hours of session history for skillopt-sleep to harvest. Passed through as --lookback-hours. */\n lookbackHours?: number;\n /** Skip interactive prompts (the disclosure confirm and the promote/hold/reject prompt); honor the re-gate verdict automatically. */\n yes?: boolean;\n /** Print the re-gate result as JSON instead of the terminal report. Implies non-interactive, same as `add`'s --json. */\n json?: boolean;\n /** Allow re-adopting a staging dir whose proposed content already matches the live skill (double-adopt guard override). */\n force?: boolean;\n}\n\nexport interface EvolveDeps {\n /** Injected subprocess runner for skillopt-sleep invocations. Defaults to a real argv-array spawn (no shell). */\n exec?: ExecFn;\n /** Injected y/N confirm for the data-boundary disclosure prompt. Defaults to a real stdin readline confirm. */\n confirm?: (question: string) => Promise<boolean>;\n}\n\ninterface SkilloptRunOutput {\n staging_dir: string | null;\n accepted: boolean;\n}\n\ninterface StagingManifest {\n live_skill_path: string;\n live_memory_path?: string | null;\n has_skill: boolean;\n has_memory: boolean;\n}\n\nexport async function evolve(targetSkillDir: string, options: EvolveOptions = {}, deps: EvolveDeps = {}): Promise<void> {\n const exec = deps.exec ?? defaultExec;\n const json = options.json;\n\n // 1. Validate the target skill dir.\n const resolvedTargetDir = resolvePath(targetSkillDir);\n const skillMdPath = join(resolvedTargetDir, 'SKILL.md');\n if (!existsSync(skillMdPath)) {\n throw new UserError(`no SKILL.md found at ${skillMdPath} — evolve requires an existing, installed skill directory`);\n }\n\n // 2. Pro gate — the whole command is Pro; free during the 0.x beta.\n const proStatus = proFeatureStatus(PRO_FEATURE_LABEL);\n if (!proStatus.enabled) {\n throw new UserError(proStatus.notice ?? 'evolve is a Pro feature — set SKILL_FORGE_LICENSE or config.licenseKey to a valid license to unlock.');\n }\n const notices: string[] = proStatus.notice ? [proStatus.notice] : [];\n\n // 3. Data-boundary disclosure.\n const backend = options.backend ?? 'mock';\n if (backend !== 'mock') {\n const disclosure =\n `data boundary: this run uses the \"${backend}\" backend. Per SkillOpt-Sleep's own docs, harvesting itself ` +\n 'is local and read-only, but a real backend sends truncated excerpts from harvested sessions and derived ' +\n 'tasks to the provider you selected — this is not currently guaranteed to be secret-free.';\n if (options.yes) {\n notices.push(`${disclosure} Proceeding (--yes).`);\n } else if (json) {\n throw new UserError(`${disclosure} Pass --yes to proceed — --json is non-interactive and cannot prompt for confirmation.`);\n } else {\n console.log(`[skill-forge] ${disclosure}`);\n const confirmFn = deps.confirm ?? defaultConfirm;\n const confirmed = await confirmFn('Proceed and send session data to this backend? [y/N] ');\n if (!confirmed) {\n throw new UserError('evolve cancelled — data-boundary disclosure was not accepted.');\n }\n notices.push(`${disclosure} Confirmed interactively.`);\n }\n } else {\n notices.push('using the offline \"mock\" backend — deterministic, no network calls, no session data leaves this machine.');\n }\n\n // 4. Detect (never install), then run skillopt-sleep.\n const project = resolvePath(options.project ?? process.cwd());\n const present = await detectSkillopt(exec, project);\n if (!present) {\n throw new UserError(\n '`skillopt-sleep` not found on PATH. Install it with `pip install skillopt` ' +\n '(https://github.com/microsoft/SkillOpt) and re-run `skill-forge evolve`.'\n );\n }\n\n const subcommand = options.dryRun ? 'dry-run' : 'run';\n const runArgs = [subcommand, '--project', project, '--target-skill-path', skillMdPath, '--json', '--backend', backend];\n if (options.lookbackHours !== undefined) {\n runArgs.push('--lookback-hours', String(options.lookbackHours));\n }\n\n const runResult = await exec(SKILLOPT_BIN, runArgs, { cwd: project });\n if (runResult.code !== 0) {\n throw new UserError(`skillopt-sleep ${subcommand} failed (exit ${runResult.code}): ${tail(runResult.stderr || runResult.stdout)}`);\n }\n\n let rawOutput: unknown;\n try {\n rawOutput = JSON.parse(runResult.stdout);\n } catch (err) {\n throw new UserError(`could not parse skillopt-sleep JSON output: ${(err as Error).message}`);\n }\n const parsed = validateRunOutput(rawOutput);\n\n if (!parsed.accepted || !parsed.staging_dir) {\n if (json) {\n console.log(JSON.stringify({ ok: true, accepted: parsed.accepted, stagingDir: parsed.staging_dir }, null, 2));\n } else {\n console.log('[skill-forge] skillopt-sleep produced no accepted proposal — nothing to re-gate or adopt.');\n }\n return;\n }\n\n // `staging_dir` is untrusted (skillopt-sleep's own JSON output) — require it to\n // be strictly contained under the expected sandbox before it's read, copied,\n // adopted, or deleted anywhere below. A naive `startsWith(stagingRoot)` would\n // be defeated by a sibling directory sharing the same string prefix (e.g.\n // `.skillopt-sleep/staging-evil` next to `.skillopt-sleep/staging`) — this\n // checks for either exact equality or the separator-qualified prefix, mirroring\n // `install/quarantine.ts`'s tar-member path-traversal guard.\n const stagingRoot = join(project, '.skillopt-sleep', 'staging');\n const stagingDir = resolvePath(parsed.staging_dir);\n if (stagingDir !== stagingRoot && !stagingDir.startsWith(stagingRoot + sep)) {\n throw new UserError(\n `skillopt-sleep reported a staging_dir outside the expected sandbox (${stagingRoot}): ${stagingDir} — refusing to use it`\n );\n }\n\n // 5. Re-gate: scan ONLY the staged proposal, in a fresh temp copy — never the live skill, never report.json/diagnostics.json.\n const proposedSkillMd = join(stagingDir, 'proposed_SKILL.md');\n const proposedClaudeMd = join(stagingDir, 'proposed_CLAUDE.md');\n const reportMdPath = join(stagingDir, 'report.md');\n if (!existsSync(proposedSkillMd)) {\n throw new UserError(`skillopt-sleep reported an accepted proposal but ${proposedSkillMd} is missing`);\n }\n if (existsSync(reportMdPath)) {\n notices.push(`SkillOpt-Sleep's full report: ${reportMdPath}`);\n }\n\n // A staged proposal is untrusted content: refuse a symlink in place of a\n // proposed file (spirit of the tar-member guard — never follow a link out\n // of the sandbox) before reading either one.\n assertNotSymlink(proposedSkillMd, 'proposed_SKILL.md');\n assertNotSymlink(proposedClaudeMd, 'proposed_CLAUDE.md');\n\n // Hash-pin what's about to be scanned, so a mutation of the staged files\n // between this scan and the `adopt` call below (TOCTOU) is caught rather\n // than silently adopted — see the re-check immediately before `adopt`.\n const scannedSkillHash = sha256File(proposedSkillMd);\n const scannedClaudeHash = sha256File(proposedClaudeMd);\n\n const tempDir = mkdtempSync(join(tmpdir(), 'skill-forge-evolve-'));\n let profile: SkillProfile;\n let safety: SafetyResult;\n try {\n cpSync(proposedSkillMd, join(tempDir, 'SKILL.md'));\n if (existsSync(proposedClaudeMd)) {\n cpSync(proposedClaudeMd, join(tempDir, 'CLAUDE.md'));\n }\n\n // Unreachable in practice: the temp dir only ever contains a renamed\n // proposed_SKILL.md (+ proposed_CLAUDE.md), never an .mcp.json or\n // plugin.json — asSkillProfile() throws before this could matter.\n profile = asSkillProfile(profileSkill(tempDir), 'for the evolved proposal');\n\n const strictness = loadConfig().strictness;\n safety = await scanSafety(tempDir, strictness);\n } finally {\n // A cleanup failure here is secondary to whatever the try block already\n // produced (a result, or an error already propagating) — never let it\n // mask that.\n try {\n rmSync(tempDir, { recursive: true, force: true });\n } catch (err) {\n console.error(`[skill-forge] failed to clean up temp re-gate dir ${tempDir}: ${(err as Error).message}`);\n }\n }\n\n const safetyVerdict = safety.verdict;\n const gateResult: GateResult = {\n profile,\n safety: {\n verdict: SAFETY_VERDICT_MAP[safetyVerdict],\n findings: mapSafetyFindings(safety.findings),\n },\n notices,\n };\n printGateOutput(gateResult, json);\n\n // --dry-run belt-and-braces: never stage/adopt anything, regardless of what\n // skillopt-sleep's JSON output claims — even if it (bug or otherwise)\n // reports an accepted proposal + a real staging_dir for a dry-run call,\n // this never reaches the decision/adopt path below.\n if (options.dryRun) {\n logOutcome('[skill-forge] --dry-run: report only, nothing staged or adopted.', json);\n return;\n }\n\n // 6. Decide — same promote/hold/reject semantics as add/scan.\n const decision =\n options.yes || json ? yesDecision(safetyVerdict) : await promptDecision(safetyVerdict, 'this evolution proposal');\n\n if (decision === 'promote') {\n const manifest = readManifest(stagingDir);\n if (isAlreadyAdopted(stagingDir, manifest) && !options.force) {\n throw new UserError(\n `staging dir ${stagingDir} looks already adopted — its proposed content is byte-identical to the live ` +\n 'skill (and memory, if present). Pass --force to re-adopt anyway.'\n );\n }\n\n // Re-verify immediately before adopting: if the staged files changed since\n // they were scanned above, what's about to be adopted is no longer what\n // was re-gated.\n if (sha256File(proposedSkillMd) !== scannedSkillHash || sha256File(proposedClaudeMd) !== scannedClaudeHash) {\n throw new UserError(`proposal changed after it was re-gated (staging dir: ${stagingDir}) — refusing to adopt`);\n }\n\n const adoptResult = await exec(SKILLOPT_BIN, ['adopt', '--staging', stagingDir, '--project', project], { cwd: project });\n if (adoptResult.code !== 0) {\n throw new UserError(`skillopt-sleep adopt failed (exit ${adoptResult.code}): ${tail(adoptResult.stderr || adoptResult.stdout)}`);\n }\n\n // The live skill has now changed — report that immediately, before the\n // bookkeeping below, which can itself fail without undoing the adopt.\n logOutcome(`[skill-forge] adopted -> ${resolvedTargetDir}`, json);\n\n // Free-tier `add` records DEFER for \"adopt as-is, no absorb/fork judgment\" —\n // the same is true here (the re-gate is a safety check, not a redundancy\n // judgment against the existing set), so this reuses DEFER rather than\n // adding a sixth member to provenance.ts's five-verb FORGE_VERBS/ForgeVerb\n // (referenced elsewhere as exactly five: docs/gate-policy.md,\n // assets/ingest-prompt.md, README.md, CLAUDE.md). The distinguishing\n // \"this was self-evolved\" signal lives in the note below and in the\n // queue entry's `origin: 'evolve'` field instead.\n const skillsRootForProvenance = dirname(resolvedTargetDir);\n let ledgerPath: string;\n try {\n ledgerPath = appendProvenance(\n {\n name: profile.name ?? basename(resolvedTargetDir),\n source: resolvedTargetDir,\n version: profile.version,\n license: profile.license,\n verb: 'DEFER',\n target: resolvedTargetDir,\n took: `self-evolved via SkillOpt-Sleep (staging: ${stagingDir})`,\n notes: `self-evolved via SkillOpt-Sleep; re-gated (verdict: ${safetyVerdict})`,\n },\n skillsRootForProvenance\n );\n } catch (err) {\n throw new UserError(\n `adopt succeeded and the live skill at ${resolvedTargetDir} has already changed, but recording the ` +\n `provenance entry failed: ${(err as Error).message}`\n );\n }\n logOutcome(`[skill-forge] provenance recorded -> ${ledgerPath}`, json);\n\n try {\n appendQueueEntry({\n source: resolvedTargetDir,\n sourceType: 'local',\n installedPath: resolvedTargetDir,\n quarantinePath: stagingDir,\n gate: {\n license: profile.license ?? null,\n safetyVerdict,\n safetyFindings: safety.findings.map(formatSafetyFinding),\n overlapTop: [],\n suggestedVerb: null,\n },\n status: 'pending',\n artifactType: 'skill',\n origin: 'evolve',\n });\n } catch (err) {\n throw new UserError(\n `adopt succeeded and the live skill at ${resolvedTargetDir} has already changed (provenance was recorded), ` +\n `but appending the pending-ingestion queue entry failed: ${(err as Error).message}`\n );\n }\n logOutcome('[skill-forge] queued for ingestion review', json);\n } else if (decision === 'reject') {\n try {\n rmSync(stagingDir, { recursive: true, force: true });\n } catch (err) {\n throw new UserError(\n `rejected the proposal, but removing the staging dir ${stagingDir} failed: ${(err as Error).message} — ` +\n 'remove it manually.'\n );\n }\n logOutcome('[skill-forge] rejected — staging dir removed', json);\n if (safetyVerdict === 'block') process.exitCode = 1;\n } else {\n logOutcome(`[skill-forge] held — staging dir left in place -> ${stagingDir}`, json);\n if (existsSync(reportMdPath)) {\n logOutcome(`[skill-forge] see ${reportMdPath} for the full evidence report`, json);\n }\n }\n}\n\nasync function defaultConfirm(question: string): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n try {\n // Iterate lines via the async-iterator protocol rather than rl.question() —\n // same rationale as decision.ts's promptDecision: question() attaches a\n // one-shot 'line' listener, and on piped/closed (non-TTY) stdin that ends\n // without ever emitting a line, that listener never fires and the promise\n // never resolves — the process hangs forever. The async iterator both\n // queues lines and terminates cleanly at EOF, so a closed stream resolves\n // (falling through to `false`, \"not confirmed\") instead of hanging.\n process.stdout.write(question);\n for await (const raw of rl) {\n return /^y(es)?$/i.test(raw.trim());\n }\n return false;\n } finally {\n rl.close();\n }\n}\n\n/** Detects skillopt-sleep on PATH by running `--help` via the injected exec — never installs it. Any rejection (ENOENT) or nonzero exit is treated as absent. */\nasync function detectSkillopt(exec: ExecFn, cwd: string): Promise<boolean> {\n try {\n const result = await exec(SKILLOPT_BIN, ['--help'], { cwd });\n return result.code === 0;\n } catch {\n return false;\n }\n}\n\n/** Last `maxLines` lines of `text`, for surfacing a subprocess failure without dumping unbounded output. */\nfunction tail(text: string, maxLines = 20): string {\n return text.trim().split('\\n').slice(-maxLines).join('\\n');\n}\n\n/**\n * Validates skillopt-sleep's `run`/`dry-run` JSON output — an external-process\n * trust boundary — before any field is used: must be a plain object,\n * `staging_dir` must be a string or null/undefined, otherwise a UserError\n * (not a TypeError surfacing later at some unrelated call site). `accepted`\n * is coerced to a boolean rather than strictly typechecked, matching how\n * loosely the field is actually consumed downstream.\n */\nfunction validateRunOutput(raw: unknown): SkilloptRunOutput {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new UserError(`skillopt-sleep produced unexpected JSON output (expected an object): ${JSON.stringify(raw)}`);\n }\n const obj = raw as Record<string, unknown>;\n const stagingDirRaw = obj.staging_dir;\n if (stagingDirRaw !== null && stagingDirRaw !== undefined && typeof stagingDirRaw !== 'string') {\n throw new UserError(\n `skillopt-sleep produced an unexpected \"staging_dir\" (expected a string or null): ${JSON.stringify(stagingDirRaw)}`\n );\n }\n return { accepted: Boolean(obj.accepted), staging_dir: stagingDirRaw ?? null };\n}\n\nfunction readManifest(stagingDir: string): StagingManifest {\n const manifestPath = join(stagingDir, 'manifest.json');\n let raw: string;\n try {\n raw = readFileSync(manifestPath, 'utf-8');\n } catch (err) {\n throw new UserError(`could not read staging manifest ${manifestPath}: ${(err as Error).message}`);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new UserError(`could not parse staging manifest ${manifestPath}: ${(err as Error).message}`);\n }\n return validateManifest(parsed, manifestPath);\n}\n\n/** Validates manifest.json's shape (another external trust boundary) before any field is used. */\nfunction validateManifest(raw: unknown, manifestPath: string): StagingManifest {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new UserError(`staging manifest ${manifestPath} has an unexpected shape (expected an object)`);\n }\n const obj = raw as Record<string, unknown>;\n if (typeof obj.live_skill_path !== 'string') {\n throw new UserError(`staging manifest ${manifestPath} is missing a valid \"live_skill_path\" string`);\n }\n if (obj.live_memory_path !== undefined && obj.live_memory_path !== null && typeof obj.live_memory_path !== 'string') {\n throw new UserError(`staging manifest ${manifestPath} has an unexpected \"live_memory_path\" (expected a string or null)`);\n }\n if (typeof obj.has_skill !== 'boolean' || typeof obj.has_memory !== 'boolean') {\n throw new UserError(`staging manifest ${manifestPath} is missing valid \"has_skill\"/\"has_memory\" booleans`);\n }\n return {\n live_skill_path: obj.live_skill_path,\n live_memory_path: (obj.live_memory_path as string | null | undefined) ?? null,\n has_skill: obj.has_skill,\n has_memory: obj.has_memory,\n };\n}\n\nfunction filesIdentical(a: string, b: string): boolean {\n if (!existsSync(a) || !existsSync(b)) return false;\n return readFileSync(a).equals(readFileSync(b));\n}\n\n/**\n * Double-adopt guard: true when the staged proposal's content is\n * byte-identical to the live file(s) it would replace — i.e. this staging\n * dir already appears to have been adopted once. skillopt-sleep's own\n * `adopt` has no confirmation and a second adopt overwrites its own backup\n * (the pre-evolution original is lost), so this guard exists purely on our\n * side of the wrapper.\n *\n * Grounded in which proposed files actually exist on disk (`existsSync`)\n * rather than trusting the manifest's `has_skill`/`has_memory` flags blindly\n * — a stale or malformed manifest claiming `false` shouldn't be able to\n * silently skip a comparison for a file that's actually sitting right there.\n */\nfunction isAlreadyAdopted(stagingDir: string, manifest: StagingManifest): boolean {\n const proposedSkillMd = join(stagingDir, 'proposed_SKILL.md');\n const proposedClaudeMd = join(stagingDir, 'proposed_CLAUDE.md');\n let sawComparison = false;\n\n if (existsSync(proposedSkillMd)) {\n sawComparison = true;\n if (!filesIdentical(proposedSkillMd, manifest.live_skill_path)) return false;\n }\n if (existsSync(proposedClaudeMd) && manifest.live_memory_path) {\n sawComparison = true;\n if (!filesIdentical(proposedClaudeMd, manifest.live_memory_path)) return false;\n }\n\n return sawComparison;\n}\n\n/** Throws a UserError if `path` is a symlink. Silent no-op if `path` doesn't exist (existsSync() calls elsewhere already gate required-vs-optional). Uses lstatSync (never follows the link) so a broken symlink is still caught. */\nfunction assertNotSymlink(path: string, label: string): void {\n let stat;\n try {\n stat = lstatSync(path);\n } catch {\n return;\n }\n if (stat.isSymbolicLink()) {\n throw new UserError(`refusing to re-gate ${label}: ${path} is a symlink (a staged proposal must be a plain file)`);\n }\n}\n\n/** sha256 hex digest of a file's contents, or null if it doesn't exist. Exported for direct unit coverage of the TOCTOU hash-pin (see evolve()'s scan-time / pre-adopt re-check). */\nexport function sha256File(path: string): string | null {\n if (!existsSync(path)) return null;\n return createHash('sha256').update(readFileSync(path)).digest('hex');\n}\n","import { evolve } from '../evolve.js';\nimport type { EvolveOptions } from '../evolve.js';\n\n/**\n * `skill-forge evolve <target-skill-dir>` — thin CLI wiring around the Pro\n * self-evolution flow in src/evolve.ts: orchestrate a SkillOpt-Sleep run,\n * re-gate the staged proposal through the same static skill safety ruleset\n * `add`/`scan` use, then promote/hold/reject. See src/evolve.ts for the\n * actual pipeline; this file only adapts commander's positional/options\n * shape into a call.\n */\nexport async function evolveCommand(targetSkillDir: string, options: EvolveOptions = {}): Promise<void> {\n await evolve(targetSkillDir, options);\n}\n","import { existsSync } from 'node:fs';\nimport { auditConfiguredSet, sanitizeForMarkdown } from '../audit.js';\nimport { getConfigPath, loadConfig } from '../config.js';\nimport { UserError } from '../errors.js';\nimport {\n FINGERPRINT_DISPLAY_LENGTH,\n MIN_FINGERPRINT_PREFIX_LENGTH,\n readAcceptedFindings,\n resolveAmongFindings,\n resolveAmongRecords,\n validateAcceptReason,\n writeAcceptedFindings,\n type AcceptedFinding,\n} from '../findings.js';\n\n/**\n * `skill-forge finding accept|revoke|list` (v0.12, MIT, free) — the human-only door for\n * acknowledging `skill-forge audit` findings you've reviewed and accepted. See\n * `src/findings.ts`'s module doc for the identity/fail-open contract this command writes into,\n * and `docs/accepted-findings-schema.md` for the full field/lifecycle reference.\n *\n * **Human-only by design.** `assets/ingest-prompt.md` and `assets/curation-prompt.md` both\n * instruct agents never to run `finding accept`/`finding revoke` — an agent that judges a finding\n * a false positive should say so in its summary and let the user run this command themselves. This\n * file enforces the mechanical half of that boundary: a severity cap (LOW/MEDIUM only, no override\n * flag for HIGH/CRITICAL) and a mandatory, validated `--reason`.\n *\n * **Never a gate input.** Nothing here touches `add`/`scan`/`promote <id>`/`evolve`'s re-gate —\n * this command only ever reads/writes the accepted-findings store and re-runs the read-only audit\n * engine to resolve a prefix against what is CURRENTLY active.\n */\n\nexport interface FindingAcceptOptions {\n reason?: string;\n json?: boolean;\n}\n\nexport interface FindingRevokeOptions {\n json?: boolean;\n}\n\nexport interface FindingListOptions {\n stale?: boolean;\n json?: boolean;\n}\n\nfunction requireSavedConfig(): void {\n if (!existsSync(getConfigPath())) {\n throw new UserError(\n 'no skill-forge config found — run `skill-forge init` first (`finding accept` re-runs the audit engine to resolve a live finding, and audit never runs against an invented default).'\n );\n }\n}\n\nfunction shortDate(iso: string): string {\n return iso.slice(0, 10) || iso;\n}\n\n/** `skill-forge finding accept <fingerprint-prefix> --reason <text>`. */\nexport async function findingAcceptCommand(prefix: string, options: FindingAcceptOptions = {}): Promise<void> {\n requireSavedConfig();\n\n if (prefix.length < MIN_FINGERPRINT_PREFIX_LENGTH) {\n throw new UserError(`prefix too short — use at least ${MIN_FINGERPRINT_PREFIX_LENGTH} hex chars (got \"${prefix}\")`);\n }\n\n const reason = options.reason;\n if (reason === undefined) {\n throw new UserError('--reason is required — a short note on why this finding is accepted');\n }\n validateAcceptReason(reason);\n\n const config = loadConfig();\n // Loaded (not merely for pass-through): resolving \"already accepted\" below needs the current\n // records, and the live re-audit needs them too so an already-accepted finding stays out of\n // `report.hygiene` (the ACTIVE list this command resolves prefixes against — see §7 below).\n const { records: accepted } = readAcceptedFindings();\n const report = await auditConfiguredSet(config, { accepted });\n\n // Resolving against report.hygiene (ACTIVE findings from a FRESH run) is deliberate, not just a\n // convenient data source: you can only accept a finding that exists RIGHT NOW — no accepting off\n // a stale report, no fabricating a record for a finding that's already gone.\n const activeMatches = resolveAmongFindings(prefix, report.hygiene);\n\n if (activeMatches.length > 1) {\n const list = activeMatches.map((f) => `${f.rule} @ ${f.target} (${f.fingerprint})`).join('; ');\n throw new UserError(`prefix \"${prefix}\" matches ${activeMatches.length} current findings — use a longer prefix: ${list}`);\n }\n\n if (activeMatches.length === 0) {\n // Give the more useful answer when the prefix actually matches something ALREADY accepted\n // (excluded from report.hygiene by definition) rather than the generic \"no match\" — this is\n // exactly the accept-an-already-accepted-fingerprint case.\n const alreadyMatches = resolveAmongRecords(prefix, accepted);\n if (alreadyMatches.length > 0) {\n const already = alreadyMatches[0];\n throw new UserError(`already accepted on ${shortDate(already.acceptedAt)}: ${already.reason}`);\n }\n throw new UserError(`no current finding matches \"${prefix}\" — run \\`skill-forge audit\\` and copy a fingerprint from the report`);\n }\n\n const finding = activeMatches[0];\n\n // Severity cap (§3 of the design): refuses HIGH and CRITICAL outright, no override flag. The\n // precision problem this command exists to solve lives at LOW/MEDIUM — a HIGH/CRITICAL false\n // positive is a rule bug to fix, not something to baseline.\n if (finding.severity === 'HIGH' || finding.severity === 'CRITICAL') {\n throw new UserError(\n `refusing to accept a ${finding.severity} finding (${finding.rule}) — acceptance is capped at LOW/MEDIUM, and there is no override flag. ` +\n 'A HIGH/CRITICAL false positive is a rule bug to report/fix, not something to baseline.'\n );\n }\n\n const record: AcceptedFinding = {\n fingerprint: finding.fingerprint!,\n severity: finding.severity,\n rule: finding.rule,\n target: finding.target,\n context: finding.context ?? finding.detail,\n detail: finding.detail,\n reason,\n acceptedAt: new Date().toISOString(),\n source: 'user',\n };\n\n writeAcceptedFindings([...accepted, record]);\n\n if (options.json) {\n console.log(JSON.stringify(record, null, 2));\n return;\n }\n console.log(`[skill-forge] accepted [${record.severity}] ${record.rule} — ${record.target}`);\n console.log(` fingerprint: ${record.fingerprint}`);\n console.log(` reason: ${record.reason}`);\n console.log(' this finding is excluded from active counts, but stays permanently visible under \"Accepted findings\" in every future report.');\n}\n\n/** `skill-forge finding revoke <fingerprint-prefix>`. */\nexport async function findingRevokeCommand(prefix: string, options: FindingRevokeOptions = {}): Promise<void> {\n if (prefix.length < MIN_FINGERPRINT_PREFIX_LENGTH) {\n throw new UserError(`prefix too short — use at least ${MIN_FINGERPRINT_PREFIX_LENGTH} hex chars (got \"${prefix}\")`);\n }\n\n // Resolved against the STORE, not a fresh audit run — the target finding may no longer be\n // producible (the skill was removed, the line changed), which is exactly when you want to clean\n // up a stale acceptance record.\n const { records } = readAcceptedFindings();\n const matches = resolveAmongRecords(prefix, records);\n\n if (matches.length > 1) {\n const list = matches.map((r) => `${r.rule} @ ${r.target} (${r.fingerprint})`).join('; ');\n throw new UserError(`prefix \"${prefix}\" matches ${matches.length} accepted findings — use a longer prefix: ${list}`);\n }\n if (matches.length === 0) {\n throw new UserError(`no accepted finding matches \"${prefix}\" — run \\`skill-forge finding list\\` to see what is accepted`);\n }\n\n const record = matches[0];\n writeAcceptedFindings(records.filter((r) => r.fingerprint !== record.fingerprint));\n\n if (options.json) {\n console.log(JSON.stringify(record, null, 2));\n return;\n }\n console.log(`[skill-forge] revoked [${record.severity}] ${record.rule} — ${record.target}`);\n}\n\n/** `skill-forge finding list [--stale]`. */\nexport async function findingListCommand(options: FindingListOptions = {}): Promise<void> {\n const { records, notices } = readAcceptedFindings();\n for (const notice of notices) console.error(`[skill-forge] ${notice}`);\n\n const rows = options.stale ? records.filter((r) => !existsSync(r.target)) : records;\n\n if (options.json) {\n console.log(JSON.stringify(rows, null, 2));\n return;\n }\n\n if (rows.length === 0) {\n console.log(options.stale ? '[skill-forge] no stale accepted findings.' : '[skill-forge] no accepted findings.');\n return;\n }\n\n console.log(`[skill-forge] accepted findings${options.stale ? ' (stale — target no longer exists)' : ''}:`);\n for (const r of rows) {\n console.log(\n ` [${r.severity}] ${sanitizeForMarkdown(r.rule)} — ${sanitizeForMarkdown(r.target)}: ${sanitizeForMarkdown(r.reason)} ` +\n `(fp ${r.fingerprint.slice(0, FINGERPRINT_DISPLAY_LENGTH)}, accepted ${shortDate(r.acceptedAt)})`\n );\n }\n}\n","import { writeNewFile } from '../util.js';\nimport { resolve as resolvePath } from 'node:path';\nimport { sanitizeForMarkdown } from '../audit.js';\nimport { loadConfig } from '../config.js';\nimport { buildOrganizeReport } from '../organize.js';\nimport type { OrganizeReport } from '../organize.js';\n\n/**\n * `skill-forge organize` — thin CLI wiring around the Pro capability\n * registry + dependency graph engine in src/organize.ts. This file only\n * adapts commander's options shape into a call, renders the human-readable\n * summary (sanitizing every untrusted string — names/tiers/domains/consumes\n * targets/paths — via audit.ts's `sanitizeForMarkdown`), and handles\n * `--out`/`--json`. See src/organize.ts for the actual scan/graph pipeline.\n */\nexport interface OrganizeCommandOptions {\n json?: boolean;\n /** Write the full registry+graph report JSON to this path (`wx` mode 0o600 — refuses an existing file). */\n out?: string;\n usageSnapshot?: string;\n}\n\n/** Resolves `--out` and writes it exclusively via the shared writer, returning the resolved path. */\nfunction writeReport(outPath: string, contents: string): string {\n const resolved = resolvePath(outPath);\n writeNewFile(resolved, contents, '--out');\n return resolved;\n}\n\nfunction renderSummary(report: OrganizeReport): string[] {\n const { registry, graph } = report;\n const lines: string[] = [];\n\n lines.push(`[skill-forge] organize: ${registry.count} skill(s) across ${registry.skillsRoots.length} root(s)`);\n lines.push(` by tier: ${JSON.stringify(registry.byTier)}`);\n lines.push(` by domain: ${JSON.stringify(registry.byDomain)}`);\n lines.push(` usage joined: ${registry.usageJoined}`);\n lines.push(` untagged (missing tier/domain): ${registry.untagged.length}`);\n for (const u of registry.untagged.slice(0, 30)) {\n lines.push(` - ${sanitizeForMarkdown(u.name)}: missing ${u.missing.join(', ')}`);\n }\n if (registry.warnings.length > 0) {\n lines.push(' warnings:');\n for (const w of registry.warnings) lines.push(` ! ${sanitizeForMarkdown(w)}`);\n }\n\n lines.push('');\n lines.push(` graph: ${graph.nodeCount} nodes, ${graph.edgeCount} edges`);\n if (graph.hotResources.length > 0) {\n lines.push(' hot resources (consumed by N customs):');\n for (const h of graph.hotResources) lines.push(` ${h.consumedBy}x ${sanitizeForMarkdown(h.name)}`);\n }\n if (graph.danglingEdges.length > 0) {\n lines.push(' dangling edges (consumes a target not in the set):');\n for (const d of graph.danglingEdges) lines.push(` ${sanitizeForMarkdown(d.from)} -> ${sanitizeForMarkdown(d.to)} (missing)`);\n }\n if (graph.ambiguousConsumesTargets.length > 0) {\n lines.push(' ambiguous consumes targets (name matched multiple skills):');\n for (const a of graph.ambiguousConsumesTargets) {\n lines.push(\n ` ${sanitizeForMarkdown(a.from)} -> ${sanitizeForMarkdown(a.to)} (matches: ${a.matches.map(sanitizeForMarkdown).join(', ')})`\n );\n }\n }\n if (graph.cycles.length > 0) {\n lines.push(' cycles (consumes should be acyclic):');\n for (const c of graph.cycles) lines.push(` ${c.map(sanitizeForMarkdown).join(' -> ')}`);\n }\n if (graph.orphans.length > 0) {\n lines.push(` custom skills with no declared consumes (${graph.orphans.length}):`);\n for (const o of graph.orphans.slice(0, 30)) lines.push(` - ${sanitizeForMarkdown(o)}`);\n }\n\n for (const n of report.notices) lines.push(` [notice] ${sanitizeForMarkdown(n)}`);\n return lines;\n}\n\nexport async function organizeCommand(options: OrganizeCommandOptions = {}): Promise<void> {\n const config = loadConfig();\n const report = buildOrganizeReport(config, { usageSnapshot: options.usageSnapshot });\n\n let outPath: string | undefined;\n if (options.out) {\n outPath = writeReport(options.out, JSON.stringify(report, null, 2) + '\\n');\n }\n\n if (options.json) {\n // Notices (incl. the beta notice) go to stderr — stdout stays exactly one JSON document.\n for (const n of report.notices) console.error(`[skill-forge] ${n}`);\n console.log(JSON.stringify(report, null, 2));\n return;\n }\n\n for (const line of renderSummary(report)) console.log(line);\n if (outPath) console.log(` report -> ${outPath}`);\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { existsSync, readFileSync, readdirSync, realpathSync, statSync, type Dirent } from 'node:fs';\nimport { basename, join, resolve as resolvePath } from 'node:path';\nimport type { Config } from './config.js';\nimport { UserError } from './errors.js';\nimport { proFeatureStatus, upgradeNotice } from './license.js';\nimport { expandHome, isDir, stripQuotes } from './util.js';\n\n/**\n * `skill-forge organize` engine (Pro) — builds a set-level capability registry and a\n * consumes-edge dependency graph across every configured skills root, in one command. See\n * `docs/forge-workflow.md` for the frontmatter keys this reads (`tier` / `domain` /\n * `consumes` / `provenance` / `maturity`).\n *\n * This module performs its OWN self-contained scan of `config.skillsRoots` —\n * it deliberately does NOT import `audit.ts`'s inventory walker, keeping the\n * two modules' file sets disjoint. Nodes and registry entries are keyed by\n * REALPATH-QUALIFIED stable IDs, not bare frontmatter names: two distinct\n * skills in two different roots that happen to share a `name:` must NOT\n * collapse into one node/adjacency entry. Ambiguous (`consumes` target name\n * matches more than one skill) and dangling (matches none) edges are\n * reported rather than silently guessed at.\n *\n * Nothing this module scans is ever executed — it only reads SKILL.md text\n * and (optionally) a usage-snapshot JSON file. The whole command is Pro\n * (`proFeatureStatus('set-level organizer reports')`), gated before any\n * filesystem walk, matching every other Pro entry point in this repo (see\n * src/evolve.ts). Free during the 0.x beta via `proFeatureStatus()`.\n */\n\nconst PRO_FEATURE_LABEL = 'set-level organizer reports';\n\nconst TIERS = new Set(['resource', 'custom']);\nconst MATURITIES = new Set(['seedling', 'stable', 'deprecated']);\n\nexport interface OrganizeOptions {\n /** Path to a skill-monitor-style snapshot JSON (`{ report: { top_skills: [[name, count], ...] } }`) to join usage counts from, by skill name. */\n usageSnapshot?: string;\n}\n\nexport interface OrganizeRegistryEntry {\n /** Realpath-qualified stable id — NOT the bare frontmatter name (which may collide across roots). */\n id: string;\n name: string;\n /** Realpath of the skill directory (same value as `id`). */\n path: string;\n /** The configured root (as given in config.skillsRoots) this skill was discovered under. */\n root: string;\n tier?: string;\n domain?: string;\n consumes: string[];\n provenance?: string;\n maturity?: string;\n validFrontmatter: boolean;\n usage?: number;\n}\n\nexport interface OrganizeUntaggedEntry {\n id: string;\n name: string;\n missing: string[];\n}\n\nexport interface OrganizeRegistry {\n skillsRoots: string[];\n count: number;\n byTier: Record<string, number>;\n byDomain: Record<string, number>;\n untagged: OrganizeUntaggedEntry[];\n warnings: string[];\n usageJoined: boolean;\n skills: OrganizeRegistryEntry[];\n}\n\nexport interface OrganizeGraphNode {\n id: string;\n name: string;\n tier?: string;\n domain?: string;\n}\n\nexport interface OrganizeGraphEdge {\n from: string;\n /** Raw `consumes` target name as declared in frontmatter — not yet resolved to an id. */\n to: string;\n /** ids of every skill whose name matches `to` — empty (dangling), one (resolved), or many (ambiguous). */\n resolvedIds: string[];\n}\n\nexport interface OrganizeGraphDanglingEdge {\n from: string;\n to: string;\n}\n\nexport interface OrganizeGraphAmbiguousEdge {\n from: string;\n to: string;\n matches: string[];\n}\n\nexport interface OrganizeGraphHotResource {\n id: string;\n name: string;\n consumedBy: number;\n}\n\nexport interface OrganizeGraph {\n nodeCount: number;\n edgeCount: number;\n nodes: OrganizeGraphNode[];\n edges: OrganizeGraphEdge[];\n /** ids of `tier: custom` skills with no declared `consumes`. */\n orphans: string[];\n hotResources: OrganizeGraphHotResource[];\n danglingEdges: OrganizeGraphDanglingEdge[];\n ambiguousConsumesTargets: OrganizeGraphAmbiguousEdge[];\n /** Each cycle is a sequence of ids, first === last (closing the loop) — only built from unambiguous (single-match) edges. */\n cycles: string[][];\n}\n\nexport interface OrganizeReport {\n generatedAt: string;\n registry: OrganizeRegistry;\n graph: OrganizeGraph;\n notices: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Self-contained scan (deliberately independent of audit.ts's walker)\n// ---------------------------------------------------------------------------\n\ninterface SkillCandidate {\n dirPath: string;\n root: string;\n}\n\n/** Subdirectories of `dir`, following directory-shaped symlinks. Swallows any read error (missing/permission) rather than throwing. */\nfunction listSubdirs(dir: string): string[] {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const out: string[] = [];\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n out.push(full);\n continue;\n }\n if (entry.isSymbolicLink()) {\n try {\n if (statSync(full).isDirectory()) out.push(full);\n } catch {\n // broken symlink — skip\n }\n }\n }\n return out;\n}\n\n/** Mirrors index_skills.py's three glob shapes (top-level dirs, a \"skills\" subdir, and nested \"skills\" subdirs one level down), scoped to one resolved root. */\nfunction findSkillDirsUnderRoot(root: string): string[] {\n const found = new Set<string>();\n const topDirs = listSubdirs(root);\n\n for (const dir of topDirs) {\n if (existsSync(join(dir, 'SKILL.md'))) found.add(dir);\n }\n for (const dir of listSubdirs(join(root, 'skills'))) {\n if (existsSync(join(dir, 'SKILL.md'))) found.add(dir);\n }\n for (const topDir of topDirs) {\n for (const dir of listSubdirs(join(topDir, 'skills'))) {\n if (existsSync(join(dir, 'SKILL.md'))) found.add(dir);\n }\n }\n\n return Array.from(found).filter((p) => !p.includes('/.git/'));\n}\n\nfunction collectCandidates(roots: readonly string[]): SkillCandidate[] {\n const out: SkillCandidate[] = [];\n for (const root of roots) {\n const resolvedRoot = resolvePath(expandHome(root));\n if (!isDir(resolvedRoot)) continue;\n for (const dirPath of findSkillDirsUnderRoot(resolvedRoot)) {\n out.push({ dirPath, root });\n }\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Capability frontmatter parsing — port of index_skills.py's parse_frontmatter/parse_list\n// ---------------------------------------------------------------------------\n\ninterface ParsedCapabilityFrontmatter {\n name?: string;\n tier?: string;\n domain?: string;\n provenance?: string;\n maturity?: string;\n consumes: string[];\n validFrontmatter: boolean;\n}\n\n/** Parses a YAML list value, inline (`key: [a, b]`) or block (`- a` lines) — port of index_skills.py's parse_list. */\nfunction parseYamlList(block: string, key: string): string[] {\n const inlineMatch = block.match(new RegExp(`^${key}:\\\\s*\\\\[(.*?)\\\\]\\\\s*$`, 'm'));\n if (inlineMatch) {\n return inlineMatch[1]\n .split(',')\n .map((x) => stripQuotes(x.trim()))\n .filter(Boolean);\n }\n if (!new RegExp(`^${key}:\\\\s*$`, 'm').test(block)) return [];\n\n const lines = block.split(/\\r\\n|\\r|\\n/);\n const startIdx = lines.findIndex((l) => new RegExp(`^${key}:\\\\s*$`).test(l));\n if (startIdx === -1) return [];\n\n const out: string[] = [];\n for (const line of lines.slice(startIdx + 1)) {\n const itemMatch = line.match(/^\\s*-\\s*(.+)$/);\n if (itemMatch) {\n out.push(stripQuotes(itemMatch[1].trim()));\n } else if (line.trim() === '') {\n continue;\n } else {\n break;\n }\n }\n return out;\n}\n\n/** Extracts the frontmatter block (text between the opening and closing `---` fences), or null if unfenced/unclosed. */\nfunction extractFrontmatterBlock(text: string): string | null {\n if (!text.startsWith('---\\n')) return null;\n const afterOpen = text.slice(4);\n const closeIdx = afterOpen.indexOf('\\n---');\n if (closeIdx === -1) return null;\n return afterOpen.slice(0, closeIdx);\n}\n\n/** Port of index_skills.py's parse_frontmatter, scoped to the capability-schema fields organize needs (name/tier/domain/provenance/maturity/consumes). */\nfunction parseCapabilityFrontmatter(text: string, fallbackName: string): ParsedCapabilityFrontmatter {\n const block = extractFrontmatterBlock(text);\n if (block === null) {\n return { name: fallbackName, consumes: [], validFrontmatter: false };\n }\n\n const fields: Record<string, string> = {};\n for (const key of ['name', 'tier', 'domain', 'provenance', 'maturity']) {\n const m = block.match(new RegExp(`^${key}:\\\\s*(.+)$`, 'm'));\n if (m) fields[key] = stripQuotes(m[1].trim());\n }\n\n return {\n name: fields.name ?? fallbackName,\n tier: fields.tier,\n domain: fields.domain,\n provenance: fields.provenance,\n maturity: fields.maturity,\n consumes: parseYamlList(block, 'consumes'),\n validFrontmatter: true,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Usage snapshot join — port of index_skills.py's load_usage\n// ---------------------------------------------------------------------------\n\ninterface UsageSnapshotResult {\n usage: Map<string, number>;\n warning?: string;\n}\n\n/** Best-effort join of usage counts from a skill-monitor-style snapshot's `report.top_skills`. Never throws — any failure becomes a warning and an empty map. */\nfunction loadUsageSnapshot(snapshotPath: string): UsageSnapshotResult {\n const resolvedPath = resolvePath(expandHome(snapshotPath));\n let raw: string;\n try {\n raw = readFileSync(resolvedPath, 'utf-8');\n } catch (err) {\n return { usage: new Map(), warning: `could not read usage snapshot (${(err as Error).message}); proceeding without usage` };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n return { usage: new Map(), warning: `could not parse usage snapshot (${(err as Error).message}); proceeding without usage` };\n }\n\n const usage = new Map<string, number>();\n const outer = parsed as Record<string, unknown> | null;\n const report = outer && typeof outer === 'object' ? (outer.report as Record<string, unknown> | undefined) : undefined;\n const topSkills = report && typeof report === 'object' ? report.top_skills : undefined;\n\n if (Array.isArray(topSkills)) {\n for (const row of topSkills) {\n if (Array.isArray(row) && row.length === 2 && typeof row[0] === 'string' && typeof row[1] === 'number') {\n const [name, count] = row as [string, number];\n usage.set(name, count);\n if (name.includes(':')) {\n // bare-slug fallback for `plugin:skill` names, matching index_skills.py.\n const bare = name.split(':').pop()!;\n if (!usage.has(bare)) usage.set(bare, count);\n }\n }\n }\n }\n\n return { usage };\n}\n\n// ---------------------------------------------------------------------------\n// Dependency graph — port of build_dependency_graph.py\n// ---------------------------------------------------------------------------\n\nconst WHITE = 0;\nconst GRAY = 1;\nconst BLACK = 2;\n\n/** DFS cycle detection over an id-keyed adjacency map — direct port of build_dependency_graph.py's find_cycles. Only ever called with unambiguous (single-match) edges, since an ambiguous target has no single node to traverse to. */\nfunction findCycles(adjacency: Map<string, string[]>): string[][] {\n const color = new Map<string, number>();\n for (const id of adjacency.keys()) color.set(id, WHITE);\n const stack: string[] = [];\n const cycles: string[][] = [];\n\n function dfs(u: string): void {\n color.set(u, GRAY);\n stack.push(u);\n for (const v of adjacency.get(u) ?? []) {\n if (!color.has(v)) continue; // not a node in this adjacency map\n if (color.get(v) === GRAY && stack.includes(v)) {\n const idx = stack.indexOf(v);\n cycles.push([...stack.slice(idx), v]);\n } else if (color.get(v) === WHITE) {\n dfs(v);\n }\n }\n color.set(u, BLACK);\n stack.pop();\n }\n\n for (const id of adjacency.keys()) {\n if (color.get(id) === WHITE) dfs(id);\n }\n return cycles;\n}\n\nfunction buildGraph(entries: OrganizeRegistryEntry[], nameToIds: Map<string, string[]>): OrganizeGraph {\n const nodes: OrganizeGraphNode[] = entries.map((e) => ({ id: e.id, name: e.name, tier: e.tier, domain: e.domain }));\n const edges: OrganizeGraphEdge[] = [];\n const dangling: OrganizeGraphDanglingEdge[] = [];\n const ambiguous: OrganizeGraphAmbiguousEdge[] = [];\n const incoming = new Map<string, number>(entries.map((e) => [e.id, 0]));\n const adjacency = new Map<string, string[]>(entries.map((e) => [e.id, []]));\n\n for (const e of entries) {\n for (const target of e.consumes) {\n const matches = nameToIds.get(target) ?? [];\n edges.push({ from: e.id, to: target, resolvedIds: matches });\n\n if (matches.length === 0) {\n dangling.push({ from: e.id, to: target });\n } else if (matches.length === 1) {\n incoming.set(matches[0], (incoming.get(matches[0]) ?? 0) + 1);\n adjacency.get(e.id)!.push(matches[0]);\n } else {\n ambiguous.push({ from: e.id, to: target, matches });\n }\n }\n }\n\n const orphans = entries.filter((e) => e.tier === 'custom' && e.consumes.length === 0).map((e) => e.id);\n\n const byId = new Map(entries.map((e) => [e.id, e]));\n const hotResources: OrganizeGraphHotResource[] = Array.from(incoming.entries())\n .filter(([, count]) => count > 0)\n .map(([id, count]) => ({ id, name: byId.get(id)!.name, consumedBy: count }))\n .sort((a, b) => b.consumedBy - a.consumedBy);\n\n const cycles = findCycles(adjacency);\n\n return {\n nodeCount: nodes.length,\n edgeCount: edges.length,\n nodes,\n edges,\n orphans,\n hotResources,\n danglingEdges: dangling,\n ambiguousConsumesTargets: ambiguous,\n cycles,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Top-level engine\n// ---------------------------------------------------------------------------\n\n/** Resolves each candidate to a deduped, realpath-qualified registry entry. Read/resolve failures become warnings, not thrown errors. */\nfunction buildRegistryEntries(candidates: SkillCandidate[], warnings: string[]): OrganizeRegistryEntry[] {\n const entries: OrganizeRegistryEntry[] = [];\n const seenRealPaths = new Set<string>();\n\n for (const candidate of candidates) {\n let realPath: string;\n try {\n realPath = realpathSync(candidate.dirPath);\n } catch (err) {\n warnings.push(`could not resolve skill directory ${candidate.dirPath}: ${(err as Error).message}`);\n continue;\n }\n if (seenRealPaths.has(realPath)) continue;\n seenRealPaths.add(realPath);\n\n const skillMdPath = join(realPath, 'SKILL.md');\n let text: string;\n try {\n text = readFileSync(skillMdPath, 'utf-8');\n } catch (err) {\n warnings.push(`could not read ${skillMdPath}: ${(err as Error).message}`);\n continue;\n }\n\n const fm = parseCapabilityFrontmatter(text, basename(realPath));\n const name = fm.name ?? basename(realPath);\n if (fm.tier && !TIERS.has(fm.tier)) {\n warnings.push(`${name}: tier '${fm.tier}' not in ${JSON.stringify(Array.from(TIERS).sort())}`);\n }\n if (fm.maturity && !MATURITIES.has(fm.maturity)) {\n warnings.push(`${name}: maturity '${fm.maturity}' not in ${JSON.stringify(Array.from(MATURITIES).sort())}`);\n }\n\n entries.push({\n id: realPath,\n name,\n path: realPath,\n root: candidate.root,\n tier: fm.tier,\n domain: fm.domain,\n consumes: fm.consumes,\n provenance: fm.provenance,\n maturity: fm.maturity,\n validFrontmatter: fm.validFrontmatter,\n });\n }\n\n return entries;\n}\n\n/** Joins usage counts from a snapshot onto `entries` by name (mutates entries in place). Returns whether a non-empty snapshot was loaded. */\nfunction joinUsageSnapshot(entries: OrganizeRegistryEntry[], snapshotPath: string, warnings: string[]): boolean {\n const { usage, warning } = loadUsageSnapshot(snapshotPath);\n if (warning) warnings.push(warning);\n if (usage.size === 0) return false;\n\n for (const entry of entries) {\n const count = usage.get(entry.name);\n if (count !== undefined) entry.usage = count;\n }\n if (!entries.some((e) => e.usage !== undefined)) {\n warnings.push(\n 'usage snapshot loaded but 0 skills matched by name — registry `name:` vs telemetry key mismatch; the usage column will be empty'\n );\n }\n return true;\n}\n\n/** Groups entry ids by name, warning on any name shared across >1 id (e.g. duplicate skill names across roots). */\nfunction buildNameToIds(entries: OrganizeRegistryEntry[], warnings: string[]): Map<string, string[]> {\n const nameToIds = new Map<string, string[]>();\n for (const e of entries) {\n const ids = nameToIds.get(e.name) ?? [];\n ids.push(e.id);\n nameToIds.set(e.name, ids);\n }\n for (const [name, ids] of nameToIds) {\n if (ids.length > 1) {\n warnings.push(`duplicate skill name \"${name}\" across ${ids.length} locations: ${ids.join(', ')}`);\n }\n }\n return nameToIds;\n}\n\n/** Entries missing `tier` and/or `domain` — rot detection. */\nfunction findUntagged(entries: OrganizeRegistryEntry[]): OrganizeUntaggedEntry[] {\n return entries\n .filter((e) => !e.tier || !e.domain)\n .map((e) => ({\n id: e.id,\n name: e.name,\n missing: [!e.tier ? 'tier' : undefined, !e.domain ? 'domain' : undefined].filter((v): v is string => Boolean(v)),\n }));\n}\n\n/** Entry counts grouped by tier and by domain, with `undefined` bucketed as `'untagged'`. */\nfunction countByTierAndDomain(entries: OrganizeRegistryEntry[]): { byTier: Record<string, number>; byDomain: Record<string, number> } {\n const byTier: Record<string, number> = {};\n const byDomain: Record<string, number> = {};\n for (const e of entries) {\n byTier[e.tier ?? 'untagged'] = (byTier[e.tier ?? 'untagged'] ?? 0) + 1;\n byDomain[e.domain ?? 'untagged'] = (byDomain[e.domain ?? 'untagged'] ?? 0) + 1;\n }\n return { byTier, byDomain };\n}\n\n/**\n * Builds the capability registry + dependency graph for every skill under\n * `config.skillsRoots`. Pro-gated BEFORE any filesystem walk — a locked\n * license (post-1.0, no key) throws a UserError and never touches disk.\n * Never executes anything it scans.\n */\nexport function buildOrganizeReport(config: Config, options: OrganizeOptions = {}): OrganizeReport {\n const proStatus = proFeatureStatus(PRO_FEATURE_LABEL);\n if (!proStatus.enabled) {\n throw new UserError(proStatus.notice ?? upgradeNotice(PRO_FEATURE_LABEL));\n }\n const notices: string[] = proStatus.notice ? [proStatus.notice] : [];\n const warnings: string[] = [];\n\n const candidates = collectCandidates(config.skillsRoots);\n if (candidates.length === 0) {\n throw new UserError(\n `no SKILL.md found under any configured skills root (${config.skillsRoots.join(', ') || '(none configured)'})`\n );\n }\n\n const entries = buildRegistryEntries(candidates, warnings);\n\n const usageJoined = options.usageSnapshot ? joinUsageSnapshot(entries, options.usageSnapshot, warnings) : false;\n\n const nameToIds = buildNameToIds(entries, warnings);\n const untagged = findUntagged(entries);\n const { byTier, byDomain } = countByTierAndDomain(entries);\n\n const registry: OrganizeRegistry = {\n skillsRoots: [...config.skillsRoots],\n count: entries.length,\n byTier,\n byDomain,\n untagged,\n warnings,\n usageJoined,\n skills: entries,\n };\n\n const graph = buildGraph(entries, nameToIds);\n\n return {\n generatedAt: new Date().toISOString(),\n registry,\n graph,\n notices,\n };\n}\n","/**\n * skillsShApi.ts — hardened HTTP client for the skills.sh discovery/audit API\n * (https://skills.sh/docs/api, base https://skills.sh/api/v1). TypeScript port of\n * the free-tier `skills_sh.py` script (rhize-plugins repo) — MIT, no commercial header.\n *\n * Hardening (all required by the Lane B1 spec, see .claude/plans/forge-plugin-merge.md):\n * - every URL is built from a fixed origin constant via `new URL` + `URLSearchParams`\n * - candidate ids (\"{source}/{slug}\") are validated against a strict charset and each\n * path segment is individually `encodeURIComponent`-encoded (slashes preserved)\n * - every request uses `redirect: 'error'` and a 15s `AbortSignal.timeout`\n * - response bodies are capped at 2 MB (checked before JSON.parse) and the parsed shape\n * is validated before use — malformed/oversized bodies fail loud, never partially used\n * - the bearer token is read from `process.env.VERCEL_OIDC_TOKEN` only, is never logged,\n * echoed, or included in any thrown error message\n * - `fetch` is injectable so tests never hit the network (mirrors the `ExecFn` DI idiom\n * in src/install/quarantine.ts)\n */\nexport const SKILLS_SH_ORIGIN = 'https://skills.sh';\nexport const SKILLS_SH_API_BASE = '/api/v1';\nconst MAX_RESPONSE_BYTES = 2 * 1024 * 1024; // 2 MB\nconst REQUEST_TIMEOUT_MS = 15_000;\nconst ID_PATTERN = /^[A-Za-z0-9._/-]+$/;\n\nexport const TOKEN_HINT =\n 'No VERCEL_OIDC_TOKEN found. skills.sh authenticates with a short-lived Vercel OIDC token:\\n' +\n ' 1. Vercel dashboard -> your project -> Settings -> OIDC Federation -> enable.\\n' +\n ' 2. npm i -g vercel && vercel link && vercel env pull (writes VERCEL_OIDC_TOKEN to .env.local)\\n' +\n ' 3. export VERCEL_OIDC_TOKEN from .env.local into your shell before running this command\\n' +\n ' (or set it directly: export VERCEL_OIDC_TOKEN=...) - docs: https://skills.sh/docs/api';\n\n/** Thrown for missing token / hostile id / oversized or malformed body / unexpected response shape. Callers set `process.exitCode` from `.exitCode`. */\nexport class SkillsShError extends Error {\n exitCode: number;\n constructor(message: string, exitCode: number) {\n super(message);\n this.name = 'SkillsShError';\n this.exitCode = exitCode;\n }\n}\n\nexport type FetchFn = typeof fetch;\n\nexport interface SkillsShSearchItem {\n id: string;\n name?: string;\n installs?: number | string;\n installUrl?: string;\n [key: string]: unknown;\n}\n\nexport interface SkillsShSearchResponse {\n data: SkillsShSearchItem[];\n count?: number;\n searchType?: string;\n [key: string]: unknown;\n}\n\nexport interface SkillsShAuditEntry {\n provider?: string;\n status?: string;\n riskLevel?: string;\n summary?: string;\n [key: string]: unknown;\n}\n\nexport interface SkillsShAuditResponse {\n audits: SkillsShAuditEntry[];\n [key: string]: unknown;\n}\n\nconst RISK_ORDER: Record<string, number> = { NONE: 0, LOW: 1, MEDIUM: 2, HIGH: 3, CRITICAL: 4 };\n\n/** Reads the OIDC token from the environment only — never from a config file or CLI flag, so it's never persisted or accidentally echoed by `status`/`--json`. */\nexport function getToken(env: NodeJS.ProcessEnv = process.env): string | undefined {\n const tok = env.VERCEL_OIDC_TOKEN;\n return tok ? tok : undefined;\n}\n\n/** Validates a skills.sh candidate id (\"{source}/{slug}\", e.g. \"vercel-labs/skills/find-skills\") against a strict charset before it ever reaches URL construction. */\nfunction assertValidId(id: string): void {\n if (id.length === 0 || !ID_PATTERN.test(id)) {\n throw new SkillsShError(`invalid skills.sh id: ${JSON.stringify(id)}`, 1);\n }\n}\n\n/**\n * Validates `id` and splits it into path segments — the id must never reach `call()` unvalidated.\n * Beyond the charset check, rejects empty, `.` and `..` segments: WHATWG URL parsing normalizes\n * dot-segments on the ASSEMBLED path (after per-segment encoding), so `../..` in an id would\n * otherwise walk the request out of the /api/v1 namespace while still carrying the bearer token.\n */\nfunction validatedIdSegments(id: string): string[] {\n assertValidId(id);\n const segments = id.split('/');\n if (segments.some((s) => s === '' || s === '.' || s === '..')) {\n throw new SkillsShError(`invalid skills.sh id: ${JSON.stringify(id)}`, 1);\n }\n return segments;\n}\n\n/** Builds a same-origin skills.sh API URL from a fixed origin constant, encoding each path segment individually so slashes in ids stay structural rather than escaped. */\nfunction buildUrl(pathSegments: string[], params?: Record<string, string | number>): URL {\n const url = new URL(SKILLS_SH_API_BASE + '/' + pathSegments.map((s) => encodeURIComponent(s)).join('/'), SKILLS_SH_ORIGIN);\n if (params) {\n const search = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) search.set(k, String(v));\n url.search = search.toString();\n }\n return url;\n}\n\n/** Reads a Response body as text, refusing anything over MAX_RESPONSE_BYTES before it's ever handed to JSON.parse. */\nasync function readCappedText(res: Response): Promise<string> {\n const text = await res.text();\n // TextEncoder gives the actual byte length (UTF-8), not just JS string length.\n if (new TextEncoder().encode(text).length > MAX_RESPONSE_BYTES) {\n throw new SkillsShError('skills.sh response exceeded the 2 MB size cap', 1);\n }\n return text;\n}\n\ninterface CallOptions {\n fetchImpl?: FetchFn;\n env?: NodeJS.ProcessEnv;\n}\n\n/**\n * Core authenticated GET against the skills.sh API. Returns `undefined` for a 404\n * (matches skills_sh.py's `return None` for \"not found yet\"). Never echoes the raw\n * response body of a non-2xx status back to the caller — only a truncated, token-free\n * snippet — so credentials or internal detail can't leak through an error message.\n */\nasync function call(pathSegments: string[], params: Record<string, string | number> | undefined, options: CallOptions = {}): Promise<unknown> {\n const env = options.env ?? process.env;\n const token = getToken(env);\n if (!token) {\n throw new SkillsShError(TOKEN_HINT, 3);\n }\n\n const url = buildUrl(pathSegments, params);\n const fetchImpl = options.fetchImpl ?? fetch;\n\n let res: Response;\n try {\n res = await fetchImpl(url, {\n headers: { Authorization: `Bearer ${token}` },\n redirect: 'error',\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new SkillsShError(`skills.sh request failed: ${message}`, 1);\n }\n\n if (res.status === 404) {\n return undefined;\n }\n\n if (!res.ok) {\n // 401 never echoes the response body — an auth-failure body can carry request\n // detail we don't want surfaced (and never the token, but keep the margin wide).\n if (res.status === 401) {\n throw new SkillsShError('skills.sh HTTP 401: unauthorized — the VERCEL_OIDC_TOKEN was rejected (expired or invalid)', 3);\n }\n let snippet = '';\n try {\n snippet = (await readCappedText(res)).slice(0, 300);\n } catch {\n // ignore body-read failures on an already-erroring response\n }\n throw new SkillsShError(`skills.sh HTTP ${res.status}${snippet ? `: ${snippet}` : ''}`, 1);\n }\n\n const text = await readCappedText(res);\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new SkillsShError('skills.sh returned malformed JSON', 1);\n }\n return parsed;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/** `GET /skills/search?q=&limit=` — validates the response has a `data` array before returning it. */\nexport async function search(query: string, limit: number, options: CallOptions = {}): Promise<SkillsShSearchResponse> {\n const data = await call(['skills', 'search'], { q: query, limit }, options);\n if (!isRecord(data) || !Array.isArray(data.data)) {\n throw new SkillsShError('skills.sh search response had an unexpected shape', 1);\n }\n return data as unknown as SkillsShSearchResponse;\n}\n\n/** `GET /skills/audit/{id}` — `undefined` means no partner audits exist yet for this id. */\nexport async function audit(id: string, options: CallOptions = {}): Promise<SkillsShAuditResponse | undefined> {\n const data = await call(['skills', 'audit', ...validatedIdSegments(id)], undefined, options);\n if (data === undefined) return undefined;\n if (!isRecord(data) || !Array.isArray(data.audits)) {\n throw new SkillsShError('skills.sh audit response had an unexpected shape', 1);\n }\n return data as unknown as SkillsShAuditResponse;\n}\n\n/** `GET /skills/{id}` — full detail + file tree, returned raw (only used for `--json`/get output). */\nexport async function get(id: string, options: CallOptions = {}): Promise<unknown> {\n return call(['skills', ...validatedIdSegments(id)], undefined, options);\n}\n\n/** `GET /skills/curated`. */\nexport async function curated(options: CallOptions = {}): Promise<unknown> {\n return call(['skills', 'curated'], undefined, options);\n}\n\n/** Aggregates a `SkillsShAuditResponse` into (verdict, worst risk level, audits) — port of skills_sh.py's `audit_verdict`. */\nexport function auditVerdict(data: SkillsShAuditResponse): { verdict: 'fail' | 'warn' | 'pass' | 'none'; risk: string; audits: SkillsShAuditEntry[] } {\n const audits = data.audits ?? [];\n const statuses = new Set(audits.map((a) => a.status));\n let worst = 'NONE';\n let worstRank = 0;\n for (const a of audits) {\n const rl = a.riskLevel ?? 'NONE';\n const rank = RISK_ORDER[rl] ?? 0;\n if (rank > worstRank) {\n worstRank = rank;\n worst = rl;\n }\n }\n const verdict: 'fail' | 'warn' | 'pass' | 'none' = statuses.has('fail')\n ? 'fail'\n : statuses.has('warn')\n ? 'warn'\n : audits.length > 0\n ? 'pass'\n : 'none';\n return { verdict, risk: worst, audits };\n}\n","import { sanitizeForMarkdown } from '../audit.js';\nimport { UserError } from '../errors.js';\nimport {\n audit as apiAudit,\n auditVerdict,\n curated as apiCurated,\n get as apiGet,\n search as apiSearch,\n SkillsShError,\n type FetchFn,\n type SkillsShSearchItem,\n} from '../gate/skillsShApi.js';\n\nexport interface FindOptions {\n limit?: number;\n json?: boolean;\n audit?: string;\n get?: string;\n curated?: boolean;\n}\n\nexport interface FindDeps {\n fetchImpl?: FetchFn;\n env?: NodeJS.ProcessEnv;\n}\n\nconst INSTALL_HINT_NOTE =\n 'Results are unvetted — `skill-forge add <id>` runs the full quarantine + safety gate; ' +\n '`skill-forge find --audit <id>` shows partner audit verdicts.';\n\nfunction isRecordWithDataArray(v: unknown): v is { data: SkillsShSearchItem[]; count?: number; searchType?: string } {\n return typeof v === 'object' && v !== null && Array.isArray((v as { data?: unknown }).data);\n}\n\n/** Emits exactly one JSON document on stdout — the `--json` contract shared by every mode. */\nfunction printJson(data: unknown): void {\n console.log(JSON.stringify(data, null, 2));\n}\n\nfunction printSearchResults(data: { data: SkillsShSearchItem[]; count?: number; searchType?: string }, query: string): void {\n const items = data.data;\n const count = data.count ?? items.length;\n const searchType = data.searchType ? ` (${sanitizeForMarkdown(data.searchType)})` : '';\n console.log(`skills.sh — ${count} matches for ${JSON.stringify(query)}${searchType}:`);\n for (const item of items) {\n const name = sanitizeForMarkdown(String(item.name ?? item.id));\n const id = sanitizeForMarkdown(String(item.id));\n const installs = item.installs ?? '?';\n console.log(` - ${name} [${id}] ${installs} installs [unvetted]`);\n console.log(` install: skill-forge add ${id}`);\n }\n console.log('');\n console.log(INSTALL_HINT_NOTE);\n}\n\n/**\n * `skill-forge find` — TypeScript port of the plugin's `skills_sh.py`. Exactly one mode\n * per invocation: a search query (default), `--audit <id>`, `--get <id>`, or `--curated`.\n * All requests hit https://skills.sh/api/v1 over HTTPS with a `VERCEL_OIDC_TOKEN` bearer\n * token (never printed); a missing/invalid token fails loud with setup guidance and\n * `process.exitCode = 3` before any request is made. Search/curated/audit results are\n * always labeled unvetted — the deep quarantine + safety gate only runs on `add`.\n */\nexport async function find(query: string | undefined, options: FindOptions = {}, deps: FindDeps = {}): Promise<void> {\n const modes = [query !== undefined, options.audit !== undefined, options.get !== undefined, Boolean(options.curated)];\n const activeCount = modes.filter(Boolean).length;\n if (activeCount === 0) {\n throw new UserError('skill-forge find: provide a search query, or exactly one of --audit <id>, --get <id>, --curated');\n }\n if (activeCount > 1) {\n throw new UserError('skill-forge find: exactly one mode per invocation — a search query, or exactly one of --audit <id>, --get <id>, --curated');\n }\n\n const callOptions = { fetchImpl: deps.fetchImpl, env: deps.env };\n\n try {\n if (query !== undefined) {\n if (query.trim().length < 2) {\n throw new UserError('skill-forge find: query must be at least 2 characters');\n }\n const limit = options.limit ?? 10;\n const data = await apiSearch(query, limit, callOptions);\n if (options.json) {\n printJson(data);\n return;\n }\n printSearchResults(data, query);\n return;\n }\n\n if (options.audit !== undefined) {\n const id = options.audit;\n const data = await apiAudit(id, callOptions);\n if (!data) {\n console.log(`no partner audits yet for ${sanitizeForMarkdown(id)} (generated after first install).`);\n return;\n }\n if (options.json) {\n printJson(data);\n return;\n }\n const { verdict, risk, audits } = auditVerdict(data);\n console.log(\n `skills.sh audit for ${sanitizeForMarkdown(id)}: ${verdict.toUpperCase()} (max risk ${sanitizeForMarkdown(risk)}) — ${audits.length} partner(s)`\n );\n for (const entry of audits) {\n const provider = sanitizeForMarkdown(String(entry.provider ?? 'unknown'));\n const status = sanitizeForMarkdown(String(entry.status ?? 'unknown'));\n const riskLevel = entry.riskLevel ? ` · ${sanitizeForMarkdown(String(entry.riskLevel))}` : '';\n const summary = entry.summary ? ` — ${sanitizeForMarkdown(String(entry.summary))}` : '';\n console.log(` - ${provider}: ${status}${riskLevel}${summary}`);\n }\n return;\n }\n\n if (options.get !== undefined) {\n const data = await apiGet(options.get, callOptions);\n printJson(data);\n return;\n }\n\n // --curated\n const data = await apiCurated(callOptions);\n if (options.json) {\n printJson(data);\n return;\n }\n if (isRecordWithDataArray(data)) {\n printSearchResults(data, '(curated)');\n return;\n }\n printJson(data);\n } catch (err) {\n if (err instanceof SkillsShError) {\n console.error(err.message);\n process.exitCode = err.exitCode;\n return;\n }\n throw err;\n }\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { realpathSync } from 'node:fs';\nimport { join, resolve as resolvePath } from 'node:path';\nimport { sanitizeForMarkdown } from '../audit.js';\nimport { loadConfig, type Config } from '../config.js';\nimport { defaultExec } from '../install/quarantine.js';\nimport type { ExecFn } from '../install/quarantine.js';\nimport { proFeatureStatus } from '../license.js';\nimport { readProvenanceLedger } from '../provenance.js';\nimport { expandHome, isDir } from '../util.js';\nimport { loadSkillMap } from '../gate/skillMap.js';\nimport { computeMapDrift, type MapDriftRow } from '../gate/skillMapDrift.js';\n\n/**\n * `skill-forge watch` (Pro) — TS port of `record_provenance.py --check-drift`. Scans EVERY\n * configured skills root's `SOURCES.md` provenance ledger (src/provenance.ts's\n * `readProvenanceLedger`/`parseProvenanceLedger`) and reports, per tracked (non-REJECT) entry,\n * whether its recorded upstream ref still matches what the source currently resolves to.\n *\n * HARD SECURITY INVARIANT: the ledger's stored `Drift check` command string is\n * attacker-influenceable data (anyone who can write to a skills root's SOURCES.md controls it) —\n * it is NEVER executed, under any circumstances. It is only ever printed, sanitized, as a\n * suggestion for the human/agent to run themselves. The only subprocess this module ever spawns\n * is a fixed, first-party `git ls-remote <url> [ref]` invocation (argv array, no shell), and only\n * for entries whose Source is recognizably a git URL and whose Upstream ref looks like a git ref\n * — see `test/watch.test.ts`'s guard test asserting no spawn happens for anything else.\n */\n\nconst PRO_FEATURE_LABEL = 'provenance drift watch (watch)';\n\nexport type DriftCheckMethod = 'git-ls-remote' | 'manual';\nexport type DriftStatus = 'moved' | 'unchanged' | 'unreachable' | 'not-checked';\n\nexport interface WatchRow {\n /** The configured root (raw, as given in config.skillsRoots) this entry's ledger belongs to. */\n root: string;\n ledgerPath: string;\n name: string;\n verb: string;\n source: string;\n upstreamRef: string;\n checkMethod: DriftCheckMethod;\n driftStatus: DriftStatus;\n detail: string;\n /** Stored drift-check command string, as recorded — DATA ONLY, never executed. See module doc comment. */\n driftCheckCommand: string;\n}\n\nexport interface WatchedRoot {\n root: string;\n realPath: string;\n exists: boolean;\n ledgerPath: string;\n ledgerExists: boolean;\n}\n\nexport interface WatchReport {\n roots: WatchedRoot[];\n rows: WatchRow[];\n /** Ledger parse warnings (malformed blocks skipped) — see provenance.ts's `parseProvenanceLedger`. */\n warnings: string[];\n notices: string[];\n /**\n * Drift results for every `fork-of` edge in the skill map passed via `--skill-map` (Phase 4,\n * `skill-map-graph-substrate.md`) — empty when `--skill-map` wasn't given. See\n * `gate/skillMapDrift.ts`'s module doc comment for the never-execute invariant this shares\n * with the SOURCES.md-ledger drift check above.\n */\n mapDrift: MapDriftRow[];\n}\n\nexport interface WatchOptions {\n json?: boolean;\n /** Skip all network calls — every entry is reported `not-checked` for manual comparison instead. */\n offline?: boolean;\n /** Path to the generated `rhize-plugins` skill map — when given, also runs the fork-of drift check against its edges (Phase 4). */\n skillMap?: string;\n}\n\nexport interface WatchDeps {\n /** Injected subprocess runner for `git ls-remote`. Defaults to a real argv-array spawn (no shell). */\n exec?: ExecFn;\n}\n\n/** True for an `https://`/`git@` URL — the only source shapes `git ls-remote` can be pointed at. */\nfunction isGitUrl(source: string): boolean {\n const s = source.trim();\n return /^https?:\\/\\/\\S+$/i.test(s) || /^git@[\\w.-]+:\\S+$/i.test(s);\n}\n\n/** True when `ref` is a plausible git ref token (commit sha or tag/branch name) — no whitespace or shell metacharacters, not empty, not the ledger's \"n/a\" placeholder. */\n/**\n * Only a commit-sha pin can be drift-compared against `git ls-remote` output — the remote\n * always resolves to a sha, so a tag/version-string pin (e.g. `v1.2.0`, or a frontmatter\n * version recorded by `add`) would compare a name against a sha and report \"moved\" on every\n * run. Non-sha refs route to the manual lane instead of getting a false verdict.\n */\nfunction looksLikeGitRef(ref: string): boolean {\n const r = ref.trim();\n return /^[0-9a-fA-F]{7,40}$/.test(r);\n}\n\nfunction firstNonEmptyLine(text: string): string {\n return text.split('\\n').find((l) => l.trim().length > 0)?.trim() ?? '(no output)';\n}\n\n/**\n * Runs `git ls-remote -- <url> <ref>` (fixed argv, no shell) and compares the resolved sha\n * against the recorded `ref`. This is the ONLY subprocess this module ever spawns, and it never\n * touches the ledger's stored `driftCheck` field.\n */\nasync function checkGitDrift(exec: ExecFn, url: string, ref: string): Promise<{ status: DriftStatus; detail: string }> {\n let result;\n try {\n result = await exec('git', ['ls-remote', '--', url, ref], { cwd: process.cwd() });\n } catch (err) {\n return { status: 'unreachable', detail: `git ls-remote failed to run: ${(err as Error).message}` };\n }\n if (result.code !== 0) {\n return { status: 'unreachable', detail: `git ls-remote exited ${result.code}: ${firstNonEmptyLine(result.stderr || result.stdout)}` };\n }\n // An annotated tag returns two lines (tag object + peeled `^{}` commit) — compare the\n // recorded ref against EVERY returned sha so a peeled-commit pin isn't a false \"moved\".\n const shas = result.stdout\n .split('\\n')\n .map((l) => l.trim())\n .filter((l) => l.length > 0)\n .map((l) => l.split(/\\s+/)[0] ?? '')\n .filter((sha) => sha.length > 0);\n if (shas.length === 0) {\n return { status: 'unreachable', detail: `no ref matching \"${ref}\" found on remote` };\n }\n const recorded = ref.trim().toLowerCase();\n if (shas.some((sha) => sha.toLowerCase() === recorded)) {\n return { status: 'unchanged', detail: `current: ${recorded}` };\n }\n return { status: 'moved', detail: `recorded ${ref}, remote now resolves to ${shas[0]}` };\n}\n\n/**\n * Scans every configured skills root's provenance ledger and classifies each tracked\n * (non-REJECT) entry's drift-check status. Pro-gated BEFORE any file read — a locked license\n * returns an empty report with a notice and touches nothing on disk. `config` is an explicit\n * parameter (rather than this function calling `loadConfig()` itself) so tests can point it at a\n * fixture set of skills roots without touching `~/.skill-forge/config.json` — mirrors\n * `auditConfiguredSet(config, options)`'s shape in src/audit.ts. `watchCommand` below is the CLI\n * entry point that supplies the real, loaded config.\n */\nexport async function runWatch(config: Config, options: WatchOptions = {}, deps: WatchDeps = {}): Promise<WatchReport> {\n const proStatus = proFeatureStatus(PRO_FEATURE_LABEL);\n if (!proStatus.enabled) {\n return { roots: [], rows: [], warnings: [], notices: [proStatus.notice ?? 'Pro feature (watch) locked.'], mapDrift: [] };\n }\n\n const notices: string[] = proStatus.notice ? [proStatus.notice] : [];\n const exec = deps.exec ?? defaultExec;\n\n const roots: WatchedRoot[] = [];\n const rows: WatchRow[] = [];\n const warnings: string[] = [];\n const seenRealPaths = new Set<string>();\n\n for (const root of config.skillsRoots) {\n const resolvedRoot = resolvePath(expandHome(root));\n const exists = isDir(resolvedRoot);\n let realPath = resolvedRoot;\n if (exists) {\n try {\n realPath = realpathSync(resolvedRoot);\n } catch {\n realPath = resolvedRoot;\n }\n }\n if (seenRealPaths.has(realPath)) continue;\n seenRealPaths.add(realPath);\n\n // Per-root failure tolerance (mirrors audit.ts's per-item philosophy): an unreadable\n // ledger in one root becomes a warning, never an abort of the whole drift check.\n let ledger: ReturnType<typeof readProvenanceLedger>;\n try {\n ledger = readProvenanceLedger(resolvedRoot);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const ledgerPath = join(resolvedRoot, 'SOURCES.md');\n roots.push({ root, realPath, exists, ledgerPath, ledgerExists: false });\n warnings.push(`${ledgerPath}: could not read ledger: ${message}`);\n continue;\n }\n roots.push({ root, realPath, exists, ledgerPath: ledger.ledgerPath, ledgerExists: ledger.exists });\n warnings.push(...ledger.warnings.map((w) => `${ledger.ledgerPath}: ${w}`));\n\n for (const entry of ledger.entries) {\n // Mirrors record_provenance.py's check-drift: a rejected source isn't tracked going forward.\n if (entry.verb === 'REJECT') continue;\n\n const rowBase = {\n root,\n ledgerPath: ledger.ledgerPath,\n name: entry.name,\n verb: entry.verb,\n source: entry.source,\n upstreamRef: entry.upstreamRef,\n driftCheckCommand: entry.driftCheck,\n };\n\n if (!(isGitUrl(entry.source) && looksLikeGitRef(entry.upstreamRef))) {\n rows.push({\n ...rowBase,\n checkMethod: 'manual',\n driftStatus: 'not-checked',\n detail: 'source is not a recognized git URL + ref pin — check manually',\n });\n continue;\n }\n\n if (options.offline) {\n rows.push({\n ...rowBase,\n checkMethod: 'git-ls-remote',\n driftStatus: 'not-checked',\n detail: '--offline: network check skipped',\n });\n continue;\n }\n\n const { status, detail } = await checkGitDrift(exec, entry.source, entry.upstreamRef);\n rows.push({ ...rowBase, checkMethod: 'git-ls-remote', driftStatus: status, detail });\n }\n }\n\n let mapDrift: MapDriftRow[] = [];\n if (options.skillMap) {\n try {\n const map = loadSkillMap(options.skillMap);\n mapDrift = await computeMapDrift(map, {\n mapPath: options.skillMap,\n ...(options.offline ? { fetchUpstream: async () => undefined } : {}),\n });\n } catch (err) {\n warnings.push(`skill map at ${options.skillMap}: ${(err as Error).message}`);\n }\n }\n\n return { roots, rows, warnings, notices, mapDrift };\n}\n\nfunction printWatchTerminal(report: WatchReport): void {\n console.log('[skill-forge] watch — provenance drift check');\n for (const notice of report.notices) console.log(`[notice] ${notice}`);\n\n if (report.roots.length === 0) {\n console.log('\\nNo configured skills roots.');\n }\n\n for (const root of report.roots) {\n console.log(`\\n${sanitizeForMarkdown(root.root)} (${sanitizeForMarkdown(root.realPath)})`);\n if (!root.exists) {\n console.log(' root does not exist on disk');\n continue;\n }\n if (!root.ledgerExists) {\n console.log(` no ledger at ${sanitizeForMarkdown(root.ledgerPath)}`);\n continue;\n }\n console.log(` ledger: ${sanitizeForMarkdown(root.ledgerPath)}`);\n\n const rowsForRoot = report.rows.filter((r) => r.root === root.root);\n if (rowsForRoot.length === 0) {\n console.log(' (no tracked, non-REJECT sources)');\n continue;\n }\n for (const row of rowsForRoot) {\n console.log(` • ${sanitizeForMarkdown(row.name)} [${sanitizeForMarkdown(row.verb)}] ref=${sanitizeForMarkdown(row.upstreamRef)}`);\n if (row.checkMethod === 'git-ls-remote') {\n console.log(` drift: ${row.driftStatus} — ${sanitizeForMarkdown(row.detail)}`);\n } else {\n console.log(` manual check — stored drift-check command (data, NOT executed): ${sanitizeForMarkdown(row.driftCheckCommand)}`);\n }\n }\n }\n\n if (report.warnings.length > 0) {\n console.log('\\nWarnings:');\n for (const w of report.warnings) console.log(` - ${sanitizeForMarkdown(w)}`);\n }\n\n if (report.mapDrift.length > 0) {\n console.log('\\nSkill-map fork-of drift:');\n for (const row of report.mapDrift) {\n console.log(` • ${sanitizeForMarkdown(row.skill)} <- ${sanitizeForMarkdown(row.upstream)}`);\n console.log(` ${row.status} — ${sanitizeForMarkdown(row.detail)}`);\n }\n }\n\n console.log('\\nCompare each stored ref to current upstream; re-run ingestion on any that moved.');\n}\n\n/**\n * `skill-forge watch` CLI entry point. `--json` prints exactly one JSON document on stdout;\n * notices go to stderr in that mode so stdout stays parseable.\n */\nexport async function watchCommand(options: WatchOptions = {}): Promise<void> {\n const report = await runWatch(loadConfig(), options);\n\n if (options.json) {\n console.log(JSON.stringify(report, null, 2));\n for (const notice of report.notices) console.error(`[skill-forge] ${notice}`);\n return;\n }\n\n printWatchTerminal(report);\n}\n","import { createHash } from 'node:crypto';\nimport { readFileSync, existsSync } from 'node:fs';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport { expandHome } from '../util.js';\nimport { edgesOfType, nodeById, type SkillMap, type SkillMapNode } from './skillMap.js';\n\n/**\n * Skill-map drift checks — the map-graph counterpart of `commands/watch.ts`'s SOURCES.md\n * ledger drift check, driven by the map's `fork-of` edges instead of a per-root SOURCES.md.\n *\n * When both the local `skill` node's `contentHashNormalized` and the upstream `external`\n * node's `baselineHash` are present, this emits the four-state (baseline-aware) verdict —\n * `in-sync` / `local-only` / `upstream-moved` / `diverged` — from\n * docs/superpowers/specs/2026-08-10-three-way-drift-design.md (rhize-plugins repo), instead\n * of today's two-way `in-sync` / `drifted` compare. Either field missing falls back to the\n * two-way compare unchanged, so older maps keep working.\n *\n * HARD SECURITY INVARIANT (same rule as watch.ts's module doc comment, and the plan's\n * Phase-4 \"never execute\" rule): nothing derived from the skill map, from a fork-of edge's\n * `driftCheck` metadata, or from fetched upstream content is ever executed as a command,\n * script, or shell string. The ONLY operations this module performs are: (1) read a local\n * file from disk, (2) fetch a remote URL's raw bytes over HTTP(S), (3) hash bytes with\n * sha256, (4) compare hashes/text. See `test/skillMapDrift.test.ts`'s guard test asserting\n * `driftCheck` fields are never read for anything but display.\n */\n\nexport type MapDriftStatus =\n | 'in-sync'\n | 'drifted'\n | 'local-only'\n | 'upstream-moved'\n | 'diverged'\n | 'upstream-unreachable'\n | 'local-missing';\n\nexport interface MapDriftRow {\n skill: string;\n upstream: string;\n status: MapDriftStatus;\n detail: string;\n /** Whether this row needs human attention — derived from `status` via `isActionable`, so consumers never have to re-derive it from `detail`'s prose. */\n actionable: boolean;\n /** Present only for the three-way (baseline-aware) verdicts — the recorded baseline hash both sides were compared against. */\n baselineHash?: string;\n /** Present only for the three-way (baseline-aware) verdicts — sha256 of the freshly-fetched upstream content. */\n upstreamHash?: string;\n}\n\nexport interface MapDriftDeps {\n /** Reads a local file's raw text — defaults to a real `fs.readFileSync`. Overridable for tests (fixture \"upstream\" paths, no network). */\n readLocalFile?: (path: string) => string | undefined;\n /** Fetches a remote URL's raw text — defaults to global `fetch`. Overridable for tests. */\n fetchUpstream?: (url: string) => Promise<string | undefined>;\n /**\n * Path to the skill-map JSON file being processed (e.g. `--skill-map <path>`). A map's\n * `skill`/`external` node `path` values are recorded repo-relative (e.g.\n * `rhize-context-manager/skills/x/SKILL.md`), so resolving them against `process.cwd()`\n * alone only works when the map happens to be run from its own repo's root. Supplying this\n * lets relative local paths also be tried against the map's own directory and its parent\n * (the map conventionally lives in `<repo>/generated/`, so the parent reaches the repo\n * root) before falling back to cwd-relative. Absolute and `~`-prefixed paths are unaffected.\n */\n mapPath?: string;\n}\n\nfunction sha256(text: string): string {\n return createHash('sha256').update(text, 'utf-8').digest('hex');\n}\n\n/** Truncates a hex hash for display — shared by the three-way and two-way (fallback) verdict paths. */\nfunction shortHash(hash: string): string {\n return hash.slice(0, 12);\n}\n\n/**\n * Whether a given drift status needs human attention, for every status this module can emit.\n * Reasoned out per status (not just the three-way four):\n * - `in-sync` — no: local and upstream both match; nothing to do.\n * - `local-only` — no: only local changed since baseline — a deliberate fork (e.g.\n * Rhize's added frontmatter), expected steady state, not a problem to fix.\n * - `upstream-moved` — yes: upstream changed since baseline; review for adoption.\n * - `diverged` — yes: both sides changed since baseline; review for adoption.\n * - `drifted` — yes: the legacy two-way compare's only \"changed\" verdict —\n * equivalent in spirit to upstream-moved/diverged, so it queues the same way.\n * - `upstream-unreachable`— yes: the check itself failed (network/read error) — broken\n * tooling state that needs investigating, not a clean comparison result.\n * - `local-missing` — yes: broken local state (missing map data or missing file on\n * disk) — needs fixing before any comparison can even happen.\n */\nconst ACTIONABLE_STATUSES: ReadonlySet<MapDriftStatus> = new Set<MapDriftStatus>([\n 'upstream-moved',\n 'diverged',\n 'drifted',\n 'upstream-unreachable',\n 'local-missing',\n]);\n\nexport function isActionable(status: MapDriftStatus): boolean {\n return ACTIONABLE_STATUSES.has(status);\n}\n\n/**\n * Pure three-way (baseline-aware) verdict decision table, keyed by the two boolean comparisons\n * against the recorded baseline. Extracted from `computeMapDrift` so it can be unit-tested\n * without touching the filesystem or network.\n */\nexport function threeWayVerdict(\n localNormalized: string,\n upstreamHash: string,\n baselineHash: string,\n): { status: MapDriftStatus; detail: string; actionable: boolean } {\n const localMatchesBaseline = localNormalized === baselineHash;\n const upstreamMatchesBaseline = upstreamHash === baselineHash;\n const hashSuffix = `baseline=${shortHash(baselineHash)}…, local-normalized=${shortHash(localNormalized)}…, upstream=${shortHash(upstreamHash)}…`;\n\n const TABLE: Record<'TT' | 'TF' | 'FT' | 'FF', { status: MapDriftStatus; detail: string }> = {\n TT: { status: 'in-sync', detail: `local and upstream both match the recorded baseline (${hashSuffix})` },\n TF: { status: 'upstream-moved', detail: `only upstream changed since baseline — review for adoption (${hashSuffix})` },\n FT: { status: 'local-only', detail: `only local content changed since baseline — deliberate fork divergence (${hashSuffix})` },\n FF: { status: 'diverged', detail: `both local and upstream changed since baseline — review for adoption (${hashSuffix})` },\n };\n\n const key = `${localMatchesBaseline ? 'T' : 'F'}${upstreamMatchesBaseline ? 'T' : 'F'}` as const;\n const { status, detail } = TABLE[key];\n return { status, detail, actionable: isActionable(status) };\n}\n\n/**\n * Resolves a possibly-relative local path recorded on a skill-map node. Absolute/`~`-prefixed\n * paths resolve exactly as before. Relative paths are tried, in order, against: (1) cwd, (2)\n * the skill map's own directory, (3) the skill map directory's parent — the first candidate\n * that exists on disk wins; if none exist, the cwd-relative candidate is returned so existing\n * \"local path does not exist: <path>\" messaging is unchanged.\n */\nfunction resolveLocalPath(path: string, mapDir?: string): string {\n const expanded = expandHome(path);\n if (isAbsolute(expanded)) return resolve(expanded);\n\n const candidates = [resolve(expanded)];\n if (mapDir) {\n candidates.push(resolve(mapDir, expanded));\n candidates.push(resolve(mapDir, '..', expanded));\n }\n for (const candidate of candidates) {\n if (existsSync(candidate)) return candidate;\n }\n return candidates[0];\n}\n\nfunction defaultReadLocalFile(path: string, mapDir?: string): string | undefined {\n const resolved = resolveLocalPath(path, mapDir);\n if (!existsSync(resolved)) return undefined;\n try {\n return readFileSync(resolved, 'utf-8');\n } catch {\n return undefined;\n }\n}\n\nasync function defaultFetchUpstream(url: string): Promise<string | undefined> {\n try {\n const res = await fetch(url);\n if (!res.ok) return undefined;\n return await res.text();\n } catch {\n return undefined;\n }\n}\n\n/** True for anything that looks like an `http(s)://` URL — routed to `fetchUpstream`; everything else routes to `readLocalFile`. */\nfunction isHttpUrl(value: string): boolean {\n return /^https?:\\/\\//i.test(value.trim());\n}\n\nfunction upstreamLabel(node: SkillMapNode | undefined, edge: { to: string }): string {\n return node?.url ?? node?.path ?? node?.name ?? node?.id ?? edge.to;\n}\n\n/**\n * Computes a drift row for every `fork-of` edge in the map. `from` is expected to be a local\n * `skill` node (must carry `path` + `contentHash`); `to` is expected to be the upstream\n * (`external`) node (must carry `url` or `path`). Tolerant of a schema that doesn't yet match\n * exactly — any edge whose endpoints can't be resolved to usable data yields a row instead of\n * throwing, so one malformed edge never aborts the whole drift run (mirrors `watch.ts`'s\n * per-root failure tolerance).\n */\nexport async function computeMapDrift(map: SkillMap, deps: MapDriftDeps = {}): Promise<MapDriftRow[]> {\n const mapDir = deps.mapPath ? dirname(resolve(expandHome(deps.mapPath))) : undefined;\n const readLocalFile = deps.readLocalFile ?? ((path: string) => defaultReadLocalFile(path, mapDir));\n const fetchUpstream = deps.fetchUpstream ?? defaultFetchUpstream;\n\n const rows: MapDriftRow[] = [];\n\n for (const edge of edgesOfType(map, 'fork-of')) {\n const localNode = nodeById(map, edge.from);\n const upstreamNode = nodeById(map, edge.to);\n const skillLabel = localNode?.name ?? localNode?.id ?? edge.from;\n const upstream = upstreamLabel(upstreamNode, edge);\n\n if (!localNode || !localNode.path || !localNode.contentHash) {\n rows.push({\n skill: skillLabel,\n upstream,\n status: 'local-missing',\n detail: !localNode\n ? `fork-of edge \"from\" node not found in map: ${edge.from}`\n : 'local skill node missing \"path\" or \"contentHash\" — cannot compare',\n actionable: isActionable('local-missing'),\n });\n continue;\n }\n\n const localResolved = resolveLocalPath(localNode.path, mapDir);\n if (!existsSync(localResolved)) {\n rows.push({\n skill: skillLabel,\n upstream,\n status: 'local-missing',\n detail: `local path does not exist: ${localResolved}`,\n actionable: isActionable('local-missing'),\n });\n continue;\n }\n\n const upstreamLocation = upstreamNode?.url ?? upstreamNode?.path;\n if (!upstreamLocation) {\n rows.push({\n skill: skillLabel,\n upstream,\n status: 'upstream-unreachable',\n detail: 'upstream node has no \"url\" or \"path\" to fetch/read',\n actionable: isActionable('upstream-unreachable'),\n });\n continue;\n }\n\n const upstreamText = isHttpUrl(upstreamLocation)\n ? await fetchUpstream(upstreamLocation)\n : readLocalFile(upstreamLocation);\n\n if (upstreamText === undefined) {\n rows.push({\n skill: skillLabel,\n upstream,\n status: 'upstream-unreachable',\n detail: `could not fetch/read upstream: ${upstreamLocation}`,\n actionable: isActionable('upstream-unreachable'),\n });\n continue;\n }\n\n const upstreamHash = sha256(upstreamText);\n const baselineHash = upstreamNode?.baselineHash;\n const localNormalized = localNode.contentHashNormalized;\n\n if (baselineHash && localNormalized) {\n // Three-way (baseline-aware) verdict: reconciles deliberate local changes (e.g. Rhize's\n // added frontmatter) against genuine upstream movement, per the three-way comparison,\n // four verdict states matrix in docs/superpowers/specs/2026-08-10-three-way-drift-design.md.\n const { status, detail, actionable } = threeWayVerdict(localNormalized, upstreamHash, baselineHash);\n rows.push({ skill: skillLabel, upstream, status, detail, actionable, baselineHash, upstreamHash });\n continue;\n }\n\n // Fallback: no baseline/normalized-hash pair available — today's two-way compare\n // (localNode.contentHash vs freshly-fetched upstream) so older maps keep working.\n if (upstreamHash === localNode.contentHash) {\n rows.push({\n skill: skillLabel,\n upstream,\n status: 'in-sync',\n detail: `contentHash matches (${shortHash(upstreamHash)}…)`,\n actionable: isActionable('in-sync'),\n });\n } else {\n rows.push({\n skill: skillLabel,\n upstream,\n status: 'drifted',\n detail: `recorded ${shortHash(localNode.contentHash)}…, upstream now hashes to ${shortHash(upstreamHash)}…`,\n actionable: isActionable('drifted'),\n });\n }\n }\n\n return rows;\n}\n","/**\n * skill-forge — Pro module (source-available, not MIT). See LICENSE-COMMERCIAL.\n */\nimport { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from 'node:fs';\nimport { writeNewFile } from '../util.js';\nimport { dirname, join } from 'node:path';\nimport { auditConfiguredSet, sanitizeForMarkdown, type AuditReport } from '../audit.js';\nimport { summarizeAudit, writeAuditState, type AuditState } from '../auditState.js';\nimport { getHomeDir, loadConfig, type Config } from '../config.js';\nimport { UserError } from '../errors.js';\nimport { readAcceptedFindings } from '../findings.js';\nimport { runHandoff } from '../handoff.js';\nimport { listQuarantineIds } from '../install/quarantine.js';\nimport { proFeatureStatus } from '../license.js';\nimport { buildOrganizeReport, type OrganizeReport } from '../organize.js';\nimport { pendingQueueCount, readQueue, updateQueueEntry, type QueueEntry } from '../queue.js';\nimport { heldCount } from './held.js';\nimport { runWatch, type WatchReport } from './watch.js';\nimport type { Severity } from '../gate/scanShared.js';\n\n/**\n * `skill-forge routine` — the whole maintenance pipeline in one command, shaped for cron.\n *\n * Every other command answers one question; keeping a skill set healthy means running four of\n * them and correlating the output by hand, which is exactly the work nobody does on a schedule.\n * This runs audit + watch + organize against the ENGINES (not the commands), correlates them into\n * one summary and one report file, and exits with a code a scheduler can alert on.\n *\n * **Non-interactive by construction**: never prompts, so it cannot hang a scheduled job. It is\n * also the only command that can be scheduled AND write — so what it may write is enumerated and\n * fenced:\n * - always: the combined report + `audit-state.json` (both under ~/.skill-forge)\n * - `--housekeeping` only: prune expired quarantine sandboxes, close orphaned queue entries\n * - `--handoff` only: launch the configured agent\n * Housekeeping and handoff are OPT-IN. A default `skill-forge routine` in a crontab reports and\n * nothing else — an unattended job that deletes or spends tokens must be something you asked for\n * in the crontab line, not something you discover later.\n *\n * It never installs, promotes, or rejects anything: no candidate enters a skills root without a\n * human/agent decision, and a scheduled job is not that.\n */\n\nconst PRO_FEATURE_LABEL = 'the scheduled maintenance routine';\n\n/** Severity floor for `--fail-on`. `any` counts every finding; `none` never fails. */\nexport type FailOn = 'none' | 'low' | 'medium' | 'high' | 'critical' | 'any';\n\nconst SEVERITY_ORDER: Severity[] = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];\n\nexport interface RoutineOptions {\n json?: boolean;\n /** Skip every network check (passed through to watch). */\n offline?: boolean;\n skipWatch?: boolean;\n skipOrganize?: boolean;\n /** Exit 1 when a finding at or above this severity exists. Default 'none' — report, don't fail. */\n failOn?: FailOn;\n /** Bounded write actions: prune expired quarantine sandboxes, close orphaned queue entries. */\n housekeeping?: boolean;\n /** Age threshold in days for quarantine pruning (default 30). Only meaningful with --housekeeping. */\n pruneDays?: number;\n /** With --housekeeping: list what would be changed, change nothing. */\n dryRun?: boolean;\n /** Hand the written report to the configured agent (curation prompt). */\n handoff?: boolean;\n /** Report path override; defaults to ~/.skill-forge/reports/routine-<timestamp>.md. */\n report?: string;\n}\n\nexport interface HousekeepingAction {\n kind: 'prune-quarantine' | 'close-orphaned-queue-entry';\n target: string;\n reason: string;\n /**\n * Three states, not a boolean: \"not attempted because --dry-run\" and \"attempted and failed\" are\n * operationally very different facts for a cron consumer, and a boolean rendered them both as\n * \"would apply\" — telling an operator nothing was tried when a delete had actually failed.\n */\n outcome: 'applied' | 'would-apply' | 'failed';\n}\n\nexport interface RoutineResult {\n generatedAt: string;\n reportPath: string;\n audit: AuditState;\n watch?: { checked: number; moved: number; unknown: number; notices: string[] };\n organize?: { skills: number; roots: number; notices: string[] };\n pending: { queue: number; quarantine: number };\n housekeeping: HousekeepingAction[];\n /**\n * Store-read notices (v0.12) — e.g. a corrupt/missing accepted-findings store. Deliberately\n * NEVER a `degraded` step: the accepted-findings store fails OPEN (nothing gets suppressed on a\n * read failure — full reporting is already restored), so a corrupt store is cosmetic, not a\n * missed step. Paging a cron on it would train exactly the alert fatigue this field exists to\n * avoid.\n */\n notices: string[];\n /**\n * Steps that were requested but could not complete: a crashed drift/registry check, or a\n * housekeeping action that failed. Without this, `watch: undefined` from a crash is\n * indistinguishable from `--skip-watch`, and a scheduled run whose every housekeeping delete is\n * failing exits 0 forever — invisible to the cron that exists to notice.\n */\n degraded: string[];\n /** Populated when the run exits nonzero; the reason. */\n failure?: string;\n}\n\nfunction parseFailOnFloor(failOn: FailOn): Severity | undefined {\n if (failOn === 'none') return undefined;\n if (failOn === 'any') return 'LOW';\n return failOn.toUpperCase() as Severity;\n}\n\n/** True when the audit found anything at or above the `--fail-on` floor. */\nfunction exceedsFloor(audit: AuditState, failOn: FailOn): string | undefined {\n const floor = parseFailOnFloor(failOn);\n if (!floor) return undefined;\n const floorIndex = SEVERITY_ORDER.indexOf(floor);\n for (let i = SEVERITY_ORDER.length - 1; i >= floorIndex; i--) {\n const severity = SEVERITY_ORDER[i];\n const count = audit.findings[severity] ?? 0;\n if (count > 0) return `${count} ${severity} finding(s) at or above --fail-on ${failOn}`;\n }\n return undefined;\n}\n\nfunction resolveReportPath(override: string | undefined, generatedAt: string): string {\n if (override) return override;\n return join(getHomeDir(), 'reports', `routine-${generatedAt.replace(/[:.]/g, '-')}.md`);\n}\n\n\n/**\n * Housekeeping — deliberately the two most boring, most reversible-in-spirit actions available.\n *\n * Pruning only ever deletes a QUARANTINE sandbox, never anything in a skills root: quarantine is\n * a staging area by definition, and a sandbox nobody has resolved in `pruneDays` is abandoned. It\n * is still a delete, so it is opt-in, age-bounded, `--dry-run`-able, and every path is listed in\n * the report. Closing an orphaned queue entry only touches queue.json, and only when the skill it\n * points at is gone from disk — the decide pass for a skill that no longer exists cannot be done.\n */\nfunction runHousekeeping(config: Config, options: RoutineOptions): HousekeepingAction[] {\n const actions: HousekeepingAction[] = [];\n if (!options.housekeeping) return actions;\n\n const apply = options.dryRun !== true;\n const pruneDays = options.pruneDays ?? 30;\n const cutoff = Date.now() - pruneDays * 86_400_000;\n\n for (const id of listQuarantineIds(config.quarantineDir)) {\n const path = join(config.quarantineDir, id);\n let mtime: number;\n try {\n mtime = statSync(path).mtimeMs;\n } catch {\n continue; // vanished between listing and stat — nothing to prune\n }\n if (mtime >= cutoff) continue;\n const ageDays = Math.floor((Date.now() - mtime) / 86_400_000);\n if (apply) {\n try {\n rmSync(path, { recursive: true, force: true });\n } catch (err) {\n actions.push({ kind: 'prune-quarantine', target: path, reason: `prune failed: ${(err as Error).message}`, outcome: 'failed' });\n continue;\n }\n }\n actions.push({\n kind: 'prune-quarantine',\n target: path,\n reason: `held ${ageDays} days without a decision (threshold ${pruneDays})`,\n outcome: apply ? 'applied' : 'would-apply',\n });\n }\n\n let entries: QueueEntry[] = [];\n try {\n entries = readQueue().entries;\n } catch (err) {\n // Report it rather than returning silently: otherwise \"no orphaned entries closed\" is\n // indistinguishable from \"the orphan pass never ran\".\n actions.push({\n kind: 'close-orphaned-queue-entry',\n target: 'queue.json',\n reason: `queue unreadable, orphan pass skipped: ${(err as Error).message}`,\n outcome: 'failed',\n });\n return actions;\n }\n for (const entry of entries) {\n if (entry.status !== 'pending') continue;\n // Only an entry whose installed skill is GONE is orphaned. A pending entry whose skill is\n // still installed is simply undone work, and closing that would silently discard a decision.\n if (!entry.installedPath || existsSync(entry.installedPath)) continue;\n if (apply) {\n try {\n updateQueueEntry(entry.id, { status: 'dismissed' });\n } catch (err) {\n actions.push({\n kind: 'close-orphaned-queue-entry',\n target: entry.id,\n reason: `close failed: ${(err as Error).message}`,\n outcome: 'failed',\n });\n continue;\n }\n }\n actions.push({\n kind: 'close-orphaned-queue-entry',\n target: entry.id,\n reason: `installed path no longer exists: ${entry.installedPath}`,\n outcome: apply ? 'applied' : 'would-apply',\n });\n }\n\n return actions;\n}\n\nfunction renderRoutineMarkdown(result: RoutineResult, audit: AuditReport, watch?: WatchReport, organize?: OrganizeReport): string {\n const lines: string[] = [\n '# skill-forge routine report',\n '',\n `Generated: ${result.generatedAt}`,\n '',\n '## Summary',\n '',\n `- Skills: ${result.audit.skillCount} across ${audit.summary.rootCount} configured root(s)`,\n `- Findings: ${audit.summary.findingCounts.CRITICAL ?? 0} critical, ${audit.summary.findingCounts.HIGH ?? 0} high, ` +\n `${audit.summary.findingCounts.MEDIUM ?? 0} medium, ${audit.summary.findingCounts.LOW ?? 0} low`,\n `- Accepted: ${audit.summary.acceptedCount} (suppressed from the counts above; see Accepted findings)`,\n `- Overlap clusters: ${result.audit.overlapClusters.length}${result.audit.overlapLocked ? ' (overlap analysis was locked — not \"none found\")' : ''}`,\n `- Pending queue entries: ${result.pending.queue}`,\n `- Held in quarantine: ${result.pending.quarantine}`,\n ];\n\n if (watch) {\n lines.push(`- Drift: ${result.watch?.moved ?? 0} moved, ${result.watch?.unknown ?? 0} unknown, of ${result.watch?.checked ?? 0} tracked`);\n }\n if (organize) {\n lines.push(`- Capability registry: ${result.organize?.skills ?? 0} skill(s) across ${result.organize?.roots ?? 0} root(s)`);\n }\n\n if (result.notices.length > 0) {\n lines.push('', '## Notices', '');\n for (const item of result.notices) lines.push(`- ${sanitizeForMarkdown(item)}`);\n }\n\n if (result.degraded.length > 0) {\n lines.push('', '## Incomplete steps', '');\n for (const item of result.degraded) lines.push(`- ${sanitizeForMarkdown(item)}`);\n }\n\n if (result.housekeeping.length > 0) {\n lines.push('', '## Housekeeping', '');\n for (const action of result.housekeeping) {\n const label = action.outcome === 'applied' ? 'APPLIED' : action.outcome === 'failed' ? 'FAILED' : 'WOULD APPLY';\n lines.push(\n `- ${label} ${action.kind}: ` +\n `${sanitizeForMarkdown(action.target)} — ${sanitizeForMarkdown(action.reason)}`\n );\n }\n }\n\n if (audit.hygiene.length > 0) {\n lines.push('', '## Findings', '');\n for (const finding of audit.hygiene) {\n const fp = finding.fingerprint ? finding.fingerprint.slice(0, 12) : 'unknown';\n lines.push(\n `- **${finding.severity}** \\`${sanitizeForMarkdown(finding.rule)}\\` — ` +\n `${sanitizeForMarkdown(finding.target)}: ${sanitizeForMarkdown(finding.detail)} (fp ${fp})`\n );\n }\n }\n\n if (audit.accepted.length > 0) {\n lines.push('', '## Accepted findings', '');\n for (const entry of audit.accepted) {\n lines.push(\n `- **${entry.finding.severity}** \\`${sanitizeForMarkdown(entry.finding.rule)}\\` — ` +\n `${sanitizeForMarkdown(entry.finding.target)}: ${sanitizeForMarkdown(entry.reason)} (accepted ${sanitizeForMarkdown(entry.acceptedAt.slice(0, 10))})`\n );\n }\n }\n\n if (watch && watch.rows.some((r) => r.driftStatus === 'moved')) {\n lines.push('', '## Upstreams that moved', '');\n for (const row of watch.rows.filter((r) => r.driftStatus === 'moved')) {\n lines.push(`- ${sanitizeForMarkdown(row.name)} (${sanitizeForMarkdown(row.source)}) — ${sanitizeForMarkdown(row.detail)}`);\n }\n }\n\n lines.push('', '## Next steps', '');\n if (result.pending.queue > 0) lines.push('- `skill-forge ingest` — pending decide-pass entries');\n if (result.pending.quarantine > 0) lines.push('- `skill-forge promote <id>` / `skill-forge reject <id>` — held candidates');\n if (result.audit.overlapClusters.length > 0) lines.push('- `skill-forge audit --handoff` — consolidate the overlap clusters above');\n lines.push('');\n\n return lines.join('\\n');\n}\n\nexport async function routineCommand(options: RoutineOptions = {}): Promise<void> {\n const pro = proFeatureStatus(PRO_FEATURE_LABEL);\n const log = options.json ? console.error : console.log;\n if (pro.notice) log(`[skill-forge] ${pro.notice}`);\n if (!pro.enabled) return;\n\n if (options.pruneDays !== undefined && (!Number.isFinite(options.pruneDays) || options.pruneDays < 1)) {\n throw new UserError(`--prune-days must be a positive number (got \"${options.pruneDays}\")`);\n }\n if (options.dryRun && !options.housekeeping) {\n throw new UserError('--dry-run only applies to --housekeeping; without it `routine` already writes nothing but its own report');\n }\n\n const config = loadConfig();\n const generatedAt = new Date().toISOString();\n\n // v0.12: load human-accepted findings and pass them through — the engine stays pure (only takes\n // the already-loaded records). A corrupt/missing store fails OPEN (see src/findings.ts's module\n // doc): nothing is suppressed, and the read failure surfaces as a NOTICE, never as `degraded` —\n // full reporting is already restored, so it's cosmetic, not a missed step (see RoutineResult's\n // `notices` doc comment).\n const { records: accepted, notices: storeNotices } = readAcceptedFindings();\n for (const notice of storeNotices) log(`[skill-forge] ${notice}`);\n\n const audit = await auditConfiguredSet(config, { accepted });\n const auditState = summarizeAudit(audit);\n\n const degraded: string[] = [];\n\n let watch: WatchReport | undefined;\n if (!options.skipWatch) {\n try {\n watch = await runWatch(config, { offline: options.offline, json: true });\n } catch (err) {\n degraded.push(`drift check failed: ${(err as Error).message}`);\n log(`[skill-forge] drift check failed: ${(err as Error).message}`);\n }\n }\n\n let organize: OrganizeReport | undefined;\n if (!options.skipOrganize) {\n try {\n organize = buildOrganizeReport(config);\n } catch (err) {\n degraded.push(`capability registry failed: ${(err as Error).message}`);\n log(`[skill-forge] capability registry failed: ${(err as Error).message}`);\n }\n }\n\n const pending = pendingQueueCount();\n // A corrupt queue is a degraded run, not \"0 pending\": the queue is the record of promoted\n // skills still owed a verb, so a scheduled run must not report an empty backlog for it.\n if (!pending.readable) degraded.push('queue.json is unreadable — pending count is not trustworthy');\n\n const housekeeping = runHousekeeping(config, options);\n for (const action of housekeeping) {\n if (action.outcome === 'failed') degraded.push(`${action.kind} failed for ${action.target}: ${action.reason}`);\n }\n\n // Counted AFTER housekeeping so the report reflects the state the run leaves behind. Uses the\n // shared counter so an unreadable quarantine dir reports 0 instead of aborting a scheduled run.\n const quarantine = heldCount(config.quarantineDir);\n if (!quarantine.readable) degraded.push('quarantine dir could not be listed — held count is not trustworthy');\n\n const result: RoutineResult = {\n generatedAt,\n reportPath: resolveReportPath(options.report, generatedAt),\n audit: auditState,\n watch: watch\n ? {\n checked: watch.rows.length,\n moved: watch.rows.filter((r) => r.driftStatus === 'moved').length,\n // 'unreachable' and 'not-checked' both mean \"we do not know\" — only 'unchanged' is a clean bill.\n unknown: watch.rows.filter((r) => r.driftStatus === 'unreachable' || r.driftStatus === 'not-checked').length,\n notices: watch.notices,\n }\n : undefined,\n organize: organize\n ? { skills: organize.registry.count, roots: organize.registry.skillsRoots.length, notices: organize.notices }\n : undefined,\n pending: { queue: pending.count, quarantine: quarantine.count },\n housekeeping,\n notices: storeNotices,\n degraded,\n };\n\n // A step the operator ASKED for that could not run is always a nonzero exit, independent of\n // --fail-on: --fail-on grades what the audit FOUND, whereas a degraded run means the routine\n // did not actually do what was scheduled. Silently exiting 0 there defeats the point of cron.\n const failure = degraded.length > 0\n ? `${degraded.length} step(s) did not complete: ${degraded.join('; ')}`\n : exceedsFloor(auditState, options.failOn ?? 'none');\n if (failure) result.failure = failure;\n\n writeNewFile(result.reportPath, renderRoutineMarkdown(result, audit, watch, organize), '--report');\n try {\n // Reuses the summary computed above — recomputing it would be free to write but invites the\n // report and the persisted state drifting apart.\n writeAuditState({ ...auditState, reportPath: result.reportPath });\n } catch (err) {\n console.error(`[skill-forge] routine report written, but recording audit state failed: ${(err as Error).message}`);\n }\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log('[skill-forge] routine');\n console.log(` skills: ${auditState.skillCount}`);\n console.log(\n ` findings: ${auditState.findings.CRITICAL ?? 0} critical / ${auditState.findings.HIGH ?? 0} high / ` +\n `${auditState.findings.MEDIUM ?? 0} medium / ${auditState.findings.LOW ?? 0} low`\n );\n console.log(` accepted: ${auditState.acceptedCount ?? 0} (suppressed from counts; see report)`);\n if (result.watch) console.log(` drift: ${result.watch.moved} moved of ${result.watch.checked} tracked`);\n if (result.organize) console.log(` registry: ${result.organize.skills} skill(s)`);\n console.log(` pending: ${result.pending.queue} queue, ${result.pending.quarantine} quarantine`);\n for (const action of housekeeping) {\n const label = action.outcome === 'applied' ? 'housekeeping' : action.outcome === 'failed' ? 'FAILED' : 'would do';\n console.log(` ${label}: ${action.kind} ${action.target}${action.outcome === 'failed' ? ` — ${action.reason}` : ''}`);\n }\n for (const item of result.degraded) console.log(` INCOMPLETE: ${item}`);\n console.log(` report -> ${result.reportPath}`);\n }\n\n if (options.handoff) {\n runHandoff(config, result.reportPath, {\n promptAsset: 'curation-prompt.md',\n log: options.json ? console.error : console.log,\n });\n }\n\n if (failure) {\n console.error(`[skill-forge] routine: ${failure}`);\n process.exitCode = 1;\n }\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { realpathSync } from 'node:fs';\nimport { dirname, isAbsolute, relative, resolve as resolvePath } from 'node:path';\nimport { loadConfig, type Config } from '../config.js';\nimport { UserError } from '../errors.js';\nimport { runHandoff } from '../handoff.js';\nimport { proFeatureStatus } from '../license.js';\nimport { getQueuePath, readQueue, updateQueueEntry, type QueueEntry, type QueueStatus } from '../queue.js';\n\n/**\n * `skill-forge ingest` (Pro) — the queue-drain UX for pending ingestions (no `<source>` form).\n * Direct single-source ingestion is NOT this command — use `skill-forge add <source> --ingest`,\n * which runs the full quarantine/gate pipeline before optionally handing off.\n *\n * `--list` is read-only (no handoff, no writes). The no-arg form validates every pending entry's\n * recorded path before handing off ONCE to the configured agent with the bundled\n * `assets/ingest-prompt.md` and the queue file path — see src/handoff.ts. `queueClose` below is\n * the thin `skill-forge queue close <id> --status ...` wiring so an agent never hand-edits\n * queue.json directly.\n */\n\nconst PRO_FEATURE_LABEL = 'the pending-ingestion queue';\nconst VALID_STATUSES: QueueStatus[] = ['ingested', 'dismissed'];\n\nexport interface IngestOptions {\n list?: boolean;\n json?: boolean;\n}\n\nexport interface QueueCloseOptions {\n status?: string;\n}\n\ninterface ListRow {\n id: string;\n source: string;\n artifactType: 'skill' | 'mcp';\n safetyVerdict: QueueEntry['gate']['safetyVerdict'];\n createdAt: string;\n}\n\ninterface ExcludedEntry {\n entry: QueueEntry;\n reason: string;\n}\n\nexport interface ValidationResult {\n valid: QueueEntry[];\n excluded: ExcludedEntry[];\n}\n\nfunction requireProFeature(): void {\n const status = proFeatureStatus(PRO_FEATURE_LABEL);\n if (!status.enabled) {\n throw new UserError(\n status.notice ?? 'ingest is a Pro feature — set SKILL_FORGE_LICENSE or config.licenseKey to a valid license to unlock.'\n );\n }\n if (status.notice) console.error(`[skill-forge] ${status.notice}`);\n}\n\n/** `entr` + `y`/`ies` for `count`, e.g. `pluralizeEntries(1)` -> `'entry'`, `pluralizeEntries(2)` -> `'entries'`. */\nfunction pluralizeEntries(count: number): string {\n return `entr${count === 1 ? 'y' : 'ies'}`;\n}\n\nfunction toListRow(entry: QueueEntry): ListRow {\n return {\n id: entry.id,\n source: entry.source,\n artifactType: entry.artifactType ?? 'skill',\n safetyVerdict: entry.gate.safetyVerdict,\n createdAt: entry.createdAt,\n };\n}\n\nfunction printPendingList(pending: QueueEntry[], json?: boolean): void {\n if (json) {\n console.log(JSON.stringify({ entries: pending.map(toListRow) }, null, 2));\n return;\n }\n if (pending.length === 0) {\n console.log('[skill-forge] no pending queue entries.');\n return;\n }\n console.log(`[skill-forge] ${pending.length} pending ${pluralizeEntries(pending.length)}:`);\n for (const entry of pending) {\n const row = toListRow(entry);\n console.log(` ${row.id} ${row.source} (${row.artifactType}) verdict=${row.safetyVerdict} created=${row.createdAt}`);\n }\n}\n\n/** `realpathSync`, swallowing any failure (missing path, broken symlink, permission error) to null. */\nfunction realpathOrNull(p: string): string | null {\n try {\n return realpathSync(p);\n } catch {\n return null;\n }\n}\n\n/** True when `candidate` is `root` or a descendant of it, comparing already-canonicalized (realpath'd) paths. */\nfunction isUnderRoot(candidate: string, root: string): boolean {\n const rel = relative(root, candidate);\n return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));\n}\n\n/**\n * Validates that every pending entry's effective path (`installedPath` once promoted, else\n * `quarantinePath`) canonicalizes under one of the configured roots: the quarantine dir, a\n * configured skills root, or a configured MCP target file's directory. `queue.json` is a plain,\n * user-writable file — nothing stops a hand-edited or malicious entry from pointing anywhere on\n * disk, so this check runs before the handoff and every failure is reported. NOTE: the handoff\n * passes the queue FILE path, so excluded entries still exist inside it — the ingest prompt\n * instructs the agent to re-apply the same containment check per entry, and the terminal output\n * tells the user how to dismiss a failed entry. Entries that fail (path\n * missing, broken/cyclic symlink, or a realpath that escapes every configured root — e.g. a\n * symlink planted inside quarantineDir pointing outside it) are excluded with a reason rather\n * than blocking the rest of the drain.\n */\nexport function validatePendingEntries(entries: QueueEntry[], config: Config): ValidationResult {\n const quarantineRoot = realpathOrNull(resolvePath(config.quarantineDir));\n const skillsRoots = config.skillsRoots.map((r) => realpathOrNull(resolvePath(r))).filter((r): r is string => r !== null);\n const mcpTargetDirs = (config.mcpTargets ?? [])\n .map((f) => realpathOrNull(dirname(resolvePath(f))))\n .filter((r): r is string => r !== null);\n const allowedRoots = [quarantineRoot, ...skillsRoots, ...mcpTargetDirs].filter((r): r is string => r !== null);\n\n const valid: QueueEntry[] = [];\n const excluded: ExcludedEntry[] = [];\n\n for (const entry of entries) {\n const effectivePath = entry.installedPath ?? entry.quarantinePath;\n if (!effectivePath) {\n excluded.push({ entry, reason: 'no installedPath or quarantinePath recorded on the entry' });\n continue;\n }\n const real = realpathOrNull(resolvePath(effectivePath));\n if (!real) {\n excluded.push({ entry, reason: `path does not exist or could not be resolved: ${effectivePath}` });\n continue;\n }\n if (allowedRoots.length === 0 || !allowedRoots.some((root) => isUnderRoot(real, root))) {\n excluded.push({ entry, reason: `path escapes every configured root (quarantineDir/skillsRoots/mcpTargets): ${effectivePath}` });\n continue;\n }\n valid.push(entry);\n }\n\n return { valid, excluded };\n}\n\n/**\n * `skill-forge ingest [--list] [--json]` — see module doc comment. `--list` is read-only and\n * skipped from the Pro gate's write path but still Pro-gated (the queue itself is a Pro\n * artifact); the no-arg form gates first, then reads the queue, validates pending entries, and\n * hands off once via `runHandoff` with the queue file path as the handoff target.\n */\nexport async function ingest(options: IngestOptions = {}): Promise<void> {\n requireProFeature();\n\n const queue = readQueue();\n const pending = queue.entries.filter((e) => e.status === 'pending');\n\n if (options.list) {\n printPendingList(pending, options.json);\n return;\n }\n\n if (pending.length === 0) {\n console.log('[skill-forge] no pending queue entries to ingest.');\n return;\n }\n\n const config = loadConfig();\n const { valid, excluded } = validatePendingEntries(pending, config);\n\n if (excluded.length > 0) {\n console.error(`[skill-forge] excluded ${excluded.length} pending ${pluralizeEntries(excluded.length)} that failed path validation:`);\n for (const { entry, reason } of excluded) {\n console.error(` - ${entry.id} (${entry.source}): ${reason}`);\n }\n console.error(\n '[skill-forge] excluded entries stay in the queue — review each, then dismiss with: skill-forge queue close <id> --status dismissed'\n );\n }\n\n if (valid.length === 0) {\n console.log('[skill-forge] no pending entries passed path validation — nothing to hand off.');\n return;\n }\n\n console.log(`[skill-forge] ${valid.length} pending ${pluralizeEntries(valid.length)} ready for ingest:`);\n for (const entry of valid) {\n console.log(` - ${entry.id} ${entry.source} (${entry.artifactType ?? 'skill'})`);\n }\n\n runHandoff(config, getQueuePath(), { promptAsset: 'ingest-prompt.md' });\n}\n\n/**\n * `skill-forge queue close <id> --status ingested|dismissed` (Pro) — thin wiring over\n * `queue.ts`'s existing `updateQueueEntry` so an agent never hand-edits `queue.json` directly.\n * Refuses an unknown `id` or a status outside `ingested`/`dismissed` (never `pending` — closing\n * doesn't reopen an entry, and there's no \"close to pending\" use case).\n */\nexport async function queueClose(id: string, options: QueueCloseOptions = {}): Promise<void> {\n requireProFeature();\n\n const status = options.status;\n if (!status || !VALID_STATUSES.includes(status as QueueStatus)) {\n throw new UserError(`--status must be one of: ${VALID_STATUSES.join(', ')} (got ${status ? JSON.stringify(status) : 'nothing'})`);\n }\n\n // updateQueueEntry throws a plain Error on an unknown id — rewrap as UserError so a\n // typo'd id gets the standard `skill-forge: <message>` output, not \"unexpected error\".\n let updated;\n try {\n updated = updateQueueEntry(id, { status: status as QueueStatus });\n } catch (err) {\n throw new UserError(err instanceof Error ? err.message : String(err));\n }\n console.log(`[skill-forge] queue entry ${updated.id} closed -> status: ${updated.status}`);\n}\n","import { createHash, randomUUID } from 'node:crypto';\nimport { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs';\nimport { basename, dirname, isAbsolute, join, relative, resolve as resolvePath, sep } from 'node:path';\nimport { createInterface, type Interface } from 'node:readline/promises';\nimport { sanitizeForMarkdown } from '../audit.js';\nimport { loadConfig, type Config } from '../config.js';\nimport { UserError } from '../errors.js';\nimport { assertPlainSkillName, gatherRefineContext, inventoryAllScopes, resolveScopes, type ScopeOverrideInventory, type ScopeResolution } from '../refine/context.js';\nimport { runHandoff } from '../handoff.js';\nimport { proFeatureStatus } from '../license.js';\nimport {\n defaultRandomHex,\n generateRefinementId,\n isOverrideType,\n isPatchAction,\n isPatternStatus,\n isRefinementCategory,\n isRefinementStatus,\n patternCount,\n type CaptureScope,\n type OverrideType,\n type PatchAction,\n type Pattern,\n type Refinement,\n type RefinementCategory,\n} from '../refine/models.js';\nimport { applyPatchBlocks, previewDiff, renderConfigOverride, renderExtendFile, type ParsedPatchBlock } from '../refine/patch.js';\nimport { appendProvenance } from '../provenance.js';\nimport {\n createBackup,\n getBackupsDir,\n isValidBackupId,\n markGeneralized,\n readBackupManifest,\n readPatternsStore,\n readRefinementsStore,\n recordRefinement,\n writePatternsStore,\n type BackupManifest,\n type RecordRefinementResult,\n} from '../refine/store.js';\nimport { expandHome } from '../util.js';\n\n/**\n * `skill-forge refine` (Pro, thin MIT CLI wiring) — capture, apply, and\n * generalize improvements to installed skills from real usage feedback. See\n * `.claude/plans/refinement-merge.md` for the full design; this file adapts\n * commander's options shape into calls against `src/refine/models.ts`\n * (types/ids), `src/refine/store.ts` (persistence), `src/refine/patch.ts`\n * (pure patch engine), and `src/refine/context.ts` (scope resolution).\n * `src/cli.ts` (owned by a different lane) wires these exported functions to\n * commander subcommands: `capture` (the default action), `list`,\n * `patterns`, `promote <pattern-id>`, `which <skill>`, `rollback <backup-id>`.\n *\n * Amendment 1 (capture never mutates a base SKILL.md): `refineCaptureCommand`\n * writes ONLY override artifacts — `SKILL.patch.md` / `SKILL.extend.md` /\n * `skill-config.json`, or a verbatim override-file copy for `full`/`hook`/\n * `script` — at the chosen PROJECT scope (`local`/`shared`, never `user`).\n * `refinePromoteCommand` is the only function that ever writes to a\n * user-scope base `SKILL.md`, and only via `applyPatchBlocks` (patch.ts),\n * never a raw string edit.\n */\n\nconst PRO_FEATURE_LABEL = 'skill refinement';\nconst REFINE_PROMPT_ASSET = 'refine-prompt.md';\n\nfunction requireProFeature(): void {\n const status = proFeatureStatus(PRO_FEATURE_LABEL);\n if (!status.enabled) {\n throw new UserError(status.notice ?? 'refine is a Pro feature — set SKILL_FORGE_LICENSE or config.licenseKey to a valid license to unlock.');\n }\n if (status.notice) console.error(`[skill-forge] ${status.notice}`);\n}\n\nfunction isInteractive(json: boolean | undefined): boolean {\n return !json && Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);\n}\n\n/** Prints `payload` as a single JSON document when `json` is true; otherwise runs `renderText`. Centralizes the `--json`-vs-terminal branch repeated across every refine subcommand. */\nfunction emit(json: boolean | undefined, payload: unknown, renderText: () => void): void {\n if (json) {\n console.log(JSON.stringify(payload, null, 2));\n } else {\n renderText();\n }\n}\n\n/** Asks `question` and returns true on an explicit y/yes answer (default no). Mirrors `src/commands/audit.ts`'s confirm idiom. */\nasync function confirmYesNo(rl: Interface, question: string): Promise<boolean> {\n const answer = (await rl.question(question)).trim().toLowerCase();\n return answer === 'y' || answer === 'yes';\n}\n\n/**\n * Resolves whether a write may proceed: `--yes` always wins; otherwise an\n * interactive TTY prompt is required (Amendment \"explicit confirmation\n * before any write ... unless --yes\"). A non-interactive caller without\n * `--yes` gets a `UserError` rather than a silent write or a silent skip.\n */\nasync function confirmWrite(yes: boolean | undefined, json: boolean | undefined, prompt: string): Promise<boolean> {\n if (yes) return true;\n if (!isInteractive(json)) {\n throw new UserError('refusing to write without confirmation — pass --yes, or run interactively.');\n }\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n try {\n return await confirmYesNo(rl, prompt);\n } finally {\n rl.close();\n }\n}\n\nfunction resolveConfiguredRoot(root: string): string {\n return resolvePath(expandHome(root));\n}\n\nfunction isUnderRoot(candidate: string, root: string): boolean {\n const rel = relative(root, candidate);\n return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));\n}\n\n/**\n * Strips C0/C1 control characters (including ANSI escapes) from a diff before it ever reaches\n * `console.log` at a confirm-prompt site — a captured refinement's patch content, or an\n * installed skill's own SKILL.md, is untrusted text that could otherwise smuggle terminal\n * control sequences into the confirmation preview. Applies `audit.ts`'s `sanitizeForMarkdown`\n * per line (not to the whole multi-line string at once) so the diff's own `\\n` line breaks\n * survive — `patch.ts`'s `previewDiff` stays a pure, unsanitized function; sanitization is a\n * command-layer concern.\n */\nfunction sanitizeDiffForConsole(diff: string): string {\n return diff\n .split('\\n')\n .map((line) => sanitizeForMarkdown(line))\n .join('\\n');\n}\n\n// ---------------------------------------------------------------------------\n// capture\n// ---------------------------------------------------------------------------\n\nexport interface RefineCaptureOptions {\n skill?: string;\n category?: string;\n target?: string;\n overrideType?: string;\n action?: string;\n marker?: string;\n content?: string;\n contentFile?: string;\n expected?: string;\n actual?: string;\n example?: string;\n outcome?: string;\n rootCause?: string;\n patternId?: string;\n scope?: string;\n dryRun?: boolean;\n json?: boolean;\n yes?: boolean;\n handoff?: boolean;\n}\n\ninterface ValidatedCaptureOptions {\n skill: string;\n category: RefinementCategory;\n target: string;\n overrideType: OverrideType;\n action?: PatchAction;\n marker?: string;\n expected: string;\n actual: string;\n example?: string;\n outcome?: string;\n rootCause?: string;\n patternId?: string;\n scope: CaptureScope;\n dryRun: boolean;\n json: boolean;\n yes: boolean;\n handoff: boolean;\n}\n\nfunction resolveContentOption(options: { content?: string; contentFile?: string }): string | undefined {\n if (options.content !== undefined) return options.content;\n if (options.contentFile) return readFileSync(resolvePath(options.contentFile), 'utf-8');\n return undefined;\n}\n\nfunction validateCaptureOptions(options: RefineCaptureOptions): ValidatedCaptureOptions {\n if (!options.skill) throw new UserError('--skill is required');\n assertPlainSkillName(options.skill);\n if (!options.category || !isRefinementCategory(options.category)) {\n throw new UserError(`--category is required and must be one of: trigger, content, hook, tool, pattern, config, new`);\n }\n if (!options.target) throw new UserError('--target is required (heading-path selector or a short description for whole-file override types)');\n if (!options.overrideType || !isOverrideType(options.overrideType)) {\n throw new UserError('--override-type is required and must be one of: patch, extend, config, full, hook, script, new');\n }\n if (!options.expected) throw new UserError('--expected is required');\n if (!options.actual) throw new UserError('--actual is required');\n\n const scopeRaw = options.scope ?? 'local';\n if (scopeRaw !== 'local' && scopeRaw !== 'shared') {\n throw new UserError('--scope must be \"local\" or \"shared\"');\n }\n\n let action: PatchAction | undefined;\n if (options.overrideType === 'patch') {\n if (!options.action || !isPatchAction(options.action)) {\n throw new UserError(\n '--action is required for --override-type patch (one of: append, prepend, replace-section, insert-after, insert-before, delete-section)'\n );\n }\n action = options.action;\n }\n\n return {\n skill: options.skill,\n category: options.category,\n target: options.target,\n overrideType: options.overrideType,\n action,\n marker: options.marker,\n expected: options.expected,\n actual: options.actual,\n example: options.example,\n outcome: options.outcome,\n rootCause: options.rootCause,\n patternId: options.patternId,\n scope: scopeRaw,\n dryRun: options.dryRun ?? false,\n json: options.json ?? false,\n yes: options.yes ?? false,\n handoff: options.handoff ?? false,\n };\n}\n\n/** Renders `<!-- ACTION: <action> \"<marker>\" -->` / `<!-- TARGET: ... -->` text matching `patch.ts`'s `parsePatchFile` grammar exactly. */\nfunction renderPatchActionBlock(params: { action: PatchAction; marker?: string; target: string; content: string }): string {\n const lines: string[] = [];\n lines.push(params.marker ? `<!-- ACTION: ${params.action} \"${params.marker}\" -->` : `<!-- ACTION: ${params.action} -->`);\n lines.push(`<!-- TARGET: ${params.target} -->`);\n lines.push(params.content);\n return lines.join('\\n');\n}\n\ninterface RenderedArtifact {\n /** Path relative to the scope directory. */\n relPath: string;\n content: string;\n}\n\n/** Default relative filename per whole-file override type; overridable via `--marker`. */\nfunction defaultOverrideFilename(overrideType: 'full' | 'hook' | 'script'): string {\n switch (overrideType) {\n case 'full':\n return 'SKILL.md';\n case 'hook':\n return 'hooks/refinement.sh';\n case 'script':\n return 'scripts/refinement.sh';\n default: {\n const exhaustive: never = overrideType;\n throw new Error(`internal error: defaultOverrideFilename called for non-whole-file override type \"${exhaustive as string}\"`);\n }\n }\n}\n\n/**\n * Renders the override artifact content for every one of the seven override\n * types (plan Amendment 2). `existingContent` is the current on-disk content\n * at the destination path (empty string if none) — `patch`/`extend`/`new`\n * accumulate onto it rather than overwriting.\n */\nfunction renderCaptureArtifact(\n opts: ValidatedCaptureOptions,\n content: string | undefined,\n refinementId: string,\n generatedAt: string,\n existingContent: string\n): RenderedArtifact {\n switch (opts.overrideType) {\n case 'patch': {\n if (!content) throw new UserError('--content or --content-file is required for --override-type patch');\n const block = renderPatchActionBlock({ action: opts.action!, marker: opts.marker, target: opts.target, content });\n const preamble = `# SKILL.patch.md\\n# Patches for: ${opts.skill}\\n`;\n const base = existingContent.trim().length > 0 ? existingContent.trimEnd() : preamble.trimEnd();\n return { relPath: 'SKILL.patch.md', content: `${base}\\n\\n${block}\\n` };\n }\n case 'extend':\n case 'new': {\n if (!content) throw new UserError(`--content or --content-file is required for --override-type ${opts.overrideType}`);\n const rendered = renderExtendFile({ skillName: opts.skill, refinementId, generatedAt, targetSection: opts.target, content });\n const combined = existingContent.trim().length > 0 ? `${existingContent.trimEnd()}\\n\\n${rendered}` : rendered;\n return { relPath: 'SKILL.extend.md', content: combined };\n }\n case 'config': {\n if (!content) throw new UserError('--content or --content-file (JSON object) is required for --override-type config');\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch (err) {\n throw new UserError(`--content for --override-type config must be valid JSON: ${(err as Error).message}`);\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new UserError('--content for --override-type config must be a JSON object');\n }\n const rendered = renderConfigOverride({\n skillName: opts.skill,\n refinementId,\n generatedAt,\n config: parsed as Record<string, unknown>,\n });\n return { relPath: 'skill-config.json', content: rendered };\n }\n case 'full':\n case 'hook':\n case 'script': {\n if (!content) throw new UserError(`--content or --content-file is required for --override-type ${opts.overrideType}`);\n return { relPath: opts.marker && opts.marker.trim().length > 0 ? opts.marker.trim() : defaultOverrideFilename(opts.overrideType), content };\n }\n default: {\n const exhaustive: never = opts.overrideType;\n throw new UserError(`unknown override type \"${exhaustive as string}\"`);\n }\n }\n}\n\n/** Refuses a symlinked scope directory, then creates it (mkdir -p) and confirms the realpath has no symlink components AND stays contained under the project's own cwd. */\nfunction ensureScopeDirSafe(scopeDir: string, resolvedCwd: string): string {\n let lst;\n try {\n lst = lstatSync(scopeDir);\n } catch {\n lst = undefined;\n }\n if (lst?.isSymbolicLink()) {\n throw new UserError(`refusing to write under ${scopeDir}: scope directory is a symlink`);\n }\n mkdirSync(scopeDir, { recursive: true });\n const real = resolveConfiguredRoot(scopeDir);\n const lexical = resolvePath(scopeDir);\n // Best-effort: if the directory tree itself contains a symlinked component, realpath and the\n // lexical resolution diverge — refuse rather than silently writing through it.\n let realResolved: string;\n try {\n realResolved = realpathSync(scopeDir);\n } catch {\n realResolved = real;\n }\n if (realResolved !== lexical) {\n throw new UserError(`refusing to write under ${scopeDir}: path contains a symlink`);\n }\n if (!isUnderRoot(realResolved, resolveConfiguredRoot(resolvedCwd))) {\n throw new UserError(`refusing to write under ${scopeDir}: resolves outside the project root ${resolvedCwd}`);\n }\n return realResolved;\n}\n\n/** Refuses a symlinked destination file, containment-checks the resolved parent dir under the scope root, and writes atomically-enough for a single small file (direct write, mode 0600). */\nfunction writeOverrideFile(scopeDir: string, relPath: string, content: string, resolvedCwd: string): string {\n const realScopeDir = ensureScopeDirSafe(scopeDir, resolvedCwd);\n const destPath = join(scopeDir, relPath);\n const destDir = dirname(destPath);\n mkdirSync(destDir, { recursive: true });\n\n const realDestDir = realpathSync(destDir);\n if (!isUnderRoot(realDestDir, realScopeDir)) {\n throw new UserError(`refusing to write ${destPath}: resolves outside scope directory ${scopeDir}`);\n }\n\n let destLst;\n try {\n destLst = lstatSync(destPath);\n } catch {\n destLst = undefined;\n }\n if (destLst?.isSymbolicLink()) {\n throw new UserError(`refusing to write ${destPath}: destination is a symlink`);\n }\n\n writeFileSync(destPath, content, { mode: 0o600 });\n chmodSync(destPath, 0o600);\n return destPath;\n}\n\nexport async function refineCaptureCommand(rawOptions: RefineCaptureOptions = {}): Promise<void> {\n requireProFeature();\n const opts = validateCaptureOptions(rawOptions);\n\n const config = loadConfig();\n const cwd = process.cwd();\n const scopes = resolveScopes(config, cwd, opts.skill);\n const scopeDir = opts.scope === 'local' ? scopes.local.dir : scopes.shared.dir;\n\n const content = resolveContentOption(rawOptions);\n\n // Pre-generate the refinement id so it can be embedded in the rendered artifact (extend/config\n // renderers reference it) AND be the exact id recordRefinement writes — passed through below as\n // an explicit `id`, NOT as a constant `randomHex` closure. A constant closure would feed\n // `generateRefinementId`'s (and, worse, the unrelated `generatePatternId`'s) collision-retry\n // do-while loop a value that never changes, turning any id collision into an infinite hang\n // (merge-gate IMPORTANT 3). Safe to precompute here because nothing else touches the store\n // between this read and the recordRefinement call below (single-threaded CLI).\n const now = new Date();\n const existingIds = new Set(readRefinementsStore().records.map((r) => r.id));\n const refinementId = generateRefinementId(existingIds, now, defaultRandomHex);\n\n const preview = renderCaptureArtifactPreview(opts, content, refinementId, now.toISOString(), scopeDir);\n\n if (opts.dryRun) {\n emit(opts.json, { dryRun: true, path: preview.destPath, diff: preview.diff }, () => {\n console.log(`[skill-forge] refine capture — ${opts.overrideType} override for \"${sanitizeForMarkdown(opts.skill)}\" at ${opts.scope} scope`);\n console.log(sanitizeDiffForConsole(preview.diff));\n });\n return;\n }\n\n if (!opts.json) {\n console.log(`[skill-forge] refine capture — ${opts.overrideType} override for \"${sanitizeForMarkdown(opts.skill)}\" at ${opts.scope} scope`);\n console.log(sanitizeDiffForConsole(preview.diff));\n }\n\n const proceed = await confirmWrite(opts.yes, opts.json, `\\nWrite ${preview.destPath}? [y/N] `);\n if (!proceed) {\n emit(opts.json, { written: false, cancelled: true, path: preview.destPath }, () => {\n console.log('[skill-forge] capture cancelled — nothing written.');\n });\n return;\n }\n\n const writtenPath = writeOverrideFile(scopeDir, preview.relPath, preview.content, cwd);\n\n let result: RecordRefinementResult;\n try {\n result = recordRefinement(\n {\n skill: opts.skill,\n category: opts.category,\n overrideType: opts.overrideType,\n targetSection: opts.target,\n expectedBehavior: opts.expected,\n actualBehavior: opts.actual,\n example: opts.example,\n desiredOutcome: opts.outcome,\n rootCause: opts.rootCause,\n project: cwd,\n projectRoot: cwd,\n patchAction: opts.action,\n patchMarker: opts.marker,\n patchContent: content,\n status: 'applied',\n scope: opts.scope,\n patternId: opts.patternId,\n },\n { now, id: refinementId }\n );\n } catch (err) {\n throw new UserError(`wrote the override artifact at ${writtenPath}, but recording the refinement in the store failed: ${(err as Error).message}`);\n }\n\n emit(\n opts.json,\n { written: true, path: writtenPath, refinementId: result.refinement.id, patternId: result.pattern.id, patternBecameReady: result.patternBecameReady },\n () => {\n console.log(`[skill-forge] wrote ${writtenPath}`);\n console.log(`[skill-forge] recorded refinement ${result.refinement.id} -> pattern ${result.pattern.id} (status: ${result.pattern.status})`);\n if (result.patternBecameReady) {\n console.log(`[skill-forge] pattern ${result.pattern.id} is now READY (seen in a second project) — consider \\`skill-forge refine promote ${result.pattern.id}\\``);\n }\n }\n );\n\n if (opts.handoff) {\n runHandoff(config, writtenPath, { promptAsset: REFINE_PROMPT_ASSET, log: opts.json ? console.error : console.log });\n }\n}\n\n/** Composes the rendered artifact + diff preview against current on-disk content, without writing anything. */\nfunction renderCaptureArtifactPreview(\n opts: ValidatedCaptureOptions,\n content: string | undefined,\n refinementId: string,\n generatedAt: string,\n scopeDir: string\n): { relPath: string; content: string; destPath: string; diff: string } {\n // Peek at existing content for accumulation (patch/extend/new) without creating any directory.\n const probeRelPath = opts.overrideType === 'patch' ? 'SKILL.patch.md' : opts.overrideType === 'extend' || opts.overrideType === 'new' ? 'SKILL.extend.md' : undefined;\n const probePath = probeRelPath ? join(scopeDir, probeRelPath) : undefined;\n const existingContent = probePath && existsSync(probePath) ? readFileSync(probePath, 'utf-8') : '';\n\n const rendered = renderCaptureArtifact(opts, content, refinementId, generatedAt, existingContent);\n const relPath = rendered.relPath;\n const destPath = join(scopeDir, relPath);\n const before = existsSync(destPath) ? readFileSync(destPath, 'utf-8') : '';\n const diff = previewDiff(before, rendered.content, destPath);\n\n return { relPath, content: rendered.content, destPath, diff };\n}\n\n// ---------------------------------------------------------------------------\n// list / patterns\n// ---------------------------------------------------------------------------\n\nexport interface RefineListOptions {\n status?: string;\n skill?: string;\n project?: string;\n json?: boolean;\n}\n\nexport async function refineListCommand(options: RefineListOptions = {}): Promise<void> {\n requireProFeature();\n const { records } = readRefinementsStore();\n let rows: Refinement[] = records;\n if (options.skill) rows = rows.filter((r) => r.skill === options.skill);\n if (options.project) rows = rows.filter((r) => r.project === options.project || r.projectRoot === resolvePath(options.project!));\n if (options.status) {\n if (!isRefinementStatus(options.status)) {\n throw new UserError('--status must be one of: pending, applied, generalized');\n }\n rows = rows.filter((r) => r.status === options.status);\n }\n const filtered = Boolean(options.skill || options.project || options.status);\n\n emit(options.json, { records: rows }, () => {\n if (rows.length === 0) {\n if (filtered && records.length > 0) {\n console.log(`[skill-forge] no refinements match the given filter(s) (${records.length} total recorded).`);\n } else {\n console.log('[skill-forge] no refinements recorded.');\n }\n return;\n }\n console.log(`[skill-forge] ${rows.length} refinement(s):`);\n for (const r of rows) {\n console.log(\n ` ${r.id} ${sanitizeForMarkdown(r.skill)} [${r.category}/${r.overrideType}] status=${r.status} pattern=${r.patternId ?? '(none)'} ${r.createdAt}`\n );\n }\n });\n}\n\nexport interface RefinePatternsOptions {\n status?: string;\n skill?: string;\n project?: string;\n json?: boolean;\n}\n\nexport async function refinePatternsCommand(options: RefinePatternsOptions = {}): Promise<void> {\n requireProFeature();\n const { records } = readPatternsStore();\n let rows: Pattern[] = records;\n if (options.status) {\n if (!isPatternStatus(options.status)) {\n throw new UserError('--status must be one of: tracking, ready, generalized, dismissed');\n }\n rows = rows.filter((p) => p.status === options.status);\n }\n if (options.skill) rows = rows.filter((p) => p.affectedSkills.includes(options.skill!));\n if (options.project) {\n const resolvedProject = resolvePath(options.project);\n rows = rows.filter((p) => p.occurrences.some((o) => o.projectRealpath === resolvedProject));\n }\n const filtered = Boolean(options.skill || options.project || options.status);\n\n emit(options.json, { records: rows.map((p) => ({ ...p, count: patternCount(p) })) }, () => {\n if (rows.length === 0) {\n if (filtered && records.length > 0) {\n console.log(`[skill-forge] no patterns match the given filter(s) (${records.length} total recorded).`);\n } else {\n console.log('[skill-forge] no patterns recorded.');\n }\n return;\n }\n console.log(`[skill-forge] ${rows.length} pattern(s):`);\n for (const p of rows) {\n console.log(\n ` ${p.id} ${sanitizeForMarkdown(p.name)} status=${p.status} count=${patternCount(p)} skills=${p.affectedSkills.map(sanitizeForMarkdown).join(', ')}`\n );\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// which\n// ---------------------------------------------------------------------------\n\nexport interface RefineWhichOptions {\n userRoot?: string;\n json?: boolean;\n}\n\nfunction renderInventoryLine(row: ScopeOverrideInventory): string {\n const present = [\n row.skillMd ? 'SKILL.md' : undefined,\n row.patchMd ? 'SKILL.patch.md' : undefined,\n row.extendMd ? 'SKILL.extend.md' : undefined,\n row.configJson ? 'skill-config.json' : undefined,\n row.hooksDir ? 'hooks/' : undefined,\n row.scriptsDir ? 'scripts/' : undefined,\n ].filter((v): v is string => Boolean(v));\n return ` [${row.scope}] ${row.dir}${row.dirExists ? '' : ' (missing)'} — ${present.length > 0 ? present.join(', ') : '(no override files)'}`;\n}\n\nexport async function refineWhichCommand(skill: string, options: RefineWhichOptions = {}): Promise<void> {\n requireProFeature();\n if (!skill) throw new UserError('a skill name is required: skill-forge refine which <skill>');\n\n const config = loadConfig();\n const cwd = process.cwd();\n const scopes = resolveScopes(config, cwd, skill, options.userRoot);\n const inventory = inventoryAllScopes(scopes);\n\n emit(options.json, { scopes, inventory }, () => {\n console.log(`[skill-forge] refine which \"${sanitizeForMarkdown(skill)}\" — resolution order (highest precedence first):`);\n for (const row of inventory) console.log(renderInventoryLine(row));\n if (!scopes.user.resolved) {\n console.log(\n ` [user] ambiguous — candidates: ${scopes.user.candidates.map((c) => c.root).join(', ') || '(none configured)'} (pass --user-root to disambiguate)`\n );\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// promote\n// ---------------------------------------------------------------------------\n\nexport interface RefinePromoteOptions {\n force?: boolean;\n dryRun?: boolean;\n json?: boolean;\n yes?: boolean;\n userRoot?: string;\n}\n\ninterface PostWriteSidecar {\n schemaVersion: 1;\n files: { path: string; sha256: string }[];\n}\n\nfunction writePostWriteSidecar(backupId: string, files: { path: string; sha256: string }[]): void {\n const sidecar: PostWriteSidecar = { schemaVersion: 1, files };\n const dir = join(getBackupsDir(), backupId);\n mkdirSync(dir, { recursive: true });\n writeFileSync(join(dir, 'post.json'), JSON.stringify(sidecar, null, 2) + '\\n', { mode: 0o600 });\n}\n\ninterface PostWriteSidecarResult {\n hashes: Map<string, string>;\n /** `missing`/`unreadable`/`corrupt` all mean rollback cannot verify the current on-disk state\n * matches what was promoted — the caller (`refineRollbackCommand`) must refuse without\n * `--force` rather than silently treating \"no data\" the same as \"no mismatch\". `hashes` is\n * always empty for every non-`ok` status (never partially populated), so a `--force` restore\n * under corruption skips hash verification uniformly for every file rather than for some. */\n status: 'ok' | 'missing' | 'unreadable' | 'corrupt';\n}\n\nfunction readPostWriteSidecar(backupId: string): PostWriteSidecarResult {\n const path = join(getBackupsDir(), backupId, 'post.json');\n if (!existsSync(path)) return { hashes: new Map(), status: 'missing' };\n\n // Split from JSON.parse/population below: a real read error (permissions, I/O) is a distinct\n // condition from a readable-but-malformed file, and gets its own status/message rather than\n // being mislabeled 'corrupt'.\n let raw: string;\n try {\n raw = readFileSync(path, 'utf-8');\n } catch {\n return { hashes: new Map(), status: 'unreadable' };\n }\n\n const hashes = new Map<string, string>();\n try {\n const parsed = JSON.parse(raw) as PostWriteSidecar;\n for (const f of parsed.files ?? []) {\n if (typeof f !== 'object' || f === null || typeof f.path !== 'string' || typeof f.sha256 !== 'string') {\n throw new Error('malformed post.json files entry');\n }\n hashes.set(f.path, f.sha256);\n }\n } catch {\n // Reset rather than returning whatever the loop partially populated before the throw — a\n // files array with one bad entry among otherwise-good ones must not leave hashes half full.\n return { hashes: new Map(), status: 'corrupt' };\n }\n return { hashes, status: 'ok' };\n}\n\nexport async function refinePromoteCommand(patternId: string, options: RefinePromoteOptions = {}): Promise<void> {\n requireProFeature();\n if (!patternId) throw new UserError('a pattern id is required: skill-forge refine promote <pattern-id>');\n\n const patternsStore = readPatternsStore();\n const pattern = patternsStore.records.find((p) => p.id === patternId);\n if (!pattern) throw new UserError(`no pattern with id: ${patternId}`);\n\n if (pattern.status === 'generalized' && !options.force) {\n throw new UserError(`pattern ${patternId} is already generalized (backup ${pattern.generalizedBackupId ?? '(unknown)'}) — pass --force to re-promote.`);\n }\n if (pattern.status !== 'ready' && pattern.status !== 'generalized' && !options.force) {\n throw new UserError(`pattern ${patternId} is not ready (status: ${pattern.status}) — pass --force to promote anyway.`);\n }\n\n const skill = pattern.affectedSkills[0];\n if (!skill) throw new UserError(`pattern ${patternId} has no affected skills recorded`);\n // Defense-in-depth: a poisoned/hand-edited patterns.json could carry a traversal skill name —\n // resolveScopes() below guards this too, but checking it here first gives a clearer error tied\n // to the pattern rather than a generic scope-resolution failure.\n assertPlainSkillName(skill);\n\n const config = loadConfig();\n const cwd = process.cwd();\n const scopes = resolveScopes(config, cwd, skill, options.userRoot);\n if (!scopes.user.resolved) {\n throw new UserError(\n `cannot resolve a single user scope for skill \"${skill}\" — candidates: ${scopes.user.candidates.map((c) => c.root).join(', ') || '(none configured)'}. Pass --user-root to disambiguate.`\n );\n }\n const skillDir = scopes.user.dir;\n\n // Containment check (mirrors writeOverrideFile's isUnderRoot check): the resolved skill\n // directory must actually live under the resolved user-scope root before any read/write.\n const resolvedUserRoot = resolveConfiguredRoot(scopes.user.root);\n let realUserRoot: string;\n try {\n realUserRoot = realpathSync(resolvedUserRoot);\n } catch {\n realUserRoot = resolvedUserRoot;\n }\n let realSkillDir: string;\n try {\n realSkillDir = realpathSync(skillDir);\n } catch {\n realSkillDir = resolvePath(skillDir);\n }\n if (!isUnderRoot(realSkillDir, realUserRoot)) {\n throw new UserError(`refusing to promote: resolved skill directory ${skillDir} is outside the configured user scope root ${scopes.user.root}`);\n }\n\n const skillMdPath = join(skillDir, 'SKILL.md');\n if (!existsSync(skillMdPath)) {\n throw new UserError(`user-scope SKILL.md not found at ${skillMdPath} — refusing to promote against a missing base skill.`);\n }\n\n const refinementsStore = readRefinementsStore();\n const patternRefinements = refinementsStore.records.filter((r) => pattern.refinementIds.includes(r.id));\n const representative = patternRefinements[patternRefinements.length - 1];\n if (!representative) throw new UserError(`pattern ${patternId} has no recorded refinements to promote`);\n\n let block: ParsedPatchBlock;\n if (representative.overrideType === 'patch' && representative.patchAction && representative.patchContent) {\n block = { action: representative.patchAction, marker: representative.patchMarker, target: representative.targetSection, content: representative.patchContent };\n } else if ((representative.overrideType === 'extend' || representative.overrideType === 'new') && representative.patchContent) {\n block = { action: 'append', target: representative.targetSection, content: representative.patchContent };\n } else {\n throw new UserError(\n `pattern ${patternId}'s representative refinement is a '${representative.overrideType}' override — promote currently only materializes patch/extend/new-type patterns against the user-scope SKILL.md; other override types must be merged manually.`\n );\n }\n\n const before = readFileSync(skillMdPath, 'utf-8');\n const after = applyPatchBlocks(before, [block]);\n const diff = previewDiff(before, after, skillMdPath);\n\n if (options.dryRun) {\n emit(options.json, { dryRun: true, path: skillMdPath, diff }, () => {\n console.log(`[skill-forge] refine promote ${patternId} -> ${skillMdPath}`);\n console.log(sanitizeDiffForConsole(diff));\n });\n return;\n }\n\n if (!options.json) {\n console.log(`[skill-forge] refine promote ${patternId} -> ${skillMdPath}`);\n console.log(sanitizeDiffForConsole(diff));\n }\n\n const proceed = await confirmWrite(options.yes, options.json, `\\nPromote pattern ${patternId} into ${skillMdPath}? [y/N] `);\n if (!proceed) {\n emit(options.json, { promoted: false, cancelled: true }, () => {\n console.log('[skill-forge] promote cancelled — nothing written.');\n });\n return;\n }\n\n const backup: BackupManifest = createBackup([skillMdPath]);\n\n const tempPath = `${skillMdPath}.tmp-${randomUUID()}`;\n try {\n writeFileSync(tempPath, after, 'utf-8');\n renameSync(tempPath, skillMdPath);\n } catch (err) {\n rmSync(tempPath, { force: true });\n throw err;\n }\n\n try {\n const postSha256 = createHash('sha256').update(readFileSync(skillMdPath)).digest('hex');\n writePostWriteSidecar(backup.id, [{ path: skillMdPath, sha256: postSha256 }]);\n markGeneralized(patternId, backup.id);\n const skillsRoot = resolveConfiguredRoot(scopes.user.root);\n appendProvenance(\n {\n name: skill,\n source: `skill-forge refine (${patternId})`,\n verb: 'DEFER',\n target: skillMdPath,\n took: `applied a ${block.action} patch materializing pattern ${patternId}`,\n notes: `generalized from ${patternId} via skill-forge refine`,\n },\n skillsRoot\n );\n } catch (err) {\n throw new UserError(\n `the user-scope skill was updated at ${skillMdPath} (backup ${backup.id}), but recording post-write verification and/or pattern/provenance state failed: ${(err as Error).message}`\n );\n }\n\n emit(options.json, { promoted: true, path: skillMdPath, backupId: backup.id }, () => {\n console.log(`[skill-forge] promoted pattern ${patternId} -> ${skillMdPath} (backup: ${backup.id})`);\n });\n}\n\n// ---------------------------------------------------------------------------\n// rollback\n// ---------------------------------------------------------------------------\n\nexport interface RefineRollbackOptions {\n force?: boolean;\n json?: boolean;\n yes?: boolean;\n}\n\n/**\n * Containment-checks a candidate backup dir under the real backups root before it's ever used —\n * `backupId` has already passed {@link isValidBackupId}'s strict format check (which alone\n * already excludes `/`, `..`, etc.), but this realpath check is defense-in-depth against the\n * backups directory itself resolving somewhere unexpected (e.g. a symlinked SKILL_FORGE_HOME).\n * Tolerates a not-yet-existing backup dir — falls through so {@link readBackupManifest}'s own\n * \"no backup with id\" error fires with its normal message.\n */\nfunction assertBackupDirContained(backupId: string): void {\n const backupsDir = getBackupsDir();\n if (!existsSync(backupsDir)) return;\n const realBackupsDir = realpathSync(backupsDir);\n const backupDir = join(backupsDir, backupId);\n if (!existsSync(backupDir)) return;\n const realBackupDir = realpathSync(backupDir);\n if (!isUnderRoot(realBackupDir, realBackupsDir)) {\n throw new UserError(`refusing rollback: backup id \"${backupId}\" resolves outside the backups directory`);\n }\n}\n\n/**\n * Resolves the realpath of the deepest EXISTING ancestor of `path`, then reattaches the\n * (lexically resolved, not-yet-existing) remainder — giving a best-effort \"where would this\n * path actually land\" answer even when `path` itself doesn't exist yet (e.g. a restore target\n * that was deleted, or a tombstone). If `path` itself exists (regular file OR symlink),\n * `realpathSync` resolves it fully, which is intentional here: a symlink leaf's realpath is its\n * OUTSIDE target, so the containment check below correctly rejects it as a second, independent\n * layer of defense behind the explicit `lstatSync`-based symlink refusal in\n * {@link assertNoSymlinkLeaves}.\n */\nfunction resolveTargetRealPath(path: string): string {\n let lexicalAncestor = resolvePath(path);\n while (true) {\n try {\n const real = realpathSync(lexicalAncestor);\n const remainder = relative(lexicalAncestor, resolvePath(path));\n return remainder === '' ? real : join(real, remainder);\n } catch {\n const parent = dirname(lexicalAncestor);\n if (parent === lexicalAncestor) return resolvePath(path);\n lexicalAncestor = parent;\n }\n }\n}\n\n/**\n * Refuses a rollback whose manifest references any file path outside the currently configured\n * `skillsRoots` — a manifest is only ever written by `createBackup` against paths this CLI itself\n * chose, but a hand-edited or otherwise tampered `manifest.json` could point anywhere on disk.\n * Aborts the whole rollback (restoring nothing) the moment one offending path is found.\n *\n * Containment is checked against the fully-resolved leaf via {@link resolveTargetRealPath} (the\n * deepest existing ancestor's realpath + the remaining lexical path), not just `dirname`'s\n * realpath — a manifest path whose PARENT directory legitimately realpaths inside a configured\n * root, but whose LEAF is itself a symlink pointing outside every root, is caught here too. This\n * is defense-in-depth: {@link assertNoSymlinkLeaves}'s explicit `lstatSync` check is the primary\n * gate for a symlink leaf (refused outright with a clearer error), not this containment check.\n */\nfunction assertManifestPathsWithinRoots(manifest: BackupManifest, config: Config): void {\n if (config.skillsRoots.length === 0) {\n throw new UserError(`refusing rollback ${manifest.id}: no skillsRoots are configured — cannot verify the backup's file paths are within a trusted skills root.`);\n }\n const roots = config.skillsRoots.map((root) => {\n const resolved = resolveConfiguredRoot(root);\n try {\n return realpathSync(resolved);\n } catch {\n return resolved;\n }\n });\n\n for (const file of manifest.files) {\n const resolvedTarget = resolveTargetRealPath(file.path);\n const withinAnyRoot = roots.some((root) => isUnderRoot(resolvedTarget, root));\n if (!withinAnyRoot) {\n throw new UserError(`refusing rollback ${manifest.id}: backup manifest references a path outside configured skills roots — ${file.path}`);\n }\n }\n}\n\n/**\n * Refuses the whole rollback (before any restore/delete is attempted) if any manifest file path\n * currently exists on disk as a symlink. A manifest is only ever written by `createBackup`\n * against paths this CLI itself chose as real files/tombstones, so a symlink now sitting at one\n * of those paths means something else replaced it after the backup was taken — following it\n * (write) or blindly unlinking it (delete) would act on attacker-controlled state. Mirrors the\n * exact lstat-symlink-refusal pattern `writeOverrideFile`/`ensureScopeDirSafe` already use on the\n * capture path. Applied uniformly to both restore (`existed: true`) and tombstone/delete\n * (`existed: false`) entries — deleting an unexpected symlink at a tombstone path is anomalous\n * and should abort rather than silently unlink it.\n */\nfunction assertNoSymlinkLeaves(manifest: BackupManifest): void {\n for (const file of manifest.files) {\n let lst;\n try {\n lst = lstatSync(file.path);\n } catch {\n lst = undefined;\n }\n if (lst?.isSymbolicLink()) {\n throw new UserError(`refusing rollback ${manifest.id}: backup manifest path is currently a symlink — refusing to follow or overwrite it: ${file.path}`);\n }\n }\n}\n\n/**\n * Finds the configured skillsRoot that lexically contains `filePath` (string comparison against\n * `resolveConfiguredRoot(root)`, no filesystem access) and returns both that lexical root and its\n * realpath (falling back to the lexical form if the root itself doesn't exist). Lexical, not\n * filesystem-resolved, containment is deliberate here: {@link walkRealComponents} is the function\n * responsible for safely resolving everything *below* the root, one component at a time — using a\n * filesystem-resolving lookup (like {@link resolveTargetRealPath}) to pick the root in the first\n * place would reintroduce the exact TOCTOU this module exists to close.\n */\nfunction findLexicalRoot(filePath: string, config: Config): { rootLexical: string; rootReal: string } {\n const lexicalTarget = resolvePath(filePath);\n for (const root of config.skillsRoots) {\n const rootLexical = resolveConfiguredRoot(root);\n const rel = relative(rootLexical, lexicalTarget);\n if (rel !== '' && (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel))) continue;\n let rootReal: string;\n try {\n rootReal = realpathSync(rootLexical);\n } catch {\n rootReal = rootLexical;\n }\n return { rootLexical, rootReal };\n }\n throw new UserError(`refusing to operate on ${filePath}: does not resolve lexically under any configured skills root`);\n}\n\ninterface ComponentWalkResult {\n /** Real, symlink-free parent directory of the leaf — undefined iff an intermediate component was missing and `createMissing` was false. */\n parentReal: string | undefined;\n leafComponent: string;\n rootReal: string;\n}\n\n/**\n * The authoritative write-time gate behind {@link safeRestoreWrite} and {@link safeDeleteTombstone}.\n * Walks from the containing skillsRoot's verified realpath down through `filePath`'s intermediate\n * directory components ONE AT A TIME, `lstatSync`-checking each immediately before creating or\n * descending into it — never a single filesystem call (like `realpathSync` or `mkdirSync(...,\n * {recursive: true})`) that could resolve or traverse through a symlink planted at any level after\n * `assertManifestPathsWithinRoots`'s preflight ran (e.g. during the confirm-prompt's await). Aborts\n * with `UserError` the instant a component is found to be a symlink, or exists but isn't a\n * directory. A missing intermediate component is not itself an error: with `createMissing: true`\n * (restore) it is created as a fresh real directory and the walk continues; with `createMissing:\n * false` (delete) the walk stops and `parentReal` comes back `undefined`, telling the caller there\n * is nothing real at that path to delete.\n */\nfunction walkRealComponents(filePath: string, config: Config, createMissing: boolean): ComponentWalkResult {\n const { rootLexical, rootReal } = findLexicalRoot(filePath, config);\n const relPath = relative(rootLexical, resolvePath(filePath));\n const components = relPath.split(sep).filter((c) => c.length > 0);\n if (components.length === 0) {\n throw new UserError(`refusing to operate on ${filePath}: resolves to the skills root itself`);\n }\n const leafComponent = components[components.length - 1];\n const dirComponents = components.slice(0, -1);\n\n let current: string | undefined = rootReal;\n for (const component of dirComponents) {\n if (current === undefined) break;\n const next = join(current, component);\n let lst;\n try {\n lst = lstatSync(next);\n } catch {\n lst = undefined;\n }\n if (lst === undefined) {\n if (!createMissing) {\n current = undefined;\n continue;\n }\n mkdirSync(next);\n current = next;\n continue;\n }\n if (lst.isSymbolicLink()) {\n throw new UserError(`refusing to operate on ${filePath}: intermediate path component is a symlink — ${next}`);\n }\n if (!lst.isDirectory()) {\n throw new UserError(`refusing to operate on ${filePath}: intermediate path component is not a directory — ${next}`);\n }\n current = next;\n }\n\n return { parentReal: current, leafComponent, rootReal };\n}\n\n/**\n * Restores a single manifest entry. This is the sole place a rollback restore ever touches disk:\n * it resolves the real, symlink-free parent directory via {@link walkRealComponents} (creating any\n * missing intermediate directories one level at a time), refuses a leaf that is now a symlink,\n * re-asserts containment under the root as a final belt-and-suspenders check, then writes through a\n * temp file in that verified-real parent followed by an atomic rename — mirroring the\n * tempPath/renameSync + rmSync-on-failure pattern `refinePromoteCommand`'s SKILL.md write already\n * uses. There is no `await` anywhere between the component walk and the rename.\n *\n * Known residual limitation: like any path-string-based (non-fd-relative) filesystem check, this\n * cannot fully close a concurrent-attacker sub-millisecond race in which an already-verified\n * ancestor directory is swapped for a symlink in the gap between its `lstatSync` and the final\n * `renameSync`. Node's `fs` API exposes no `openat`/`O_NOFOLLOW`-relative primitive to eliminate\n * it. This requires an attacker with concurrent write access to the same skills root the process\n * is operating on — who already has comparable ability to tamper with the target files directly.\n */\nfunction safeRestoreWrite(filePath: string, contentBuffer: Buffer, mode: number, config: Config): void {\n const { parentReal, leafComponent, rootReal } = walkRealComponents(filePath, config, true);\n if (parentReal === undefined) {\n throw new UserError(`refusing to restore ${filePath}: could not establish a real parent directory`);\n }\n const finalPath = join(parentReal, leafComponent);\n if (!isUnderRoot(finalPath, rootReal)) {\n throw new UserError(`refusing to restore ${filePath}: resolves outside root ${rootReal}`);\n }\n\n let leafLst;\n try {\n leafLst = lstatSync(finalPath);\n } catch {\n leafLst = undefined;\n }\n if (leafLst?.isSymbolicLink()) {\n throw new UserError(`refusing to restore ${filePath}: it is now a symlink — refusing to follow or overwrite it.`);\n }\n\n const tempPath = `${finalPath}.tmp-${randomUUID()}`;\n try {\n writeFileSync(tempPath, contentBuffer);\n chmodSync(tempPath, mode);\n renameSync(tempPath, finalPath);\n } catch (err) {\n rmSync(tempPath, { force: true });\n throw err;\n }\n}\n\n/**\n * Deletes a single tombstone manifest entry (a file that didn't exist when the backup was taken).\n * Uses the same {@link walkRealComponents} component-by-component symlink refusal as\n * {@link safeRestoreWrite} — without it, a planted symlinked intermediate directory would let\n * `rmSync` traverse through it and delete a file outside every skillsRoot. A missing intermediate\n * component (or a missing leaf) means there is genuinely nothing to delete — not an error.\n */\nfunction safeDeleteTombstone(filePath: string, config: Config): void {\n const { parentReal, leafComponent, rootReal } = walkRealComponents(filePath, config, false);\n if (parentReal === undefined) return;\n const finalPath = join(parentReal, leafComponent);\n if (!isUnderRoot(finalPath, rootReal)) {\n throw new UserError(`refusing to delete ${filePath}: resolves outside root ${rootReal}`);\n }\n\n let leafLst;\n try {\n leafLst = lstatSync(finalPath);\n } catch {\n leafLst = undefined;\n }\n if (leafLst === undefined) return;\n if (leafLst.isSymbolicLink()) {\n throw new UserError(`refusing to delete ${filePath}: it is now a symlink — refusing to follow or overwrite it.`);\n }\n rmSync(finalPath, { force: true });\n}\n\nexport async function refineRollbackCommand(backupId: string, options: RefineRollbackOptions = {}): Promise<void> {\n requireProFeature();\n if (!backupId) throw new UserError('a backup id is required: skill-forge refine rollback <backup-id>');\n if (!isValidBackupId(backupId)) {\n throw new UserError(`invalid backup id \"${backupId}\" — expected the format produced by \\`skill-forge refine promote\\` (e.g. 20260720T142305Z-a1b2)`);\n }\n assertBackupDirContained(backupId);\n\n const manifest = readBackupManifest(backupId);\n\n const config = loadConfig();\n assertManifestPathsWithinRoots(manifest, config);\n assertNoSymlinkLeaves(manifest);\n\n const warnings: string[] = [];\n const sidecar = readPostWriteSidecar(backupId);\n if (sidecar.status !== 'ok') {\n const reason = sidecar.status;\n if (!options.force) {\n throw new UserError(\n `refusing rollback ${backupId}: post-write verification data (post.json) is ${reason} — cannot confirm the current file state matches what was promoted. Pass --force to override.`\n );\n }\n warnings.push(`post-write verification data for backup ${backupId} is ${reason} — restoring without hash verification (--force).`);\n }\n\n const mismatches: string[] = [];\n for (const file of manifest.files) {\n const expected = sidecar.hashes.get(file.path);\n if (expected === undefined) continue;\n const currentSha256 = existsSync(file.path) ? createHash('sha256').update(readFileSync(file.path)).digest('hex') : null;\n if (currentSha256 !== expected) mismatches.push(file.path);\n }\n if (mismatches.length > 0 && !options.force) {\n throw new UserError(\n `refusing rollback ${backupId}: current file(s) differ from the state recorded right after promote — ${mismatches.join(', ')} (pass --force to override)`\n );\n }\n\n const fileList = manifest.files.map((file) => ` ${file.existed ? 'restore' : 'delete'} ${file.path}`).join('\\n');\n if (!options.json) {\n console.log(`[skill-forge] refine rollback ${backupId} — restoring ${manifest.files.length} file(s):`);\n console.log(fileList);\n for (const w of warnings) console.log(`[skill-forge] warning: ${w}`);\n }\n\n const proceed = await confirmWrite(options.yes, options.json, `\\nRoll back backup ${backupId}? Affected files:\\n${fileList}\\n[y/N] `);\n if (!proceed) {\n emit(options.json, { rolledBack: false, cancelled: true, backupId }, () => {\n console.log('[skill-forge] rollback cancelled — nothing restored.');\n });\n return;\n }\n\n const restored: string[] = [];\n for (const file of manifest.files) {\n try {\n // safeRestoreWrite/safeDeleteTombstone are the authoritative write-time gate: they walk\n // every intermediate path component and lstat the leaf immediately before touching disk,\n // with no intervening await, closing the TOCTOU window between the preflight checks above\n // (assertManifestPathsWithinRoots, assertNoSymlinkLeaves) and this loop — e.g. a confirm\n // prompt that waited on user input in between.\n if (!file.existed) {\n safeDeleteTombstone(file.path, config);\n } else {\n safeRestoreWrite(file.path, Buffer.from(file.contentBase64 ?? '', 'base64'), file.mode ?? 0o644, config);\n }\n restored.push(file.path);\n } catch (err) {\n const remaining = manifest.files.slice(restored.length + 1).map((f) => f.path);\n throw new UserError(\n `rollback ${backupId} failed partway through: restored ${restored.length} of ${manifest.files.length} file(s) (${restored.join(', ') || '(none)'}), then failed on ${file.path}: ${(err as Error).message}. NOT restored: ${[file.path, ...remaining].join(', ')}.`\n );\n }\n }\n\n const patternsStore = readPatternsStore();\n const pattern = patternsStore.records.find((p) => p.generalizedBackupId === backupId);\n if (pattern) {\n pattern.status = 'ready';\n pattern.generalizedAt = undefined;\n pattern.generalizedBackupId = undefined;\n writePatternsStore(patternsStore);\n }\n\n emit(options.json, { rolledBack: true, backupId, patternReset: pattern?.id, warnings }, () => {\n console.log(`[skill-forge] rollback complete for ${backupId}${pattern ? ` (pattern ${pattern.id} reset to ready)` : ''}`);\n for (const w of warnings) console.log(`[skill-forge] warning: ${w}`);\n });\n}\n\n// Re-exported for src/cli.ts wiring convenience and for tests that want the full context bundle\n// without re-deriving it (e.g. a future `refine context` debugging subcommand).\nexport { gatherRefineContext, resolveScopes, inventoryAllScopes };\nexport type { Config, ScopeResolution };\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { existsSync, realpathSync, statSync } from 'node:fs';\nimport { isAbsolute, join, relative, resolve as resolvePath } from 'node:path';\nimport type { Config } from '../config.js';\nimport { UserError } from '../errors.js';\nimport { defaultExec, type ExecFn } from '../install/quarantine.js';\nimport { expandHome } from '../util.js';\nimport { fingerprintKey, type OverrideType, type PatchAction, type Pattern, type Refinement } from './models.js';\nimport { readPatternsStore, readRefinementsStore } from './store.js';\n\n/**\n * Portable context gathering for `skill-forge refine` (v0.10): override-precedence scope\n * resolution, an override-file inventory per scope, git status/log of the target skill dir,\n * and similar-past-refinement lookup from the user-scope store. Deliberately out of scope\n * (see `.claude/plans/refinement-merge.md`'s \"What is deliberately NOT ported\" section):\n * syncing to a personal MCP server and session-history harvesting tied to a specific agent's\n * tooling — neither is portable to a plain npm CLI. Nothing here executes anything from a\n * target skill; the only subprocess ever spawned is a fixed `git -C <dir> ...`\n * (argv array, no shell), mirroring `src/commands/watch.ts`'s exec\n * injection discipline.\n */\n\nexport type RefineScope = 'local' | 'shared' | 'user';\n\nexport interface ResolvedProjectScopeDir {\n scope: 'local' | 'shared';\n /** `<cwd>/.claude/skills/<skill>` (local) or `<cwd>/skills/<skill>` (shared). */\n dir: string;\n exists: boolean;\n}\n\nexport interface UserScopeCandidate {\n /** The configured root exactly as given in `config.skillsRoots`. */\n root: string;\n /** `<root>/<skill>` — the candidate base skill directory. */\n dir: string;\n}\n\n/**\n * Resolution of the USER scope for one skill, per plan Amendment 6's\n * explicit order: `--user-root` flag -> the configured root that already\n * (realpath-)contains the target skill -> the sole configured root outside\n * the project -> ambiguous (caller must ask interactively or throw a\n * `UserError` listing candidates — this module never guesses).\n */\nexport type UserScopeResolution =\n | { resolved: true; dir: string; root: string; reason: 'flag' | 'contains-skill' | 'sole-external-root' }\n | { resolved: false; candidates: UserScopeCandidate[] };\n\nexport interface ScopeResolution {\n local: ResolvedProjectScopeDir;\n shared: ResolvedProjectScopeDir;\n user: UserScopeResolution;\n}\n\nfunction resolveConfiguredRoot(root: string): string {\n return resolvePath(expandHome(root));\n}\n\n/**\n * Rejects a skill name that isn't a plain directory basename — no path\n * separators, and not a `.`/`..` dot-segment — BEFORE it's ever joined into\n * a filesystem path. Every place a skill name (CLI `--skill` input, or one\n * read back out of the refinements/patterns store, e.g. `pattern.affectedSkills[0]`)\n * is turned into a directory must call this first; `resolveScopes` below is\n * the single choke point every refine subcommand routes through, so guarding\n * it here also protects a poisoned store entry, not just new captures.\n */\nexport function assertPlainSkillName(name: string): void {\n if (!name || name.trim().length === 0) {\n throw new UserError('skill name must not be empty');\n }\n if (name.includes('/') || name.includes('\\\\') || name === '.' || name === '..') {\n throw new UserError(`invalid skill name \"${name}\" — must be a plain directory name, not a path`);\n }\n}\n\nfunction realpathOrNull(p: string): string | null {\n try {\n return realpathSync(p);\n } catch {\n return null;\n }\n}\n\nfunction isUnderRoot(candidate: string, root: string): boolean {\n const rel = relative(root, candidate);\n return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));\n}\n\n/**\n * Resolves the three override-precedence scopes for `skill` relative to\n * `cwd`: PROJECT LOCAL (`<cwd>/.claude/skills/<skill>`), PROJECT SHARED\n * (`<cwd>/skills/<skill>`), and USER SCOPE per Amendment 6's explicit order.\n * `userRootFlag`, when given, always wins for the user scope regardless of\n * whether it actually contains the skill yet (an agent/user may be\n * bootstrapping a brand-new user-scope skill). Never silently guesses\n * between multiple equally-plausible candidate roots — see\n * `UserScopeResolution`'s `resolved: false` branch.\n */\nexport function resolveScopes(config: Config, cwd: string, skill: string, userRootFlag?: string): ScopeResolution {\n assertPlainSkillName(skill);\n const resolvedCwd = resolvePath(cwd);\n const localDir = join(resolvedCwd, '.claude', 'skills', skill);\n const sharedDir = join(resolvedCwd, 'skills', skill);\n\n const local: ResolvedProjectScopeDir = { scope: 'local', dir: localDir, exists: existsSync(localDir) };\n const shared: ResolvedProjectScopeDir = { scope: 'shared', dir: sharedDir, exists: existsSync(sharedDir) };\n\n const user = resolveUserScope(config, resolvedCwd, skill, userRootFlag);\n\n return { local, shared, user };\n}\n\nfunction resolveUserScope(config: Config, resolvedCwd: string, skill: string, userRootFlag?: string): UserScopeResolution {\n if (userRootFlag) {\n const root = resolvePath(userRootFlag);\n return { resolved: true, dir: join(root, skill), root: userRootFlag, reason: 'flag' };\n }\n\n const cwdReal = realpathOrNull(resolvedCwd) ?? resolvedCwd;\n const candidates: UserScopeCandidate[] = config.skillsRoots.map((root) => ({ root, dir: join(resolveConfiguredRoot(root), skill) }));\n\n // Rank 1: a configured root that already (realpath-)contains the target skill.\n for (const candidate of candidates) {\n const skillReal = realpathOrNull(candidate.dir);\n if (skillReal) {\n return { resolved: true, dir: candidate.dir, root: candidate.root, reason: 'contains-skill' };\n }\n }\n\n // Rank 2: the sole configured root that lies outside the current project.\n const externalCandidates = candidates.filter((candidate) => {\n const rootReal = realpathOrNull(resolveConfiguredRoot(candidate.root)) ?? resolveConfiguredRoot(candidate.root);\n return !isUnderRoot(rootReal, cwdReal) && !isUnderRoot(cwdReal, rootReal);\n });\n if (externalCandidates.length === 1) {\n return { resolved: true, dir: externalCandidates[0].dir, root: externalCandidates[0].root, reason: 'sole-external-root' };\n }\n\n return { resolved: false, candidates: externalCandidates.length > 0 ? externalCandidates : candidates };\n}\n\n// ---------------------------------------------------------------------------\n// Override-file inventory\n// ---------------------------------------------------------------------------\n\nexport interface ScopeOverrideInventory {\n scope: RefineScope;\n dir: string;\n dirExists: boolean;\n skillMd: boolean;\n patchMd: boolean;\n extendMd: boolean;\n configJson: boolean;\n hooksDir: boolean;\n scriptsDir: boolean;\n}\n\nfunction isFile(p: string): boolean {\n try {\n return statSync(p).isFile();\n } catch {\n return false;\n }\n}\n\nfunction isDirectory(p: string): boolean {\n try {\n return statSync(p).isDirectory();\n } catch {\n return false;\n }\n}\n\n/** Inventories which override artifacts exist at a single scope's base directory — the \"why didn't my patch take effect\" primitive `refine which` is built on. */\nexport function inventoryScopeDir(scope: RefineScope, dir: string): ScopeOverrideInventory {\n return {\n scope,\n dir,\n dirExists: isDirectory(dir),\n skillMd: isFile(join(dir, 'SKILL.md')),\n patchMd: isFile(join(dir, 'SKILL.patch.md')),\n extendMd: isFile(join(dir, 'SKILL.extend.md')),\n configJson: isFile(join(dir, 'skill-config.json')),\n hooksDir: isDirectory(join(dir, 'hooks')),\n scriptsDir: isDirectory(join(dir, 'scripts')),\n };\n}\n\n/** Inventories override artifacts across all three resolved scopes, in precedence order (local, shared, user). The user scope is only inventoried when resolved to a single directory. */\nexport function inventoryAllScopes(resolution: ScopeResolution): ScopeOverrideInventory[] {\n const rows = [inventoryScopeDir('local', resolution.local.dir), inventoryScopeDir('shared', resolution.shared.dir)];\n if (resolution.user.resolved) {\n rows.push(inventoryScopeDir('user', resolution.user.dir));\n }\n return rows;\n}\n\n// ---------------------------------------------------------------------------\n// Git status/log of the target skill dir — injectable ExecFn, argv-array only\n// ---------------------------------------------------------------------------\n\nexport interface GitContext {\n isRepo: boolean;\n /** `git status --porcelain` output, only present when `isRepo` is true. */\n status?: string;\n /** `git log -5 --oneline` output, only present when `isRepo` is true. */\n log?: string;\n /** Present when the dir isn't a git repo (or git isn't runnable) — never thrown, always tolerated. */\n detail?: string;\n}\n\nexport interface GitContextDeps {\n /** Injectable subprocess runner, mirroring `src/commands/watch.ts`'s `ExecFn` injection. Defaults to a real argv-array spawn (no shell). */\n exec?: ExecFn;\n}\n\nfunction firstLine(text: string): string {\n return text.split('\\n').find((l) => l.trim().length > 0)?.trim() ?? '(no output)';\n}\n\n/**\n * Runs `git -C <dir> status --porcelain` and `git -C <dir> log -5 --oneline`\n * (fixed argv, no shell) against `skillDir`, tolerant of `dir` not being a\n * git repo at all (or git being unavailable) — never throws. This is the\n * ONLY subprocess this module ever spawns.\n */\nexport async function gatherGitContext(skillDir: string, deps: GitContextDeps = {}): Promise<GitContext> {\n const exec = deps.exec ?? defaultExec;\n\n let statusResult;\n try {\n statusResult = await exec('git', ['-C', skillDir, 'status', '--porcelain'], { cwd: skillDir });\n } catch (err) {\n return { isRepo: false, detail: `git not runnable: ${(err as Error).message}` };\n }\n if (statusResult.code !== 0) {\n return { isRepo: false, detail: firstLine(statusResult.stderr || statusResult.stdout || 'not a git repository') };\n }\n\n let logResult;\n try {\n logResult = await exec('git', ['-C', skillDir, 'log', '-5', '--oneline'], { cwd: skillDir });\n } catch (err) {\n return { isRepo: true, status: statusResult.stdout, detail: `git log failed: ${(err as Error).message}` };\n }\n\n return {\n isRepo: true,\n status: statusResult.stdout,\n log: logResult.code === 0 ? logResult.stdout : undefined,\n detail: logResult.code === 0 ? undefined : firstLine(logResult.stderr || logResult.stdout),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Similar-refinement lookup (by skill, then fingerprintKey match)\n// ---------------------------------------------------------------------------\n\nexport interface SimilarRefinementMatch {\n refinement: Refinement;\n pattern?: Pattern;\n /** `'fingerprint'` when the match shares an exact deterministic join key with the query (see `fingerprintKey`); `'skill'` when it's merely the same skill. */\n matchedBy: 'fingerprint' | 'skill';\n}\n\nexport interface FindSimilarRefinementsOptions {\n targetSection?: string;\n overrideType?: OverrideType;\n patchAction?: PatchAction;\n}\n\n/**\n * Looks up past refinements against `skill` from the user-scope store, most\n * relevant first: an exact `fingerprintKey` match (same skill + target\n * section + override type + patch action) sorts ahead of every other\n * same-skill refinement. Returns every same-skill refinement — callers slice\n * for display. Read-only; never mutates the store.\n */\nexport function findSimilarRefinements(skill: string, options: FindSimilarRefinementsOptions = {}): SimilarRefinementMatch[] {\n const { records: refinements } = readRefinementsStore();\n const { records: patterns } = readPatternsStore();\n const patternById = new Map(patterns.map((p) => [p.id, p] as const));\n\n const bySkill = refinements.filter((r) => r.skill === skill);\n\n const fingerprint =\n options.targetSection !== undefined && options.overrideType !== undefined\n ? fingerprintKey(skill, options.targetSection, options.overrideType, options.patchAction)\n : undefined;\n\n const matches: SimilarRefinementMatch[] = bySkill.map((refinement) => {\n const pattern = refinement.patternId ? patternById.get(refinement.patternId) : undefined;\n const matchedBy: SimilarRefinementMatch['matchedBy'] = fingerprint && pattern?.fingerprint === fingerprint ? 'fingerprint' : 'skill';\n return { refinement, pattern, matchedBy };\n });\n\n return matches.sort((a, b) => {\n if (a.matchedBy === b.matchedBy) return b.refinement.createdAt.localeCompare(a.refinement.createdAt);\n return a.matchedBy === 'fingerprint' ? -1 : 1;\n });\n}\n\n// ---------------------------------------------------------------------------\n// Top-level bundle\n// ---------------------------------------------------------------------------\n\nexport interface RefineContext {\n scopes: ScopeResolution;\n overrideInventory: ScopeOverrideInventory[];\n git: GitContext;\n similarRefinements: SimilarRefinementMatch[];\n}\n\nexport interface GatherRefineContextOptions {\n userRoot?: string;\n targetSection?: string;\n overrideType?: OverrideType;\n patchAction?: PatchAction;\n}\n\n/**\n * Composes the full portable context bundle for capturing a refinement\n * against `skill`: scope resolution, override-file inventory across every\n * resolved scope, git status/log of the resolved USER-scope skill dir (the\n * base skill a promote would eventually mutate — project-scope override\n * dirs rarely have their own git history), and similar past refinements.\n * This is the CLI's analog of `gather_context.py`'s portable subset — see\n * this module's doc comment for what was deliberately dropped.\n */\nexport async function gatherRefineContext(\n config: Config,\n cwd: string,\n skill: string,\n options: GatherRefineContextOptions = {},\n deps: GitContextDeps = {}\n): Promise<RefineContext> {\n const scopes = resolveScopes(config, cwd, skill, options.userRoot);\n const overrideInventory = inventoryAllScopes(scopes);\n const gitTargetDir = scopes.user.resolved ? scopes.user.dir : cwd;\n const git = await gatherGitContext(gitTargetDir, deps);\n const similarRefinements = findSimilarRefinements(skill, {\n targetSection: options.targetSection,\n overrideType: options.overrideType,\n patchAction: options.patchAction,\n });\n\n return { scopes, overrideInventory, git, similarRefinements };\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { randomBytes } from 'node:crypto';\n\n/**\n * Data model for `skill-forge refine` (v0.10): record types, JSON-store semantics, and the\n * pattern fingerprint/similarity criteria. See `.claude/plans/refinement-merge.md` for the\n * full design (Amendments 3 and 7 in particular shape `Pattern`/`Refinement` here).\n *\n * Notable design choices:\n * - `PatternStatus` values are `tracking|ready|generalized|dismissed` (the\n * plan's Goal section).\n * - `Pattern.count` does not exist as a stored field — `patternCount()`\n * derives it from unique occurrence project realpaths, so it can never\n * drift from the occurrences it's counting (Amendment 3).\n * - IDs get a 4-hex random suffix instead of a same-day sequence counter\n * (Amendment 7), avoiding a directory-listing scan to allocate an id.\n */\n\nexport type RefinementCategory = 'trigger' | 'content' | 'hook' | 'tool' | 'pattern' | 'config' | 'new';\n\nexport const REFINEMENT_CATEGORIES: readonly RefinementCategory[] = ['trigger', 'content', 'hook', 'tool', 'pattern', 'config', 'new'];\n\n/** All seven override types (plan Amendment 2). patch/extend/config are rendered; full/hook/script are verbatim override-file writes; new creates an extension section. */\nexport type OverrideType = 'patch' | 'extend' | 'config' | 'full' | 'hook' | 'script' | 'new';\n\nexport const OVERRIDE_TYPES: readonly OverrideType[] = ['patch', 'extend', 'config', 'full', 'hook', 'script', 'new'];\n\n/** The six section-patch actions. Owned here (not patch.ts) so both models.ts and patch.ts can import the same type without a circular dependency. */\nexport type PatchAction = 'append' | 'prepend' | 'replace-section' | 'insert-after' | 'insert-before' | 'delete-section';\n\nexport const PATCH_ACTIONS: readonly PatchAction[] = ['append', 'prepend', 'replace-section', 'insert-after', 'insert-before', 'delete-section'];\n\n/** tracking: count(unique projects) < 2. ready: >= 2 (Amendment 3's \"second, different project\" rule). generalized: promoted to user scope. dismissed: manually rejected. */\nexport type PatternStatus = 'tracking' | 'ready' | 'generalized' | 'dismissed';\n\nexport const PATTERN_STATUSES: readonly PatternStatus[] = ['tracking', 'ready', 'generalized', 'dismissed'];\n\nexport type GeneralizationPotential = 'high' | 'medium' | 'low';\n\n/** Project scope a capture writes its override artifacts to (plan's override-precedence section). */\nexport type CaptureScope = 'local' | 'shared';\n\n/** Lifecycle status of a single Refinement record. */\nexport type RefinementStatus = 'pending' | 'applied' | 'generalized';\n\nexport function isRefinementCategory(value: string): value is RefinementCategory {\n return (REFINEMENT_CATEGORIES as readonly string[]).includes(value);\n}\n\nexport function isOverrideType(value: string): value is OverrideType {\n return (OVERRIDE_TYPES as readonly string[]).includes(value);\n}\n\nexport function isPatchAction(value: string): value is PatchAction {\n return (PATCH_ACTIONS as readonly string[]).includes(value);\n}\n\nexport function isPatternStatus(value: string): value is PatternStatus {\n return (PATTERN_STATUSES as readonly string[]).includes(value);\n}\n\n/** All three refinement lifecycle statuses. */\nexport const REFINEMENT_STATUSES: readonly RefinementStatus[] = ['pending', 'applied', 'generalized'];\n\nexport function isRefinementStatus(value: string): value is RefinementStatus {\n return (REFINEMENT_STATUSES as readonly string[]).includes(value);\n}\n\n/**\n * A single captured/applied refinement against an installed skill. `id` is\n * `REF-YYYYMMDD-xxxx` (see {@link generateRefinementId}). `patchAction`/\n * `patchMarker`/`patchContent` are only meaningful when `overrideType`\n * is `'patch'`.\n */\nexport interface Refinement {\n id: string;\n skill: string;\n category: RefinementCategory;\n overrideType: OverrideType;\n /** Heading-path selector, e.g. `\"Hooks > duplicate-check\"` (plan Amendment 4). */\n targetSection: string;\n\n expectedBehavior: string;\n actualBehavior: string;\n example?: string;\n desiredOutcome?: string;\n\n rootCause?: string;\n confidence?: number;\n\n /** Human-readable project name/label (may differ from projectRoot's basename). */\n project: string;\n /** Realpath of the project root this refinement was captured in — the unit the second-project rule counts by. */\n projectRoot: string;\n\n patchAction?: PatchAction;\n patchMarker?: string;\n patchContent?: string;\n\n /** The pattern this refinement was joined into (auto-matched or explicit `--pattern-id`). */\n patternId?: string;\n generalizationPotential?: GeneralizationPotential;\n\n scope?: CaptureScope;\n status: RefinementStatus;\n\n createdAt: string;\n appliedAt?: string;\n}\n\n/** One occurrence of a pattern: a refinement captured against it in a given project, at a given time. */\nexport interface PatternOccurrence {\n refinementId: string;\n /** Realpath of the project root the occurrence was captured in — see {@link patternCount}. */\n projectRealpath: string;\n timestamp: string;\n}\n\n/**\n * A refinement pattern tracked across projects. `id` is `PAT-xxxx` (see\n * {@link generatePatternId}). There is deliberately no stored `count` field\n * (Amendment 3) — always derive it with {@link patternCount}.\n */\nexport interface Pattern {\n id: string;\n name: string;\n description: string;\n affectedSkills: string[];\n refinementIds: string[];\n occurrences: PatternOccurrence[];\n status: PatternStatus;\n /** file/artifact path -> proposed change content. `proposedChanges.content` holds the representative sample used for content-similarity joins (see {@link contentSimilarity}). */\n proposedChanges: Record<string, string>;\n /** Deterministic fingerprint of skill+target+overrideType+action (see {@link fingerprintKey}) for exact-match joins. */\n fingerprint: string;\n\n firstSeen: string;\n lastSeen: string;\n generalizedAt?: string;\n /** Backup id created by the promote that generalized this pattern; needed for `--rollback`. */\n generalizedBackupId?: string;\n dismissedReason?: string;\n}\n\n/** Counts unique project realpaths across a pattern's occurrences — the value `count` would have held, always in sync with `occurrences`. */\nexport function patternCount(pattern: Pattern): number {\n return new Set(pattern.occurrences.map((o) => o.projectRealpath)).size;\n}\n\n/** Injectable random-hex source (4 hex chars) so tests can produce deterministic ids. */\nexport type RandomHex = () => string;\n\n/** Default `RandomHex` source (4 hex chars from crypto). Shared by store.ts's id/backup-id generation. */\nexport function defaultRandomHex(): string {\n return randomBytes(2).toString('hex');\n}\n\nfunction formatYYYYMMDD(date: Date): string {\n const yyyy = date.getUTCFullYear().toString().padStart(4, '0');\n const mm = (date.getUTCMonth() + 1).toString().padStart(2, '0');\n const dd = date.getUTCDate().toString().padStart(2, '0');\n return `${yyyy}${mm}${dd}`;\n}\n\n/** Generates `REF-YYYYMMDD-xxxx`, retrying the random suffix on collision against `existingIds`. */\nexport function generateRefinementId(\n existingIds: ReadonlySet<string> = new Set(),\n now: Date = new Date(),\n randomHex: RandomHex = defaultRandomHex\n): string {\n const datePart = formatYYYYMMDD(now);\n let id: string;\n do {\n id = `REF-${datePart}-${randomHex()}`;\n } while (existingIds.has(id));\n return id;\n}\n\n/** Generates `PAT-xxxx`, retrying the random suffix on collision against `existingIds`. */\nexport function generatePatternId(existingIds: ReadonlySet<string> = new Set(), randomHex: RandomHex = defaultRandomHex): string {\n let id: string;\n do {\n id = `PAT-${randomHex()}`;\n } while (existingIds.has(id));\n return id;\n}\n\n/**\n * Deterministic join key: same skill + target section + override type +\n * patch action always fingerprint identically, regardless of the exact\n * wording of the expected/actual behavior text. Case-insensitive,\n * whitespace-normalized (matches the selector-matching rules patch.ts uses\n * for target sections — plan Amendment 4).\n */\nexport function fingerprintKey(skill: string, target: string, overrideType: OverrideType, action?: PatchAction | null): string {\n const normalize = (s: string): string => s.trim().toLowerCase().replace(/\\s+/g, ' ');\n return [normalize(skill), normalize(target), overrideType, action ?? ''].join('::');\n}\n\n/**\n * Token-Jaccard content similarity: lowercase, whitespace-split into\n * a token set per side, then intersection-over-union. Pure content-shape\n * signal — callers combine it with {@link fingerprintKey} rather than using\n * it alone. A join threshold of `> 0.7` is the caller's decision (see\n * `store.ts`'s `SIMILARITY_JOIN_THRESHOLD`), not baked in here.\n */\nexport function contentSimilarity(a: string, b: string): number {\n const tokenize = (s: string): Set<string> => new Set(s.toLowerCase().split(/\\s+/).filter(Boolean));\n const tokensA = tokenize(a);\n const tokensB = tokenize(b);\n if (tokensA.size === 0 || tokensB.size === 0) return 0;\n\n let intersectionSize = 0;\n for (const token of tokensA) {\n if (tokensB.has(token)) intersectionSize++;\n }\n const unionSize = tokensA.size + tokensB.size - intersectionSize;\n return unionSize === 0 ? 0 : intersectionSize / unionSize;\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { createHash, randomUUID } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, realpathSync } from 'node:fs';\nimport { basename, dirname, join, resolve as resolvePath } from 'node:path';\nimport { getHomeDir } from '../config.js';\nimport { UserError } from '../errors.js';\nimport { writeJsonAtomic } from '../util.js';\nimport {\n contentSimilarity,\n defaultRandomHex,\n fingerprintKey,\n generatePatternId,\n generateRefinementId,\n patternCount,\n type CaptureScope,\n type GeneralizationPotential,\n type OverrideType,\n type PatchAction,\n type Pattern,\n type RandomHex,\n type Refinement,\n type RefinementCategory,\n type RefinementStatus,\n} from './models.js';\n\n/**\n * User-scope store for `skill-forge refine` (v0.10) — `~/.skill-forge/refinements/`\n * (honors SKILL_FORGE_HOME via `getHomeDir()`, same as `queue.ts`). Two\n * versioned JSON envelopes — `refinements.json` and `patterns.json` — plus\n * per-promotion backup manifests are the store. `refine list`/`refine patterns` (a\n * different lane's `src/commands/refine.ts`) render the human view from\n * these files; there is no other canonical store.\n *\n * Writes are atomic (temp file + rename within the same directory, same\n * idiom as `queue.ts`'s `writeQueue`) and mode 0600. There is no\n * cross-process lock — this is a single-user CLI, and atomic rename is the\n * consistency boundary (plan Amendment 7).\n */\n\nexport interface RefinementStore {\n schemaVersion: 1;\n records: Refinement[];\n}\n\nexport interface PatternStore {\n schemaVersion: 1;\n records: Pattern[];\n}\n\n/** Content-similarity join threshold (plan Amendment 3): a refinement's patch content joins an existing pattern when {@link contentSimilarity} exceeds this. */\nexport const SIMILARITY_JOIN_THRESHOLD = 0.7;\n\n/** unique-project count a pattern needs to flip from `tracking` to `ready` (the \"second, different project\" rule). */\nconst READY_THRESHOLD = 2;\n\nexport function getRefinementsDir(): string {\n return join(getHomeDir(), 'refinements');\n}\n\nexport function getRefinementsPath(): string {\n return join(getRefinementsDir(), 'refinements.json');\n}\n\nexport function getPatternsPath(): string {\n return join(getRefinementsDir(), 'patterns.json');\n}\n\nexport function getBackupsDir(): string {\n return join(getRefinementsDir(), 'backups');\n}\n\n\n/**\n * Reads and JSON-parses `path`, labeling both failure modes with `label` in\n * a `UserError`: unreadable (permissions, etc.) and malformed (not valid\n * JSON). Shared by {@link readStore} and {@link readBackupManifest} — the\n * only difference between those two callers is what a *missing* file means.\n */\nfunction readJsonFile(path: string, label: string): unknown {\n let raw: string;\n try {\n raw = readFileSync(path, 'utf-8');\n } catch (err) {\n throw new UserError(`unable to read ${label} at ${path}: ${(err as Error).message}`);\n }\n\n try {\n return JSON.parse(raw);\n } catch {\n throw new UserError(`malformed ${label} at ${path}: not valid JSON`);\n }\n}\n\n/**\n * Tolerant, typed envelope load: a missing file is an empty store (first\n * run); a present-but-unparseable or wrong-shaped file is a loud\n * `UserError` naming the file, never a silent reset to empty (which would\n * discard real records).\n */\nfunction readStore<T>(path: string, label: string, validateRecord?: (record: unknown, index: number) => void): { schemaVersion: 1; records: T[] } {\n if (!existsSync(path)) {\n return { schemaVersion: 1, records: [] };\n }\n\n const parsed = readJsonFile(path, label);\n if (typeof parsed !== 'object' || parsed === null || !Array.isArray((parsed as { records?: unknown }).records)) {\n throw new UserError(`malformed ${label} at ${path}: expected an object with a \"records\" array`);\n }\n\n const records = (parsed as { records: unknown[] }).records;\n if (validateRecord) {\n records.forEach((record, index) => validateRecord(record, index));\n }\n\n return { schemaVersion: 1, records: records as T[] };\n}\n\n/**\n * Minimal shape check for a `refinements.json` record — fails loud, naming the file, instead of\n * surfacing a later `undefined`-property TypeError somewhere downstream. Also enforces the\n * `overrideType`-conditional coupling of `patchAction`/`patchMarker`/`patchContent` (merge-gate\n * CONTRACT-HARDENING C1/C2): those fields are only meaningful — and only allowed to be present —\n * when `overrideType` is `'patch'`, matching `schemas/refinement.schema.json`'s `if`/`then`.\n */\nfunction validateRefinementRecordShape(record: unknown, index: number, path: string): void {\n if (typeof record !== 'object' || record === null) {\n throw new UserError(`malformed refinements.json at ${path}: record[${index}] is not an object`);\n }\n const r = record as Record<string, unknown>;\n if (typeof r.id !== 'string' || r.id.length === 0) {\n throw new UserError(`malformed refinements.json at ${path}: record[${index}] is missing a valid \"id\"`);\n }\n if (typeof r.createdAt !== 'string' || r.createdAt.length === 0) {\n throw new UserError(`malformed refinements.json at ${path}: record \"${r.id}\" is missing a valid \"createdAt\"`);\n }\n if (typeof r.status !== 'string' || r.status.length === 0) {\n throw new UserError(`malformed refinements.json at ${path}: record \"${r.id}\" is missing a valid \"status\"`);\n }\n if (typeof r.skill !== 'string' || r.skill.length === 0) {\n throw new UserError(`malformed refinements.json at ${path}: record \"${r.id}\" is missing a valid \"skill\"`);\n }\n\n // NOTE (deliberate narrowing of merge-gate CONTRACT-HARDENING C1/C2's literal wording): the\n // finding as written would also require patchMarker/patchContent to be ABSENT whenever\n // overrideType !== 'patch'. That's contradicted by primary evidence in this same lane's\n // src/commands/refine.ts — `refineCaptureCommand` passes `patchContent: content` for every one\n // of the seven override types (renderCaptureArtifact requires non-empty content for all of\n // them, not just 'patch'), and passes `patchMarker: opts.marker` unconditionally too (reused as\n // the destination filename override for full/hook/script). Enforcing absence here would throw\n // immediately after any legitimate extend/new/config/full/hook/script capture. So: patchAction\n // is strictly coupled to overrideType === 'patch' (verified safe — validateCaptureOptions only\n // ever sets `action` for that override type); patchMarker's presence is required only when a\n // patch action that needs a marker is used (matches patch.ts's requireMarker); patchContent is\n // required whenever overrideType === 'patch' (the only case store.ts itself depends on the\n // field for, via content-similarity joins) but is not restricted for the other six types.\n const patchActionsRequiringMarker = new Set(['insert-after', 'insert-before', 'replace-section', 'delete-section']);\n if (r.overrideType === 'patch') {\n if (typeof r.patchAction !== 'string' || r.patchAction.length === 0) {\n throw new UserError(`malformed refinements.json at ${path}: record \"${r.id}\" has overrideType \"patch\" but is missing \"patchAction\"`);\n }\n if (patchActionsRequiringMarker.has(r.patchAction) && (typeof r.patchMarker !== 'string' || r.patchMarker.length === 0)) {\n throw new UserError(\n `malformed refinements.json at ${path}: record \"${r.id}\" has patchAction \"${r.patchAction}\" but is missing \"patchMarker\"`\n );\n }\n if (typeof r.patchContent !== 'string' || r.patchContent.length === 0) {\n throw new UserError(`malformed refinements.json at ${path}: record \"${r.id}\" has overrideType \"patch\" but is missing \"patchContent\"`);\n }\n } else if (r.patchAction !== undefined) {\n throw new UserError(\n `malformed refinements.json at ${path}: record \"${r.id}\" has overrideType \"${String(r.overrideType)}\" but carries \"patchAction\" — only valid when overrideType is \"patch\"`\n );\n }\n}\n\n/**\n * Minimal shape check for a `patterns.json` record — same rationale as\n * {@link validateRefinementRecordShape}. Also enforces the `status`-conditional lifecycle\n * coupling (merge-gate CONTRACT-HARDENING I1): `dismissed` requires `dismissedReason`,\n * `generalized` requires `generalizedAt`/`generalizedBackupId`, matching\n * `schemas/pattern.schema.json`'s `if`/`then`.\n */\nfunction validatePatternRecordShape(record: unknown, index: number, path: string): void {\n if (typeof record !== 'object' || record === null) {\n throw new UserError(`malformed patterns.json at ${path}: record[${index}] is not an object`);\n }\n const p = record as Record<string, unknown>;\n if (typeof p.id !== 'string' || p.id.length === 0) {\n throw new UserError(`malformed patterns.json at ${path}: record[${index}] is missing a valid \"id\"`);\n }\n if (typeof p.status !== 'string' || p.status.length === 0) {\n throw new UserError(`malformed patterns.json at ${path}: record \"${p.id}\" is missing a valid \"status\"`);\n }\n if (!Array.isArray(p.occurrences)) {\n throw new UserError(`malformed patterns.json at ${path}: record \"${p.id}\" is missing a valid \"occurrences\" array`);\n }\n\n if (p.status === 'dismissed' && (typeof p.dismissedReason !== 'string' || p.dismissedReason.length === 0)) {\n throw new UserError(`malformed patterns.json at ${path}: record \"${p.id}\" has status \"dismissed\" but is missing \"dismissedReason\"`);\n }\n if (p.status === 'generalized') {\n if (typeof p.generalizedAt !== 'string' || p.generalizedAt.length === 0) {\n throw new UserError(`malformed patterns.json at ${path}: record \"${p.id}\" has status \"generalized\" but is missing \"generalizedAt\"`);\n }\n if (typeof p.generalizedBackupId !== 'string' || p.generalizedBackupId.length === 0) {\n throw new UserError(`malformed patterns.json at ${path}: record \"${p.id}\" has status \"generalized\" but is missing \"generalizedBackupId\"`);\n }\n }\n}\n\nexport function readRefinementsStore(): RefinementStore {\n const path = getRefinementsPath();\n return readStore<Refinement>(path, 'refinements.json', (record, index) => validateRefinementRecordShape(record, index, path));\n}\n\nexport function writeRefinementsStore(store: RefinementStore): void {\n writeJsonAtomic(getRefinementsPath(), store);\n}\n\nexport function readPatternsStore(): PatternStore {\n const path = getPatternsPath();\n return readStore<Pattern>(path, 'patterns.json', (record, index) => validatePatternRecordShape(record, index, path));\n}\n\nexport function writePatternsStore(store: PatternStore): void {\n writeJsonAtomic(getPatternsPath(), store);\n}\n\n/** Resolves a project root to its realpath (the identity the second-project rule counts by); falls back to the resolved-but-unresolved path if the directory doesn't exist (e.g. it was removed since capture). */\nfunction resolveProjectRealpath(projectRoot: string): string {\n const resolved = resolvePath(projectRoot);\n try {\n return realpathSync(resolved);\n } catch {\n return resolved;\n }\n}\n\nfunction generatePatternName(refinement: Refinement): string {\n const parts = refinement.targetSection\n .replace(/\\//g, ' ')\n .replace(/-/g, ' ')\n .split(/\\s+/)\n .filter(Boolean);\n const lastThree = parts.slice(-3);\n if (lastThree.length === 0) return refinement.targetSection || `pattern-${refinement.skill}`;\n return lastThree.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');\n}\n\nfunction generatePatternDescription(refinement: Refinement): string {\n const category = refinement.category.charAt(0).toUpperCase() + refinement.category.slice(1);\n const cause = refinement.rootCause || refinement.expectedBehavior;\n return `${category} modification for ${refinement.skill}: ${cause}`;\n}\n\n/**\n * Finds or creates the pattern this refinement belongs to, mutating\n * `records` in place (pushing a new pattern, or mutating the matched one)\n * and returning it. Join order: explicit `patternId` override, then exact\n * `fingerprintKey` match, then content-similarity `> SIMILARITY_JOIN_THRESHOLD`\n * against the pattern's stored sample content — all skipped for\n * `generalized`/`dismissed` patterns (a settled pattern doesn't silently\n * reabsorb new occurrences).\n */\nfunction upsertPattern(\n records: Pattern[],\n refinement: Refinement,\n projectRealpath: string,\n nowIso: string,\n randomHex: RandomHex | undefined,\n explicitPatternId: string | undefined\n): { pattern: Pattern; becameReady: boolean } {\n let pattern: Pattern | undefined;\n\n if (explicitPatternId) {\n pattern = records.find((p) => p.id === explicitPatternId);\n if (!pattern) {\n throw new UserError(`no pattern with id: ${explicitPatternId}`);\n }\n if (pattern.status === 'dismissed' || pattern.status === 'generalized') {\n throw new UserError(`cannot join pattern ${explicitPatternId}: it is ${pattern.status} and no longer accepts new occurrences`);\n }\n } else {\n const joinable = records.filter((p) => p.status !== 'generalized' && p.status !== 'dismissed');\n const fingerprint = fingerprintKey(refinement.skill, refinement.targetSection, refinement.overrideType, refinement.patchAction);\n pattern = joinable.find((p) => p.fingerprint === fingerprint);\n\n if (!pattern && refinement.patchContent) {\n pattern = joinable.find((p) => {\n const sample = p.proposedChanges.content;\n return sample ? contentSimilarity(refinement.patchContent!, sample) > SIMILARITY_JOIN_THRESHOLD : false;\n });\n }\n }\n\n const wasTracking = pattern?.status === 'tracking';\n\n if (!pattern) {\n const created: Pattern = {\n id: generatePatternId(new Set(records.map((p) => p.id)), randomHex),\n name: generatePatternName(refinement),\n description: generatePatternDescription(refinement),\n affectedSkills: [refinement.skill],\n refinementIds: [refinement.id],\n occurrences: [{ refinementId: refinement.id, projectRealpath, timestamp: nowIso }],\n status: 'tracking',\n proposedChanges: refinement.patchContent ? { content: refinement.patchContent } : {},\n fingerprint: fingerprintKey(refinement.skill, refinement.targetSection, refinement.overrideType, refinement.patchAction),\n firstSeen: nowIso,\n lastSeen: nowIso,\n };\n records.push(created);\n return { pattern: created, becameReady: false };\n }\n\n if (!pattern.affectedSkills.includes(refinement.skill)) pattern.affectedSkills.push(refinement.skill);\n if (!pattern.refinementIds.includes(refinement.id)) pattern.refinementIds.push(refinement.id);\n pattern.occurrences.push({ refinementId: refinement.id, projectRealpath, timestamp: nowIso });\n pattern.lastSeen = nowIso;\n\n const becameReady = wasTracking && patternCount(pattern) >= READY_THRESHOLD;\n if (becameReady) pattern.status = 'ready';\n\n return { pattern, becameReady };\n}\n\nexport interface RecordRefinementInput {\n skill: string;\n category: RefinementCategory;\n overrideType: OverrideType;\n targetSection: string;\n expectedBehavior: string;\n actualBehavior: string;\n example?: string;\n desiredOutcome?: string;\n rootCause?: string;\n confidence?: number;\n project: string;\n /** Any path to the project root; resolved to a realpath for pattern-occurrence identity. */\n projectRoot: string;\n patchAction?: PatchAction;\n patchMarker?: string;\n patchContent?: string;\n generalizationPotential?: GeneralizationPotential;\n status?: RefinementStatus;\n scope?: CaptureScope;\n /** Explicit join override (plan Amendment 3's `--pattern-id`): must name an existing, non-dismissed/generalized pattern or a `UserError` is thrown. */\n patternId?: string;\n}\n\nexport interface RecordRefinementDeps {\n now?: Date;\n randomHex?: RandomHex;\n /**\n * Pre-minted refinement id to use verbatim instead of generating one — lets a caller (e.g.\n * `refineCaptureCommand`) embed the exact id it already committed to in a rendered artifact,\n * without smuggling a constant `randomHex` closure into {@link generateRefinementId}'s\n * collision-retry loop (a constant closure turns any id collision into an infinite loop, and\n * previously also neutered {@link generatePatternId}'s independent retry — see merge-gate\n * IMPORTANT 3). When omitted, `randomHex` (or the real crypto-random default) is used as\n * before.\n */\n id?: string;\n}\n\nexport interface RecordRefinementResult {\n refinement: Refinement;\n pattern: Pattern;\n /** True only when this call is the one that flipped the pattern tracking -> ready (Amendment 3's second-project rule). */\n patternBecameReady: boolean;\n}\n\n/**\n * Records a captured refinement: allocates its id, resolves the pattern it\n * belongs to (creating one if none matches), appends the refinement and\n * writes both stores atomically. A same-project repeat against an existing\n * `tracking` pattern does NOT flip it to `ready` — only a second, distinct\n * project's realpath does (Amendment 3).\n */\nexport function recordRefinement(input: RecordRefinementInput, deps: RecordRefinementDeps = {}): RecordRefinementResult {\n const now = deps.now ?? new Date();\n const nowIso = now.toISOString();\n\n const refinementsStore = readRefinementsStore();\n const patternsStore = readPatternsStore();\n\n const existingRefIds = new Set(refinementsStore.records.map((r) => r.id));\n const refinementId = deps.id ?? generateRefinementId(existingRefIds, now, deps.randomHex);\n const projectRealpath = resolveProjectRealpath(input.projectRoot);\n\n const refinement: Refinement = {\n id: refinementId,\n skill: input.skill,\n category: input.category,\n overrideType: input.overrideType,\n targetSection: input.targetSection,\n expectedBehavior: input.expectedBehavior,\n actualBehavior: input.actualBehavior,\n example: input.example,\n desiredOutcome: input.desiredOutcome,\n rootCause: input.rootCause,\n confidence: input.confidence,\n project: input.project,\n projectRoot: projectRealpath,\n patchAction: input.patchAction,\n patchMarker: input.patchMarker,\n patchContent: input.patchContent,\n generalizationPotential: input.generalizationPotential,\n scope: input.scope,\n status: input.status ?? 'applied',\n createdAt: nowIso,\n };\n\n const { pattern, becameReady } = upsertPattern(patternsStore.records, refinement, projectRealpath, nowIso, deps.randomHex, input.patternId);\n refinement.patternId = pattern.id;\n\n refinementsStore.records.push(refinement);\n\n writeRefinementsStore(refinementsStore);\n try {\n writePatternsStore(patternsStore);\n } catch (err) {\n throw new UserError(\n `refinements.json was updated with refinement ${refinement.id}, but patterns.json failed to update: ${(err as Error).message}`\n );\n }\n\n return { refinement, pattern, patternBecameReady: becameReady };\n}\n\n/** Marks a pattern `dismissed` (won't generalize) with a reason. Throws `UserError` if `id` isn't found. */\nexport function dismissPattern(id: string, reason: string): Pattern {\n const store = readPatternsStore();\n const pattern = store.records.find((p) => p.id === id);\n if (!pattern) {\n throw new UserError(`no pattern with id: ${id}`);\n }\n pattern.status = 'dismissed';\n pattern.dismissedReason = reason;\n pattern.lastSeen = new Date().toISOString();\n writePatternsStore(store);\n return pattern;\n}\n\n/** Marks a pattern `generalized`, recording the backup id created by the promote that applied it (needed for `--rollback`). Throws `UserError` if `id` isn't found. */\nexport function markGeneralized(id: string, backupId: string): Pattern {\n const store = readPatternsStore();\n const pattern = store.records.find((p) => p.id === id);\n if (!pattern) {\n throw new UserError(`no pattern with id: ${id}`);\n }\n const nowIso = new Date().toISOString();\n pattern.status = 'generalized';\n pattern.generalizedAt = nowIso;\n pattern.generalizedBackupId = backupId;\n pattern.lastSeen = nowIso;\n writePatternsStore(store);\n return pattern;\n}\n\nexport interface BackupManifestEntry {\n path: string;\n /** False = a tombstone: the file did not exist when the backup was taken (so restoring it means deleting it). */\n existed: boolean;\n sha256: string | null;\n /** POSIX permission bits (e.g. 0o644); null for a tombstone. */\n mode: number | null;\n /** Base64-encoded original bytes; null for a tombstone. */\n contentBase64: string | null;\n}\n\nexport interface BackupManifest {\n id: string;\n createdAt: string;\n files: BackupManifestEntry[];\n}\n\nexport interface CreateBackupDeps {\n now?: Date;\n randomHex?: RandomHex;\n}\n\nfunction formatBackupTimestamp(date: Date): string {\n return date.toISOString().replace(/[-:]/g, '').replace(/\\.\\d{3}Z$/, 'Z');\n}\n\n/**\n * Matches exactly what {@link formatBackupTimestamp} + a 4-hex `randomHex()` suffix produces\n * (`YYYYMMDDTHHMMSSZ-xxxx`) — nothing else is a valid backup id. `readBackupManifest` rejects\n * anything else before it's ever joined into a path, closing off `../`-style traversal via a\n * user-supplied `<backup-id>` positional.\n */\nconst BACKUP_ID_PATTERN = /^\\d{8}T\\d{6}Z-[0-9a-f]{4}$/;\n\nexport function isValidBackupId(id: string): boolean {\n return BACKUP_ID_PATTERN.test(id);\n}\n\n/**\n * Backs up the current on-disk state of `filePaths` (pre-promotion) into\n * `backups/<id>/manifest.json`: per-file sha256 + original bytes (base64) +\n * mode, or a tombstone entry when the file doesn't exist yet (plan\n * Amendment 5). The manifest alone is sufficient to restore every file —\n * `--rollback` reads it back with {@link readBackupManifest}.\n */\nexport function createBackup(filePaths: readonly string[], deps: CreateBackupDeps = {}): BackupManifest {\n const now = deps.now ?? new Date();\n const randomHex = deps.randomHex ?? defaultRandomHex;\n const id = `${formatBackupTimestamp(now)}-${randomHex()}`;\n\n const files: BackupManifestEntry[] = filePaths.map((path) => {\n if (!existsSync(path)) {\n return { path, existed: false, sha256: null, mode: null, contentBase64: null };\n }\n const bytes = readFileSync(path);\n const mode = statSync(path).mode & 0o777;\n const sha256 = createHash('sha256').update(bytes).digest('hex');\n return { path, existed: true, sha256, mode, contentBase64: bytes.toString('base64') };\n });\n\n const manifest: BackupManifest = { id, createdAt: now.toISOString(), files };\n writeJsonAtomic(join(getBackupsDir(), id, 'manifest.json'), manifest);\n\n return manifest;\n}\n\n/**\n * Validates the shape of a parsed `manifest.json` before it is ever trusted by a rollback restore\n * loop (merge-gate IMPORTANT 4): the top-level envelope (`id`/`createdAt`/`files` array) plus\n * every `files[]` entry — `path`/`existed` always required, and `sha256`/`mode`/`contentBase64`\n * required (non-null) exactly when `existed` is `true` (a tombstone carries `null` for all three,\n * matching {@link createBackup}'s own tombstone branch). Throws `UserError` naming `path` on any\n * violation, BEFORE `refineRollbackCommand`'s restore loop touches disk — closing the gap where a\n * malformed manifest previously surfaced as a raw `TypeError` mid-restore, after some files may\n * already have been overwritten.\n */\nfunction validateBackupManifestShape(value: unknown, path: string): asserts value is BackupManifest {\n if (typeof value !== 'object' || value === null) {\n throw new UserError(`malformed backup manifest at ${path}: not an object`);\n }\n const m = value as Record<string, unknown>;\n if (typeof m.id !== 'string' || m.id.length === 0) {\n throw new UserError(`malformed backup manifest at ${path}: missing a valid \"id\"`);\n }\n if (typeof m.createdAt !== 'string' || m.createdAt.length === 0) {\n throw new UserError(`malformed backup manifest at ${path}: missing a valid \"createdAt\"`);\n }\n if (!Array.isArray(m.files)) {\n throw new UserError(`malformed backup manifest at ${path}: missing a valid \"files\" array`);\n }\n\n m.files.forEach((entry, index) => {\n if (typeof entry !== 'object' || entry === null) {\n throw new UserError(`malformed backup manifest at ${path}: files[${index}] is not an object`);\n }\n const f = entry as Record<string, unknown>;\n if (typeof f.path !== 'string' || f.path.length === 0) {\n throw new UserError(`malformed backup manifest at ${path}: files[${index}] is missing a valid \"path\"`);\n }\n if (typeof f.existed !== 'boolean') {\n throw new UserError(`malformed backup manifest at ${path}: files[${index}] (\"${f.path}\") is missing a valid \"existed\" (must be boolean)`);\n }\n if (f.existed) {\n if (typeof f.sha256 !== 'string' || f.sha256.length === 0) {\n throw new UserError(`malformed backup manifest at ${path}: files[${index}] (\"${f.path}\") has existed:true but is missing \"sha256\"`);\n }\n if (typeof f.mode !== 'number') {\n throw new UserError(`malformed backup manifest at ${path}: files[${index}] (\"${f.path}\") has existed:true but is missing a numeric \"mode\"`);\n }\n if (typeof f.contentBase64 !== 'string') {\n throw new UserError(`malformed backup manifest at ${path}: files[${index}] (\"${f.path}\") has existed:true but is missing \"contentBase64\"`);\n }\n } else {\n if (f.sha256 !== null || f.mode !== null || f.contentBase64 !== null) {\n throw new UserError(`malformed backup manifest at ${path}: files[${index}] (\"${f.path}\") has existed:false (a tombstone) but sha256/mode/contentBase64 are not all null`);\n }\n }\n });\n}\n\n/** Reads back a backup manifest written by {@link createBackup}. Throws `UserError` if the backup id is unknown or its manifest is unreadable/malformed/wrong-shaped. */\nexport function readBackupManifest(id: string): BackupManifest {\n if (!isValidBackupId(id)) {\n throw new UserError(`invalid backup id \"${id}\" — expected the format produced by \\`skill-forge refine promote\\` (e.g. 20260720T142305Z-a1b2)`);\n }\n\n const manifestPath = join(getBackupsDir(), id, 'manifest.json');\n if (!existsSync(manifestPath)) {\n throw new UserError(`no backup with id: ${id}`);\n }\n\n const parsed = readJsonFile(manifestPath, 'backup manifest');\n validateBackupManifestShape(parsed, manifestPath);\n return parsed;\n}\n","/*\n * Rhize Commercial License — NOT covered by the MIT license that applies to\n * the rest of this repository. Production use of this module's functionality\n * requires a valid license key. See LICENSE and LICENSE-COMMERCIAL.\n * Copyright (c) 2026 Rhize Media\n */\nimport { UserError } from '../errors.js';\nimport { isPatchAction, type PatchAction } from './models.js';\n\n/**\n * The `skill-forge refine` patch engine — parses and applies section-patch action blocks\n * against a `SKILL.md`'s section tree. See `.claude/plans/refinement-merge.md` (Amendment 4)\n * for the selector rules this implements.\n *\n * Pure functions only: every export here takes strings/structures in and\n * returns strings/structures out. NO filesystem access, NO subprocesses —\n * the command layer (`src/commands/refine.ts`, not yet built) owns all I/O.\n * That keeps this module trivially unit-testable and keeps its security\n * review surface to \"does the string transformation do what it claims\",\n * with no execution or write-path risk to reason about here at all.\n *\n * ## Patch file format\n *\n * A `SKILL.patch.md` (or `SKILL.extend.md`) is a sequence of accumulated\n * action blocks:\n *\n * ```\n * <!-- ACTION: insert-after \"Only check paths\" -->\n * <!-- TARGET: Hooks > duplicate-check -->\n * # Exclude test directories\n * if echo \"$FILE_PATH\" | grep -qE \"(__tests__|fixtures)\"; then\n * exit 0\n * fi\n * ```\n *\n * `ACTION` is required and optionally carries a quoted marker argument\n * (required for `replace-section` / `insert-after` / `insert-before` /\n * `delete-section`, ignored for `append` / `prepend`). `TARGET` is optional\n * — a heading-path selector (`>`-separated, case-insensitive,\n * whitespace-normalized) identifying which section of the target `SKILL.md`\n * the block applies to; omitted means the whole document body. Any text\n * before the first `ACTION` comment (a `# SKILL.patch.md` / `# Patches\n * for: ...` preamble) is ignored. `ACTION`/`TARGET`\n * comments and `#`-heading lines inside fenced code blocks are never\n * treated as directives/headings — a patch block's own example content can\n * safely contain literal `<!-- ACTION -->` text or `#` lines inside a\n * fence without being misparsed.\n *\n * ## Selector semantics\n *\n * - Section targets are heading-path selectors resolved against a heading\n * tree built from the target `SKILL.md`'s markdown headings (`#`..`######`).\n * - `insert-after` / `insert-before` markers are substring matches (after\n * whitespace normalization) against any line within the resolved target\n * section's full body (including nested subsections).\n * - `replace-section` / `delete-section` markers are substring matches\n * against a *direct child heading* of the resolved target section — i.e.\n * they operate on a literal markdown subsection, not an arbitrary\n * in-content comment marker. This is a narrow, unambiguous definition.\n * - Every selector — section path or marker — fails loud (`UserError`\n * listing the candidates) on zero or more-than-one matches. Never fuzzy,\n * never \"use the first match\".\n *\n * YAML frontmatter (a leading `---` ... `---` block) is never scanned for\n * headings and is carried through every transformation byte-identical.\n */\n\nexport interface ParsedPatchBlock {\n action: PatchAction;\n /** Marker argument from `<!-- ACTION: <action> \"<marker>\" -->`, when present. */\n marker?: string;\n /** Heading-path selector from `<!-- TARGET: ... -->`, when present. Omitted = whole document body. */\n target?: string;\n /** Block body, trailing/leading blank lines trimmed. */\n content: string;\n}\n\nconst FENCE_RE = /^\\s*(```|~~~)/;\nconst HEADING_RE = /^(#{1,6})\\s+(.*)$/;\nconst ACTION_RE = /^<!--\\s*ACTION:\\s*([a-zA-Z-]+)(?:\\s+\"([^\"]*)\")?\\s*-->\\s*$/;\nconst TARGET_RE = /^<!--\\s*TARGET:\\s*(.+?)\\s*-->\\s*$/;\n\nconst ACTIONS_REQUIRING_MARKER: ReadonlySet<string> = new Set(['replace-section', 'insert-after', 'insert-before', 'delete-section']);\n\n/**\n * Parses a `SKILL.patch.md`/`SKILL.extend.md` source into its accumulated\n * action blocks, in file order. Throws `UserError` on an unrecognized\n * action, a missing required marker, or if the file contains no ACTION\n * blocks at all.\n */\nexport function parsePatchFile(source: string): ParsedPatchBlock[] {\n const lines = source.split('\\n');\n const blocks: ParsedPatchBlock[] = [];\n let inFence = false;\n let current: { action: PatchAction; marker?: string; target?: string; contentLines: string[] } | null = null;\n\n const finalizeCurrent = (): void => {\n if (!current) return;\n const contentLines = current.contentLines;\n while (contentLines.length > 0 && contentLines[0].trim() === '') contentLines.shift();\n while (contentLines.length > 0 && contentLines[contentLines.length - 1].trim() === '') contentLines.pop();\n blocks.push({\n action: current.action,\n marker: current.marker,\n target: current.target,\n content: contentLines.join('\\n'),\n });\n current = null;\n };\n\n let i = 0;\n while (i < lines.length) {\n const line = lines[i];\n\n if (FENCE_RE.test(line)) {\n inFence = !inFence;\n if (current) current.contentLines.push(line);\n i++;\n continue;\n }\n\n if (!inFence) {\n const trimmed = line.trim();\n const actionMatch = ACTION_RE.exec(trimmed);\n if (actionMatch) {\n finalizeCurrent();\n const action = actionMatch[1];\n if (!isPatchAction(action)) {\n throw new UserError(`Unknown patch action \"${action}\" (valid: append, prepend, replace-section, insert-after, insert-before, delete-section)`);\n }\n const marker = actionMatch[2];\n if (ACTIONS_REQUIRING_MARKER.has(action) && (!marker || marker.trim().length === 0)) {\n throw new UserError(`Action \"${action}\" requires a quoted marker argument, e.g. <!-- ACTION: ${action} \"marker text\" -->`);\n }\n current = { action, marker: marker || undefined, contentLines: [] };\n i++;\n\n // Optional TARGET metadata line, allowed after any leading blank lines.\n let peek = i;\n while (peek < lines.length && lines[peek].trim() === '') peek++;\n if (peek < lines.length) {\n const targetMatch = TARGET_RE.exec(lines[peek].trim());\n if (targetMatch) {\n current.target = targetMatch[1];\n i = peek + 1;\n }\n }\n continue;\n }\n }\n\n if (current) current.contentLines.push(line);\n i++;\n }\n finalizeCurrent();\n\n if (blocks.length === 0) {\n throw new UserError('No ACTION blocks found in patch source');\n }\n return blocks;\n}\n\ninterface HeadingNode {\n level: number;\n heading: string;\n /** Absolute line index of the heading line itself (root uses bodyOffset - 1, never dereferenced). */\n headingLine: number;\n /** Absolute line index (inclusive) where this section's body starts. */\n bodyStart: number;\n /** Absolute line index (exclusive) where this section's body ends. */\n bodyEnd: number;\n children: HeadingNode[];\n}\n\nfunction frontmatterOffset(lines: readonly string[]): number {\n if (lines[0] !== '---') return 0;\n for (let i = 1; i < lines.length; i++) {\n if (lines[i] === '---') return i + 1;\n }\n return 0; // unterminated frontmatter fence: treat as no frontmatter\n}\n\nfunction buildHeadingTree(lines: readonly string[], bodyOffset: number): HeadingNode {\n const flat: Array<{ level: number; text: string; line: number }> = [];\n let inFence = false;\n for (let i = bodyOffset; i < lines.length; i++) {\n const line = lines[i];\n if (FENCE_RE.test(line)) {\n inFence = !inFence;\n continue;\n }\n if (inFence) continue;\n const m = HEADING_RE.exec(line);\n if (m) flat.push({ level: m[1].length, text: m[2].trim(), line: i });\n }\n\n const root: HeadingNode = { level: 0, heading: '', headingLine: bodyOffset - 1, bodyStart: bodyOffset, bodyEnd: lines.length, children: [] };\n const stack: HeadingNode[] = [root];\n for (const h of flat) {\n while (stack.length > 1 && stack[stack.length - 1].level >= h.level) {\n const done = stack.pop();\n if (done) done.bodyEnd = h.line;\n }\n const node: HeadingNode = { level: h.level, heading: h.text, headingLine: h.line, bodyStart: h.line + 1, bodyEnd: lines.length, children: [] };\n stack[stack.length - 1].children.push(node);\n stack.push(node);\n }\n return root;\n}\n\nfunction normalizeText(s: string): string {\n return s.trim().replace(/\\s+/g, ' ');\n}\n\nfunction splitHeadingPath(target: string): string[] {\n return target\n .split('>')\n .map((segment) => segment.trim())\n .filter((segment) => segment.length > 0);\n}\n\nfunction collectDescendants(node: HeadingNode, acc: HeadingNode[]): void {\n acc.push(node);\n for (const child of node.children) collectDescendants(child, acc);\n}\n\n/**\n * Resolves a `>`-separated heading-path selector against a heading tree.\n * The first segment matches a heading anywhere in the document (so a\n * top-level `## Triggers` selector works whether or not the document has a\n * wrapping `# Title`); every subsequent segment matches a *direct child* of\n * the previous match, i.e. `>` means \"nested under\", not \"anywhere below\".\n * Fails loud on 0 or >1 matches.\n */\nfunction resolveHeadingPath(root: HeadingNode, segments: readonly string[]): HeadingNode {\n if (segments.length === 0) return root;\n\n const allNodes: HeadingNode[] = [];\n collectDescendants(root, allNodes);\n const headingNodes = allNodes.filter((n) => n.level > 0);\n\n const firstNorm = normalizeText(segments[0]);\n let candidates = headingNodes.filter((n) => normalizeText(n.heading) === firstNorm);\n if (candidates.length === 0) {\n const available = headingNodes.map((n) => n.heading);\n throw new UserError(`No section matching \"${segments[0]}\" found (available headings: ${available.length > 0 ? available.join(', ') : 'none'})`);\n }\n\n for (let i = 1; i < segments.length; i++) {\n const segment = segments[i];\n const norm = normalizeText(segment);\n const next: HeadingNode[] = [];\n for (const candidate of candidates) {\n for (const child of candidate.children) {\n if (normalizeText(child.heading) === norm) next.push(child);\n }\n }\n if (next.length === 0) {\n const available = [...new Set(candidates.flatMap((c) => c.children.map((child) => child.heading)))];\n throw new UserError(`No section matching \"${segment}\" found (available headings: ${available.length > 0 ? available.join(', ') : 'none'})`);\n }\n candidates = next;\n }\n\n if (candidates.length > 1) {\n throw new UserError(`Target selector \"${segments.join(' > ')}\" matched ${candidates.length} sections; be more specific`);\n }\n return candidates[0];\n}\n\nfunction findMarkerLine(lines: readonly string[], start: number, end: number, marker: string): number {\n const norm = normalizeText(marker);\n const matches: number[] = [];\n for (let i = start; i < end; i++) {\n if (normalizeText(lines[i]).includes(norm)) matches.push(i);\n }\n if (matches.length === 0) {\n throw new UserError(`No line matching marker \"${marker}\" found in target section`);\n }\n if (matches.length > 1) {\n const preview = matches.map((i) => `line ${i + 1}: \"${lines[i].trim()}\"`).join('; ');\n throw new UserError(`Marker \"${marker}\" matched ${matches.length} lines, expected exactly 1 (${preview})`);\n }\n return matches[0];\n}\n\nfunction findChildByMarker(target: HeadingNode, marker: string): HeadingNode {\n const norm = normalizeText(marker);\n const matches = target.children.filter((c) => normalizeText(c.heading).includes(norm));\n if (matches.length === 0) {\n const available = target.children.map((c) => c.heading).join(', ') || 'none';\n throw new UserError(`No subsection matching \"${marker}\" found under target section (available: ${available})`);\n }\n if (matches.length > 1) {\n throw new UserError(`Marker \"${marker}\" matched ${matches.length} subsections: ${matches.map((c) => c.heading).join(', ')}`);\n }\n return matches[0];\n}\n\nfunction requireMarker(block: ParsedPatchBlock): string {\n if (!block.marker || block.marker.trim().length === 0) {\n throw new UserError(`Action \"${block.action}\" requires a marker`);\n }\n return block.marker;\n}\n\nfunction applyOneAction(lines: string[], target: HeadingNode, block: ParsedPatchBlock): void {\n const contentLines = block.content.length === 0 ? [''] : block.content.split('\\n');\n\n switch (block.action) {\n case 'append': {\n lines.splice(target.bodyEnd, 0, ...contentLines);\n return;\n }\n case 'prepend': {\n lines.splice(target.bodyStart, 0, ...contentLines);\n return;\n }\n case 'insert-after':\n case 'insert-before': {\n const idx = findMarkerLine(lines, target.bodyStart, target.bodyEnd, requireMarker(block));\n lines.splice(block.action === 'insert-after' ? idx + 1 : idx, 0, ...contentLines);\n return;\n }\n case 'replace-section':\n case 'delete-section': {\n const child = findChildByMarker(target, requireMarker(block));\n const replacement = block.action === 'replace-section' ? contentLines : [];\n lines.splice(child.headingLine, child.bodyEnd - child.headingLine, ...replacement);\n return;\n }\n default: {\n const exhaustive: never = block.action;\n throw new UserError(`Unknown patch action \"${exhaustive as string}\"`);\n }\n }\n}\n\n/**\n * Applies accumulated patch blocks (parsed via {@link parsePatchFile}) to a\n * `SKILL.md` document's text, in block order. The heading tree is rebuilt\n * before each block so line-number shifts from earlier blocks never go\n * stale. YAML frontmatter is never touched. Trailing newline structure is\n * preserved automatically (round-trips through `split('\\n')`/`join('\\n')`).\n */\nexport function applyPatchBlocks(skillMd: string, blocks: readonly ParsedPatchBlock[]): string {\n const lines = skillMd.split('\\n');\n for (const block of blocks) {\n const bodyOffset = frontmatterOffset(lines);\n const root = buildHeadingTree(lines, bodyOffset);\n const segments = block.target ? splitHeadingPath(block.target) : [];\n const target = resolveHeadingPath(root, segments);\n applyOneAction(lines, target, block);\n }\n return lines.join('\\n');\n}\n\nexport interface RenderMeta {\n skillName: string;\n refinementId: string;\n /** ISO timestamp, caller-supplied — keeps rendering deterministic/testable (no internal `Date.now()`). */\n generatedAt: string;\n}\n\n/** Renders a `SKILL.extend.md` additive-extension file (always a single `append` block). */\nexport function renderExtendFile(meta: RenderMeta & { targetSection: string; content: string }): string {\n return [\n '# SKILL.extend.md',\n `# Extensions for: ${meta.skillName}`,\n `# Generated: ${meta.generatedAt}`,\n `# Refinement: ${meta.refinementId}`,\n '',\n `## EXTEND: ${meta.targetSection}`,\n '<!-- ACTION: append -->',\n meta.content,\n '',\n ].join('\\n');\n}\n\n/** Renders a `skill-config.json` config-override artifact. */\nexport function renderConfigOverride(meta: RenderMeta & { config: Record<string, unknown> }): string {\n const payload = {\n skill: meta.skillName,\n version: '1.0.0',\n generated: meta.generatedAt,\n config: meta.config,\n };\n return `${JSON.stringify(payload, null, 2)}\\n`;\n}\n\n/**\n * `full` / `hook` / `script` override types are whole-file overrides:\n * verbatim content, no transformation at all. This function exists so\n * call sites have one uniform rendering entry point per override type\n * (mirrors `renderExtendFile`/`renderConfigOverride`) rather than special-\n * casing \"just use the content\" three different ways.\n */\nexport function renderOverridePassthrough(content: string): string {\n return content;\n}\n\ntype DiffOpType = 'context' | 'add' | 'remove';\ninterface DiffOp {\n type: DiffOpType;\n line: string;\n}\n\n/** Line-level LCS diff. O(n*m) time/space — fine for skill-sized documents, not intended for huge files. */\nfunction computeLcsDiff(a: readonly string[], b: readonly string[]): DiffOp[] {\n const n = a.length;\n const m = b.length;\n const dp: number[][] = Array.from({ length: n + 1 }, () => new Array<number>(m + 1).fill(0));\n for (let i = n - 1; i >= 0; i--) {\n for (let j = m - 1; j >= 0; j--) {\n dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);\n }\n }\n\n const ops: DiffOp[] = [];\n let i = 0;\n let j = 0;\n while (i < n && j < m) {\n if (a[i] === b[j]) {\n ops.push({ type: 'context', line: a[i] });\n i++;\n j++;\n } else if (dp[i + 1][j] >= dp[i][j + 1]) {\n ops.push({ type: 'remove', line: a[i] });\n i++;\n } else {\n ops.push({ type: 'add', line: b[j] });\n j++;\n }\n }\n while (i < n) {\n ops.push({ type: 'remove', line: a[i] });\n i++;\n }\n while (j < m) {\n ops.push({ type: 'add', line: b[j] });\n j++;\n }\n return ops;\n}\n\n/**\n * A simple, deterministic unified-diff-style preview of `before` -> `after`\n * for `path`, stdlib-only. No hunk headers/context windowing — every line\n * is shown, prefixed ` `/`+`/`-`. Good enough for a human confirmation\n * prompt before a write; not a drop-in replacement for `diff -u`.\n */\nexport function previewDiff(before: string, after: string, path: string): string {\n const a = before.split('\\n');\n const b = after.split('\\n');\n const ops = computeLcsDiff(a, b);\n const lines = [`--- ${path}`, `+++ ${path}`];\n for (const op of ops) {\n const prefix = op.type === 'add' ? '+' : op.type === 'remove' ? '-' : ' ';\n lines.push(`${prefix}${op.line}`);\n }\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["join","readFileSync","join","readFileSync","existsSync","mkdirSync","statSync","writeFileSync","homedir","join","homedir","join","statSync","mkdirSync","writeFileSync","existsSync","randomUUID","existsSync","mkdirSync","readdirSync","basename","join","SKIP_DIRS","randomUUID","join","mkdirSync","existsSync","basename","readdirSync","walk","reject","cpSync","existsSync","mkdirSync","renameSync","rmSync","basename","join","readFileSync","readdirSync","join","SKIP_DIRS","walk","join","basename","existsSync","mkdirSync","rmSync","renameSync","cpSync","existsSync","mkdirSync","readFileSync","writeFileSync","dirname","existsSync","readFileSync","writeFileSync","mkdirSync","dirname","readFileSync","dirname","join","pkg","existsSync","readFileSync","statSync","writeFileSync","join","readdirSync","existsSync","readFileSync","basename","dirname","join","existsSync","lstatSync","readFileSync","readdirSync","realpathSync","renameSync","rmSync","statSync","writeFileSync","basename","delimiter","extname","join","resolvePath","statSync","delimiter","join","statSync","delimiter","join","statSync","basename","extname","basename","extname","statSync","existsSync","readFileSync","readdirSync","basename","dirname","join","resolve","sep","readFileSync","basename","dirname","existsSync","readdirSync","join","resolve","createHash","existsSync","readFileSync","join","createHash","join","existsSync","readFileSync","status","resolvePath","statSync","readdirSync","join","existsSync","realpathSync","readFileSync","basename","extname","find","delimiter","which","lstatSync","writeFileSync","renameSync","rmSync","join","finding","dirname","basename","existsSync","readFileSync","join","existsSync","mkdtempSync","readFileSync","readdirSync","rmSync","statSync","basename","dirname","extname","join","resolve","existsSync","readFileSync","extname","join","readFileSync","pkg","join","existsSync","extname","resolve","existsSync","statSync","join","MCP_SDK_DEP","MCP_KEYWORD","MCP_BIN_PREFIX","readFileSync","pkg","basename","extname","mkdtempSync","dirname","rmSync","readdirSync","m","existsSync","readFileSync","resolve","resolve","existsSync","readFileSync","round3","score","readFileSync","resolve","resolve","readFileSync","STOPWORDS","tokenize","intersectSize","unionSize","round3","score","finding","readdirSync","join","randomUUID","existsSync","mkdirSync","readFileSync","renameSync","rmSync","writeFileSync","join","join","existsSync","readFileSync","queue","mkdirSync","randomUUID","writeFileSync","renameSync","rmSync","existsSync","dirname","join","spawn","existsSync","homedir","createInterface","statSync","homedir","join","resolve","statSync","resolve","join","homedir","existsSync","join","resolvePath","createInterface","resolvePath","join","existsSync","createInterface","homedir","createInterface","existsSync","join","join","join","existsSync","existsSync","realpathSync","basename","join","resolvePath","sep","PRO_FEATURE_LABEL","basename","join","existsSync","realpathSync","resolvePath","sep","existsSync","createInterface","createInterface","createInterface","createHash","cpSync","existsSync","lstatSync","mkdtempSync","readFileSync","rmSync","tmpdir","basename","dirname","join","resolvePath","sep","PRO_FEATURE_LABEL","resolvePath","join","existsSync","sep","mkdtempSync","tmpdir","cpSync","rmSync","dirname","basename","createInterface","readFileSync","lstatSync","createHash","existsSync","existsSync","list","finding","resolvePath","existsSync","readFileSync","readdirSync","realpathSync","statSync","basename","join","resolvePath","PRO_FEATURE_LABEL","listSubdirs","readdirSync","join","statSync","findSkillDirsUnderRoot","existsSync","resolvePath","readFileSync","realpathSync","basename","resolvePath","search","data","status","realpathSync","join","resolvePath","createHash","readFileSync","existsSync","dirname","isAbsolute","resolve","createHash","status","isAbsolute","resolve","existsSync","readFileSync","dirname","PRO_FEATURE_LABEL","resolvePath","realpathSync","join","status","existsSync","rmSync","statSync","join","PRO_FEATURE_LABEL","audit","resolveReportPath","join","statSync","rmSync","existsSync","finding","quarantine","realpathSync","dirname","isAbsolute","relative","resolvePath","PRO_FEATURE_LABEL","status","realpathSync","relative","isAbsolute","resolvePath","dirname","queue","createHash","randomUUID","existsSync","lstatSync","mkdirSync","readFileSync","realpathSync","renameSync","rmSync","writeFileSync","dirname","isAbsolute","join","relative","resolvePath","sep","createInterface","existsSync","realpathSync","statSync","isAbsolute","join","relative","resolvePath","tokenize","unionSize","createHash","existsSync","readFileSync","statSync","realpathSync","join","resolvePath","join","readFileSync","existsSync","resolvePath","realpathSync","existsSync","readFileSync","statSync","sha256","createHash","join","resolveConfiguredRoot","resolvePath","realpathOrNull","realpathSync","isUnderRoot","relative","isAbsolute","join","existsSync","isFile","statSync","i","j","PRO_FEATURE_LABEL","requireProFeature","status","createInterface","resolveConfiguredRoot","resolvePath","isUnderRoot","relative","isAbsolute","readFileSync","lstatSync","mkdirSync","realpathSync","join","dirname","writeFileSync","existsSync","randomUUID","renameSync","rmSync","createHash","sep","require"]}
|