@noir-ai/cli 1.11.0 → 1.11.1-beta.1

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.
Files changed (41) hide show
  1. package/README.md +4 -3
  2. package/dist/bin.js +19 -16
  3. package/dist/bin.js.map +1 -1
  4. package/dist/{chunk-CN3KUSQT.js → chunk-CT6TYLHN.js} +4 -3
  5. package/dist/chunk-CT6TYLHN.js.map +1 -0
  6. package/dist/{chunk-S2PDLS4C.js → chunk-I2R55ACD.js} +4 -2
  7. package/dist/{chunk-S2PDLS4C.js.map → chunk-I2R55ACD.js.map} +1 -1
  8. package/dist/{chunk-Y4M2YC6U.js → chunk-QIVHIKYD.js} +3 -3
  9. package/dist/{chunk-US567S5T.js → chunk-RON2XESF.js} +2 -2
  10. package/dist/{create-DJ6B5K36.js → create-VK2QMXYS.js} +3 -3
  11. package/dist/index.js +2 -2
  12. package/dist/{install-X7QSYLLS.js → install-4U4UDDNE.js} +3 -3
  13. package/dist/{sync-FKWXQOWT.js → sync-VXZKODQA.js} +3 -3
  14. package/dist/tui/{chunk-2KJ27HGA.js → chunk-F6XRS72N.js} +4 -3
  15. package/dist/tui/chunk-F6XRS72N.js.map +1 -0
  16. package/dist/tui/{chunk-4OBUMHF6.js → chunk-NLK6UNIA.js} +3 -3
  17. package/dist/tui/{chunk-R6246ISQ.js → chunk-QF6777NJ.js} +2 -2
  18. package/dist/tui/{chunk-MD6WXZBF.js → chunk-R5YX44UL.js} +4 -2
  19. package/dist/tui/{chunk-MD6WXZBF.js.map → chunk-R5YX44UL.js.map} +1 -1
  20. package/dist/tui/{create-CFAXIIYW.js → create-WKFVM237.js} +3 -3
  21. package/dist/tui/index.js +185 -153
  22. package/dist/tui/index.js.map +1 -1
  23. package/dist/tui/{install-HZKZXBQD.js → install-JR6ZXNYA.js} +3 -3
  24. package/dist/tui/{sync-Y7MTQ4JC.js → sync-DUWJAF7Z.js} +3 -3
  25. package/dist/tui/{update-OM2EP2F5.js → update-PGAM6OVL.js} +4 -4
  26. package/dist/{update-S3IZXXKO.js → update-ZDAVLOXB.js} +4 -4
  27. package/package.json +12 -12
  28. package/dist/chunk-CN3KUSQT.js.map +0 -1
  29. package/dist/tui/chunk-2KJ27HGA.js.map +0 -1
  30. /package/dist/{chunk-Y4M2YC6U.js.map → chunk-QIVHIKYD.js.map} +0 -0
  31. /package/dist/{chunk-US567S5T.js.map → chunk-RON2XESF.js.map} +0 -0
  32. /package/dist/{create-DJ6B5K36.js.map → create-VK2QMXYS.js.map} +0 -0
  33. /package/dist/{install-X7QSYLLS.js.map → install-4U4UDDNE.js.map} +0 -0
  34. /package/dist/{sync-FKWXQOWT.js.map → sync-VXZKODQA.js.map} +0 -0
  35. /package/dist/tui/{chunk-4OBUMHF6.js.map → chunk-NLK6UNIA.js.map} +0 -0
  36. /package/dist/tui/{chunk-R6246ISQ.js.map → chunk-QF6777NJ.js.map} +0 -0
  37. /package/dist/tui/{create-CFAXIIYW.js.map → create-WKFVM237.js.map} +0 -0
  38. /package/dist/tui/{install-HZKZXBQD.js.map → install-JR6ZXNYA.js.map} +0 -0
  39. /package/dist/tui/{sync-Y7MTQ4JC.js.map → sync-DUWJAF7Z.js.map} +0 -0
  40. /package/dist/tui/{update-OM2EP2F5.js.map → update-PGAM6OVL.js.map} +0 -0
  41. /package/dist/{update-S3IZXXKO.js.map → update-ZDAVLOXB.js.map} +0 -0
@@ -11,7 +11,7 @@ import {
11
11
  success,
12
12
  table,
13
13
  warn
14
- } from "./chunk-S2PDLS4C.js";
14
+ } from "./chunk-I2R55ACD.js";
15
15
 
16
16
  // src/commands/doctor.ts
17
17
  import { spawnSync } from "child_process";
@@ -713,7 +713,8 @@ async function clackConflictResolver(ctx) {
713
713
  if (clack.isCancel(choice)) return "cancel";
714
714
  const resolution = choice;
715
715
  const mode = ctx.mode ?? "regenerate";
716
- if (mode === "regenerate") {
716
+ const supportsApplyToAll = mode !== "managedBlock" && mode !== "managedBlocks";
717
+ if (supportsApplyToAll) {
717
718
  const remember = await clack.select({
718
719
  message: `Apply "${labelFor(resolution)}" to all remaining conflicts this run?`,
719
720
  initialValue: "no",
@@ -1101,4 +1102,4 @@ export {
1101
1102
  reportPlannedWrites,
1102
1103
  assertTransportUrl
1103
1104
  };
1104
- //# sourceMappingURL=chunk-CN3KUSQT.js.map
1105
+ //# sourceMappingURL=chunk-CT6TYLHN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/doctor.ts","../src/conflict.ts","../src/dedup-write.ts","../src/init.ts"],"sourcesContent":["// S9 — `noir doctor`.\n//\n// Environment + project health. Runs a fixed set of checks and renders a\n// results table (human, stderr) or the versioned `{ok,data}` envelope (--json,\n// stdout). Exit 1 if any CRITICAL (fail) check is unhealthy, else 0.\n//\n// Severity model (S9 spec F8):\n// - ok — healthy.\n// - warn — degraded but usable (daemon down, onnx missing, provider key\n// missing, project not initialized). NEVER triggers exit 1.\n// - fail — CRITICAL: the product is broken on this host. Only `config` (parse\n// error), `native deps` (better-sqlite3 / sqlite-vec will not load),\n// and `store` (open fails) can fail. Exit 1 if any fail.\n//\n// Honesty rules: provider status uses `resolveModelConfig` — a PURE projection\n// of the user's config + env-var NAME presence; it makes NO live call (blueprint\n// D5). Native-dep probes are best-effort try/catch (the store's native\n// binaries are probed via `vecAvailability`, the single cross-package surface;\n// onnxruntime-node is the context engine's own dep and is probed separately).\n//\n// Cold-start (NF6): the @noir-ai/store import (which loads better-sqlite3) is\n// DYNAMIC, inside the action, so merely running `noir status` / `noir --help`\n// never pays the native-bindings cost. @noir-ai/model is imported eagerly — its\n// provider self-registration is light (SDKs load lazily inside `complete()`).\n//\n// Stream discipline (S9): `--json` writes `{ok:true, data:{checks,summary}}`\n// to stdout (the only stdout write). Doctor always emits `ok:true` because the\n// COMMAND succeeded in producing a diagnosis; the system-health pass/fail is\n// signaled by the EXIT CODE (0 healthy / 1 critical failure) — the same shape\n// `npm audit --json` uses (valid JSON out, non-zero exit when issues found).\n\nimport { spawnSync } from 'node:child_process';\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { dirname, join, relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { type HostId, resolveAdapter } from '@noir-ai/adapters';\nimport {\n detectActiveMethod,\n type InstallMethod,\n latestVersionFromCache,\n loadProjectInfo,\n NOIR_VERSION,\n type ProjectInfo,\n paths,\n readInstallRecord,\n readUpdateCache,\n} from '@noir-ai/core';\nimport { CURRENT_SCAFFOLD_VERSION, readScaffoldVersion } from '@noir-ai/create';\nimport { pidAlive, readDaemonRecord } from '@noir-ai/daemon';\nimport { resolveModelConfig } from '@noir-ai/model';\nimport {\n type CliOptions,\n EXIT,\n error as err,\n log,\n NoirCliError,\n success,\n table,\n warn,\n} from '../output.js';\nimport { type BadgeState, badge } from '../theme.js';\n\n/** Options accepted by `doctor`: the global flags + the opt-in `--dedup`. */\nexport interface DoctorOptions extends CliOptions {\n /** `--dedup`: scan host-context + `.noir/` docs for semantic near-duplicates.\n * Opt-in (loads the local embedder) so a default `noir doctor` stays fast. */\n dedup?: boolean;\n}\n\ntype Severity = 'ok' | 'warn' | 'fail';\n\n/** One row of the doctor report (table cell + `--json` element). */\nexport interface CheckResult {\n name: string;\n status: Severity;\n detail: string;\n}\n\n/** The `data` payload of `noir doctor --json`. */\nexport interface DoctorPayload {\n noir: string;\n checks: CheckResult[];\n summary: { ok: number; warn: number; fail: number };\n /** Scaffold-version drift (slice S). `onDisk` is null when the stamp is\n * absent (uninitialized or pre-Slice-S project); `drift` is true only when\n * a stamp is present AND differs from the engine's current version. */\n scaffold: { onDisk: string | null; current: string; drift: boolean };\n /** RULES.md budget measurement. `null` when the project isn't\n * initialized OR `.noir/rules/RULES.md` is absent — in either case there\n * is nothing to measure and the check row stays informational (warn skip\n * or ok \"no RULES.md\"). Never drives `fail` — over-budget is a `warn`. */\n rules: {\n onDisk: { bytes: number; lines: number };\n budget: { kb: number; maxLines: number };\n over: boolean;\n } | null;\n /** S10 host report. `null` when the project isn't initialized (no resolved\n * host); otherwise carries the active `host` + the list of repo-relative\n * primary artifact paths the doctor verified (AGENTS.md for agents-md/\n * cursor/opencode; the host's native context file when distinct from\n * AGENTS.md (claude → CLAUDE.md, gemini → GEMINI.md); the host's MCP\n * config). The check row is `ok` when\n * all present, `warn` (NEVER `fail`) when any are missing — re-running\n * `noir sync` restores them. */\n host: { active: HostId; expected: string[]; missing: string[] } | null;\n /** S11 publish-readiness report (repo-developer-facing, advisory). `null`\n * when doctor is NOT running from a monorepo checkout (a global install has\n * no workspace package.json set to validate → the row reports `ok`\n * \"skipped\"). Otherwise carries the count of workspace `package.json` files\n * validated + the list of advisory issues found (bad semver, missing\n * `files`, cli missing `bin`, …). The row severity is `warn` when any issues\n * are present, `ok` otherwise — NEVER `fail` (a missing field does not break\n * the local install). */\n publish: { checked: number; issues: string[] } | null;\n /** C1 install-method report. `method` from ~/.noir/install.json (fallback\n * unknown); `latestKnown` from the update cache (never a live call). */\n install: {\n method: InstallMethod;\n installedVersion: string | null;\n latestKnown: string | null;\n } | null;\n}\n\n// ---------------------------------------------------------------------------\n// Small helpers\n// ---------------------------------------------------------------------------\n\n/** Best-effort probe of the `onnxruntime-node` native binding. */\nasync function probeOnnx(): Promise<{ ok: boolean; reason: string }> {\n try {\n // Variable specifier (not a string literal) so tsc does NOT try to resolve\n // a module the CLI package does not directly depend on — onnxruntime-node\n // is the context engine's dep and resolves through ITS node_modules at\n // runtime. Best-effort: hosts that hoist it resolve; otherwise not-loadable.\n const spec = 'onnxruntime-node';\n await import(spec);\n return { ok: true, reason: 'loadable' };\n } catch (e) {\n return { ok: false, reason: e instanceof Error ? e.message : String(e) };\n }\n}\n\n/** Human label for the onnx probe outcome (hides confusing module-not-found noise). */\nfunction describeOnnx(o: { ok: boolean; reason: string }): string {\n if (o.ok) return 'loadable';\n if (/Cannot find (module|package)/i.test(o.reason)) {\n return 'not resolvable from CLI probe (best-effort)';\n }\n const r = o.reason.length > 60 ? `${o.reason.slice(0, 59)}…` : o.reason;\n return `probe failed: ${r}`;\n}\n\nfunction fmtUptime(sec: number): string {\n if (!Number.isFinite(sec) || sec < 0) return 'unknown';\n if (sec < 60) return `${sec}s`;\n if (sec < 3600) return `${Math.floor(sec / 60)}m ${sec % 60}s`;\n return `${Math.floor(sec / 3600)}h ${Math.floor((sec % 3600) / 60)}m`;\n}\n\nfunction msg(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\n// ---------------------------------------------------------------------------\n// Checks. Each appends to `checks`; project-dependent ones short-circuit to a\n// `warn` \"skipped\" row when the project isn't initialized (so the table still\n// accounts for them, but a pre-init `noir doctor` never exit-1s on them).\n// ---------------------------------------------------------------------------\n\nasync function checkRuntime(checks: CheckResult[]): Promise<void> {\n checks.push({\n name: 'runtime',\n status: 'ok',\n detail: `noir ${NOIR_VERSION} · node ${process.version} · ${process.platform}`,\n });\n}\n\nfunction checkConfig(root: string): { project: ProjectInfo | undefined; result: CheckResult } {\n const configPath = paths.config(root);\n if (!existsSync(configPath)) {\n return {\n project: undefined,\n result: {\n name: 'config',\n status: 'warn',\n detail: `no .noir/config.yml in ${root} — run \\`noir init\\``,\n },\n };\n }\n try {\n const project = loadProjectInfo(root);\n return {\n project,\n result: {\n name: 'config',\n status: 'ok',\n detail: `host=${project.config.host} · mode=${project.config.mode}`,\n },\n };\n } catch (e) {\n return {\n project: undefined,\n result: { name: 'config', status: 'fail', detail: `config parse error: ${msg(e)}` },\n };\n }\n}\n\nasync function checkDaemon(checks: CheckResult[]): Promise<void> {\n const rec = readDaemonRecord();\n if (!rec) {\n checks.push({\n name: 'daemon',\n status: 'warn',\n detail: 'not running (start with `noir daemon start`)',\n });\n return;\n }\n if (!pidAlive(rec.pid)) {\n checks.push({\n name: 'daemon',\n status: 'warn',\n detail: `stale record (pid ${rec.pid} not alive)`,\n });\n return;\n }\n try {\n const res = await fetch(`http://127.0.0.1:${rec.port}/health`);\n const body =\n res.status === 200 ? ((await res.json()) as { ok?: boolean; uptimeSec?: number }) : null;\n if (body && body.ok === true) {\n checks.push({\n name: 'daemon',\n status: 'ok',\n detail: `running (pid ${rec.pid}, port ${rec.port}, up ${fmtUptime(body.uptimeSec ?? 0)})`,\n });\n return;\n }\n checks.push({\n name: 'daemon',\n status: 'warn',\n detail: `record present but /health unreachable on port ${rec.port}`,\n });\n } catch {\n checks.push({\n name: 'daemon',\n status: 'warn',\n detail: `record present but /health unreachable on port ${rec.port}`,\n });\n }\n}\n\nasync function checkNativeDeps(checks: CheckResult[]): Promise<{ vecOk: boolean }> {\n // vecAvailability probes better-sqlite3 + sqlite-vec together (the store opens\n // a Database and loads sqlite-vec). Dynamic import keeps the native bindings\n // out of cold-start for every other command.\n const { vecAvailability } = await import('@noir-ai/store');\n const vec = vecAvailability();\n const onnx = await probeOnnx();\n const vecOk = vec.ok === true;\n const onnxOk = onnx.ok === true;\n // The sqlite side is CRITICAL (the store cannot open without it). onnx only\n // backs local embeddings, so its absence is a warning (search → BM25-only).\n const status: Severity = vecOk ? (onnxOk ? 'ok' : 'warn') : 'fail';\n checks.push({\n name: 'native deps',\n status,\n detail: `better-sqlite3+sqlite-vec: ${vecOk ? 'loadable' : vec.ok === false ? vec.reason : 'unknown'} · onnxruntime-node: ${describeOnnx(onnx)}`,\n });\n return { vecOk };\n}\n\nasync function checkStore(\n checks: CheckResult[],\n project: ProjectInfo | undefined,\n root: string,\n): Promise<void> {\n if (!project) {\n checks.push({ name: 'store', status: 'warn', detail: 'skipped — not initialized' });\n return;\n }\n const dbPath = paths.storeDb(root, project.id);\n if (!existsSync(dbPath)) {\n // Fresh project: the store DB is created LAZILY when the daemon first runs\n // (or when content is indexed) — `noir init`/`create` does not pre-create it.\n // Its absence on an otherwise-initialized project is EXPECTED, not a failure:\n // surfacing it as `fail` would alarm every new user on their first `noir doctor`.\n // Warn (never exit-1) with the action that materializes the store.\n checks.push({\n name: 'store',\n status: 'warn',\n detail: `not created yet — the store DB is created when the daemon first runs (\\`noir daemon start\\` or index content); expected at ${dbPath}`,\n });\n return;\n }\n const { openStore } = await import('@noir-ai/store');\n let store: { close(): Promise<void> | void } | undefined;\n try {\n store = await openStore({ projectId: project.id, root, readonly: true });\n checks.push({\n name: 'store',\n status: 'ok',\n detail: `opens readonly (db: ${paths.storeDb(root, project.id)})`,\n });\n } catch (e) {\n checks.push({ name: 'store', status: 'fail', detail: `open failed: ${msg(e)}` });\n } finally {\n if (store) {\n try {\n await Promise.resolve(store.close());\n } catch {\n /* a close error must not mask the open result */\n }\n }\n }\n}\n\nfunction checkEmbedder(\n checks: CheckResult[],\n project: ProjectInfo | undefined,\n vecOk: boolean,\n): void {\n if (!project) {\n checks.push({ name: 'embedder', status: 'warn', detail: 'skipped — not initialized' });\n return;\n }\n const emb = project.config.context.embedder;\n const kind = emb.kind;\n const model = typeof emb.model === 'string' && emb.model.length > 0 ? emb.model : '<unset>';\n if (kind === 'none') {\n checks.push({\n name: 'embedder',\n status: 'ok',\n detail: `kind=none (BM25-only) · dim=${emb.dim}`,\n });\n return;\n }\n if (!vecOk) {\n // vec native layer is the embedder's hard prereq (vecs write into vec0).\n checks.push({\n name: 'embedder',\n status: 'warn',\n detail: `${kind}/${model} (${emb.dim}-dim) configured but vec native layer unavailable — search degrades to BM25`,\n });\n return;\n }\n checks.push({\n name: 'embedder',\n status: 'ok',\n detail: `${kind}/${model} (${emb.dim}-dim) · vec backend available`,\n });\n}\n\nfunction checkProvider(checks: CheckResult[], project: ProjectInfo | undefined): void {\n if (!project) {\n checks.push({ name: 'provider', status: 'warn', detail: 'skipped — not initialized' });\n return;\n }\n // PURE projection — resolveModelConfig reads config + env-var NAMES only; it\n // NEVER makes a live call and NEVER infers a provider from env-var presence.\n const resolved = resolveModelConfig(project.config.model);\n const names = Object.keys(resolved.providers);\n if (names.length === 0) {\n checks.push({\n name: 'provider',\n status: 'ok',\n detail: 'none configured (offline mode; drafting degrades to templates)',\n });\n return;\n }\n const parts: string[] = [];\n let missing = false;\n for (const name of names) {\n const p = resolved.providers[name];\n if (!p) continue;\n const key = p.hasKey ? 'key present' : p.apiKeyEnv ? `missing ${p.apiKeyEnv}` : 'anonymous';\n if (!p.hasKey && p.apiKeyEnv) missing = true;\n parts.push(`${name}/${p.model ?? '?'} (${key})`);\n }\n // Provider readiness is never CRITICAL — the model layer degrades to `null`\n // (templates) when a key is missing, so this is an ok/warn signal only.\n checks.push({\n name: 'provider',\n status: missing ? 'warn' : 'ok',\n detail: parts.join(' · '),\n });\n}\n\n// ---------------------------------------------------------------------------\n// C1 — doctor install row (advisory; ok/warn only, never fail, cache-only).\n// ---------------------------------------------------------------------------\n\nexport interface InstallCheckOutcome {\n name: 'install';\n status: 'ok' | 'warn';\n detail: string;\n method: InstallMethod;\n installedVersion: string | null;\n latestKnown: string | null;\n}\n\n/** Pure: build the doctor `install` row. NEVER `fail` — an install method issue\n * is advisory, not a broken host. Reads the cache only; no network. */\nexport function buildInstallCheck(opts: {\n method: InstallMethod;\n version: string | null;\n latestKnown: string | null;\n}): InstallCheckOutcome {\n const { method, version, latestKnown } = opts;\n const parts: string[] = [`method=${method}`];\n if (version) parts.push(`v${version}`);\n const advisory: string[] = [];\n if (method !== 'native') advisory.push('native recommended');\n if (latestKnown && version && latestKnown !== version) advisory.push('update available');\n const status: 'ok' | 'warn' = advisory.length > 0 ? 'warn' : 'ok';\n if (advisory.length > 0) parts.push(advisory.join(' + '));\n return {\n name: 'install',\n status,\n detail: parts.join(' · '),\n method,\n installedVersion: version,\n latestKnown,\n };\n}\n\nfunction checkScaffoldVersion(\n checks: CheckResult[],\n root: string,\n): {\n onDisk: string | null;\n current: string;\n drift: boolean;\n} {\n // readScaffoldVersion never throws — it returns null on an absent/malformed\n // stamp, so doctor keeps reporting even on a partially-initialized project.\n const onDisk = readScaffoldVersion(root);\n const current = CURRENT_SCAFFOLD_VERSION;\n const drift = onDisk !== null && onDisk !== current;\n // Drift is NEVER critical: a stale scaffold still works (the manifest entries\n // are forward-compatible), it just means `noir init --upgrade` will run\n // pending migrations. Absent stamp on an uninitialized project → warn (parity\n // with the other project-dependent checks that skip-with-warn pre-init).\n const status: Severity = onDisk === null || drift ? 'warn' : 'ok';\n const detail =\n onDisk === null\n ? 'no .noir/scaffold-version stamp (run `noir init`)'\n : drift\n ? `on-disk ${onDisk} ≠ current ${current} (run \\`noir init --upgrade\\`)`\n : `${onDisk} (up to date)`;\n checks.push({ name: 'scaffold version', status, detail });\n return { onDisk, current, drift };\n}\n\n/**\n * Soft RULES.md budget cap. Mirrors the Failure-Backed Rules doctrine:\n * an over-budget RULES.md tends to accumulate stale / speculative clauses, so\n * doctor warns (NEVER fails — purely a hygiene nudge) when the file exceeds\n * the configured `lengthBudgetKb` (default 6 KB) OR a 150-line ceiling. The\n * check is honest about the three \"nothing to measure\" cases:\n *\n * • project not initialized → skip-warn (parity with store/embedder/provider)\n * • RULES.md absent → ok informational (no file = no budget problem)\n * • RULES.md unreadable → warn (treat like a malformed-stamp: surface it)\n *\n * Returns the structured measurement for the `--json` envelope's `data.rules`,\n * or `null` when there is nothing to measure (absent / skipped).\n */\nfunction checkRulesMdBudget(\n checks: CheckResult[],\n root: string,\n project: ProjectInfo | undefined,\n): {\n onDisk: { bytes: number; lines: number };\n budget: { kb: number; maxLines: number };\n over: boolean;\n} | null {\n if (!project) {\n checks.push({ name: 'rules budget', status: 'warn', detail: 'skipped — not initialized' });\n return null;\n }\n const rulesPath = paths.rulesMd(root);\n if (!existsSync(rulesPath)) {\n // No RULES.md is fine — most projects don't have one yet. Informational ok\n // so a green run stays green without forcing a file creation.\n checks.push({\n name: 'rules budget',\n status: 'ok',\n detail: 'no .noir/rules/RULES.md',\n });\n return null;\n }\n // Read + measure. A read/decode failure surfaces as a warn (parallel to the\n // malformed-stamp handling in checkScaffoldVersion): the doctor keeps\n // reporting rather than crashing, and the user sees a clear hint.\n let content: string;\n try {\n content = readFileSync(rulesPath, 'utf8');\n } catch (e) {\n checks.push({\n name: 'rules budget',\n status: 'warn',\n detail: `RULES.md unreadable: ${msg(e)}`,\n });\n return null;\n }\n // bytes = UTF-8 file size on disk (the natural unit for a KB budget). lines\n // uses wc-style counting: count of '\\n' chars, +1 if the last line has no\n // trailing newline; 0 only for an empty file.\n const bytes = Buffer.byteLength(content, 'utf8');\n const lineCount =\n content.length === 0 ? 0 : content.split('\\n').length - (content.endsWith('\\n') ? 1 : 0);\n const lengthBudgetKb = project.config.rules.lengthBudgetKb;\n const budgetBytes = lengthBudgetKb * 1024;\n const maxLines = 150;\n const overBytes = bytes > budgetBytes;\n const overLines = lineCount > maxLines;\n const over = overBytes || overLines;\n const status: Severity = over ? 'warn' : 'ok';\n const kb = (n: number): string => (n / 1024).toFixed(1);\n const detail = over\n ? `OVER: ${kb(bytes)} KB / ${lengthBudgetKb} KB · ${lineCount}/${maxLines} lines — trim RULES.md (failure-backed clauses only) or raise rules.lengthBudgetKb`\n : `${kb(bytes)} KB / ${lengthBudgetKb} KB · ${lineCount}/${maxLines} lines (within budget)`;\n checks.push({ name: 'rules budget', status, detail });\n return {\n onDisk: { bytes, lines: lineCount },\n budget: { kb: lengthBudgetKb, maxLines },\n over,\n };\n}\n\nfunction summarize(checks: CheckResult[]): DoctorPayload['summary'] {\n let ok = 0;\n let warnN = 0;\n let fail = 0;\n for (const c of checks) {\n if (c.status === 'ok') ok++;\n else if (c.status === 'warn') warnN++;\n else fail++;\n }\n return { ok, warn: warnN, fail };\n}\n\n/**\n * S10 — host-artifacts presence check. Reports the ACTIVE host (read from\n * `project.config.host`) + verifies the host's primary emission paths exist on\n * disk. The expected set mirrors `buildHostArtifacts` in @noir-ai/create:\n *\n * - AGENTS.md for the hosts that emit it (agents-md/cursor/opencode — their\n * native context surface IS AGENTS.md). claude/gemini do NOT emit AGENTS.md\n * (their CLAUDE.md/GEMINI.md @-import the canonical .noir/ sources; a root\n * AGENTS.md would double-import them — fix-wave I1 removed that delta).\n * - The host's native context file when distinct from AGENTS.md\n * (claude → CLAUDE.md; gemini → GEMINI.md; agents-md/cursor/opencode →\n * none, AGENTS.md IS the context). Cursor's working-rules ride AGENTS.md's\n * `@.noir/rules/RULES.md` import — NO separate `.cursor/rules/noir-contract.mdc`\n * host-rules pointer (it was `noir-`-prefixed and the C3 cursor flat-skill\n * prune deleted it on every sync).\n * - The host's MCP config (.mcp.json / .gemini/mcp.json / .cursor/mcp.json /\n * opencode.json).\n *\n * Severity is `ok` when every expected path exists, `warn` when any are\n * missing (NEVER `fail` — a missing host artifact is restored by `noir sync`,\n * not a critical product failure). Mirrors the policy of the other\n * presence/projection checks (scaffold-version drift, RULES.md budget): warn,\n * surface the remediation, never block.\n *\n * Returns the structured payload for the `--json` envelope's `data.host`, or\n * `null` when the project isn't initialized (no resolved host).\n */\nfunction checkHostArtifacts(\n checks: CheckResult[],\n root: string,\n project: ProjectInfo | undefined,\n): { active: HostId; expected: string[]; missing: string[] } | null {\n if (!project) {\n checks.push({ name: 'host artifacts', status: 'warn', detail: 'skipped — not initialized' });\n return null;\n }\n const host = project.config.host;\n const adapter = resolveAdapter(host);\n const ectx = { root };\n\n // Build the expected repo-relative path list. Mirrors buildHostArtifacts in\n // @noir-ai/create so a missing row maps 1:1 to a manifest entry the user can\n // restore with `noir sync`. Kept inline (not imported) so doctor stays a\n // READ-ONLY health probe with zero write-side coupling.\n const expected: string[] = [];\n // AGENTS.md — only for hosts whose emitContext IS the AGENTS.md (I1: claude/\n // gemini skip it — their native context file already covers .noir/).\n const emitsAgentsMd = host === 'agents-md' || host === 'cursor' || host === 'opencode';\n if (emitsAgentsMd) {\n expected.push(relOr(adapter.agentsMdPath?.(ectx) ?? 'AGENTS.md', root));\n }\n // Host-native context file (when distinct from AGENTS.md).\n if (host === 'claude') expected.push('CLAUDE.md');\n else if (host === 'gemini') expected.push('GEMINI.md');\n // Cursor has NO separate host-rules .mdc — the prior `noir-contract.mdc`\n // pointer was REMOVED (it was `noir-`-prefixed, so the C3 cursor flat-skill\n // prune in emitSkillsToDir deleted it on every noir init/create/sync).\n // Cursor's rules ride AGENTS.md's `@.noir/rules/RULES.md` import instead.\n // Host MCP config (fallback .mcp.json for claude/agents-md).\n expected.push(relOr(adapter.mcpConfigPath?.(ectx) ?? '.mcp.json', root));\n\n const missing = expected.filter((p) => !existsSync(joinRoot(root, p)));\n const status: Severity = missing.length === 0 ? 'ok' : 'warn';\n const detail =\n missing.length === 0\n ? `host=${host} · all ${expected.length} primary artifacts present`\n : `host=${host} · missing ${missing.join(', ')} (run \\`noir sync\\` to restore)`;\n checks.push({ name: 'host artifacts', status, detail });\n return { active: host, expected, missing };\n}\n\n// ---------------------------------------------------------------------------\n// SP-A — nested-`.noir` detection (read-only).\n// ---------------------------------------------------------------------------\n\n/**\n * Detects the fingerprint of a `noir init`/`create` run from INSIDE `.noir/`\n * (now PREVENTED by `assertSafeRoot` in @noir-ai/create, but legacy/already-\n * nested projects still carry the damage): a nested `<root>/.noir/.noir/` store\n * and/or host artifacts emitted into `.noir/` as if it were a project root\n * (`.noir/CLAUDE.md`, `.noir/.mcp.json`, `.noir/.claude/`, and the ignore files\n * `.noir/.gitignore` / `.dockerignore` / `.npmignore` / `.prettierignore`).\n * Read-only `warn` —\n * doctor never mutates; remediation is manual removal (a future follow-up\n * slice can automate it). Never `fail`: a nested store wastes space + confuses\n * tooling but does not break the outer project.\n */\nexport function checkNestedNoir(\n checks: CheckResult[],\n root: string,\n): { detected: boolean; paths: string[] } {\n const candidates = [\n '.noir/.noir',\n '.noir/CLAUDE.md',\n '.noir/.mcp.json',\n '.noir/.claude',\n '.noir/.gitignore',\n '.noir/.dockerignore',\n '.noir/.npmignore',\n '.noir/.prettierignore',\n ];\n const found = candidates.filter((rel) => existsSync(join(root, rel)));\n const detected = found.length > 0;\n checks.push({\n name: 'nested .noir',\n status: detected ? 'warn' : 'ok',\n detail: detected\n ? `nested Noir artifacts inside .noir/ (${found.join(', ')}) — likely from running \\`noir init\\` inside .noir/. Remove them; the real project is at ${root}.`\n : 'no nested .noir/ artifacts',\n });\n return { detected, paths: found };\n}\n\n// ---------------------------------------------------------------------------\n// SP-C deferred — semantic duplicate detection (`--dedup`; loads the embedder).\n// ---------------------------------------------------------------------------\n\n/** Local embedder shape (@noir-ai/context's `EmbedFn`). */\ntype EmbedLike = (text: string) => Promise<Float32Array>;\n\n/**\n * Collect dedup candidates: the host context files that exist at the root\n * (CLAUDE.md / AGENTS.md / GEMINI.md — the hand-mirrored-overlap case) plus\n * `.noir/rules/RULES.md`. `.noir/NOIR.md` is EXCLUDED because host context\n * files `@import` it by design (their similarity is intentional, not a dup).\n */\nexport function collectDedupCandidates(root: string): { path: string; text: string }[] {\n const rels = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md', '.noir/rules/RULES.md'];\n const out: { path: string; text: string }[] = [];\n for (const rel of rels) {\n const abs = join(root, rel);\n if (!existsSync(abs)) continue;\n let text: string;\n try {\n text = readFileSync(abs, 'utf8');\n } catch {\n continue;\n }\n if (text.trim().length === 0) continue;\n out.push({ path: rel, text });\n }\n return out;\n}\n\n/**\n * `noir doctor --dedup`: embed the host-context + RULES.md candidates and\n * report near-duplicate pairs (cosine ≥ 0.90). The ONLY mechanism that catches\n * cross-file SEMANTIC overlap (e.g. a hand-mirrored CLAUDE.md ≈ AGENTS.md) —\n * exact content-hash cannot. Opt-in so a default `noir doctor` never pays the\n * embedder-load cost; degrades to a `warn` skip when the embedder is\n * unavailable (kind=none / native layer missing). `opts.embed` is a testability\n * seam — tests inject a deterministic fake; production omits it and the local\n * embedder is lazy-loaded via `@noir-ai/context`.\n */\nexport async function checkSemanticDupDoctor(\n checks: CheckResult[],\n root: string,\n project: ProjectInfo | undefined,\n opts: { embed?: EmbedLike } = {},\n): Promise<void> {\n if (!project) {\n checks.push({ name: 'semantic dup', status: 'warn', detail: 'skipped — not initialized' });\n return;\n }\n const candidates = collectDedupCandidates(root);\n if (candidates.length < 2) {\n checks.push({\n name: 'semantic dup',\n status: 'ok',\n detail: `${candidates.length} candidate file(s); nothing to compare`,\n });\n return;\n }\n const ctx = await import('@noir-ai/context');\n let embed: EmbedLike | undefined = opts.embed;\n if (!embed) {\n const cfg = ctx.resolveEmbedderConfig(project.config.context);\n if (cfg.kind === 'none') {\n checks.push({\n name: 'semantic dup',\n status: 'warn',\n detail: 'embedder kind=none — set context.embedder.kind=local to enable semantic dedup',\n });\n return;\n }\n try {\n embed = ctx.createEmbedFn(cfg).embed as EmbedLike;\n } catch (e) {\n checks.push({\n name: 'semantic dup',\n status: 'warn',\n detail: `embedder unavailable — skipped (${msg(e)})`,\n });\n return;\n }\n }\n if (!embed) return;\n const pairs = await ctx.findSemanticDuplicates(candidates, embed, ctx.DEFAULT_DUP_THRESHOLD);\n checks.push({\n name: 'semantic dup',\n status: pairs.length > 0 ? 'warn' : 'ok',\n detail:\n pairs.length === 0\n ? `no near-duplicates across ${candidates.length} file(s)`\n : `${pairs.length} near-duplicate pair${pairs.length === 1 ? '' : 's'}: ${pairs\n .map((p) => `${p.a}≈${p.b} (${p.similarity.toFixed(2)})`)\n .join('; ')}`,\n });\n}\n\n// ---------------------------------------------------------------------------\n// S11 — publish-readiness check (advisory, repo-developer-facing).\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the workspace `packages/` directory from the CLI's own location —\n * walk up from `import.meta.url` to the nearest `package.json` named\n * `@noir-ai/cli`, then return its PARENT dir (the `packages/` root) IF that\n * dir's parent carries the monorepo marker (`pnpm-workspace.yaml`). Returns\n * `null` when not running from a monorepo checkout (a global `npm install -g`\n * has no `packages/*` workspace to validate → the publish check skips).\n *\n * The walk is robust across layouts: source (`packages/cli/src/commands/`),\n * built (`packages/cli/dist/`), and bundled-chunk (`packages/cli/dist/`) all\n * walk up to `packages/cli/package.json`. In a global install the same walk\n * lands on `node_modules/@noir-ai/cli/package.json`, whose parent\n * (`node_modules/@noir-ai/`) has NO `pnpm-workspace.yaml` parent → null.\n */\nfunction resolveWorkspacePackagesDir(): string | null {\n let here = dirname(fileURLToPath(import.meta.url));\n // Bound the walk so a pathological layout never loops the filesystem.\n for (let i = 0; i < 8; i++) {\n const pkgPath = join(here, 'package.json');\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as { name?: string };\n if (pkg.name === '@noir-ai/cli') {\n // `here` is the cli package dir; `dirname(here)` is `packages/`.\n const packagesDir = dirname(here);\n // Monorepo gate: the repo root (`dirname(packagesDir)`) carries\n // `pnpm-workspace.yaml`. A global install fails this gate.\n if (existsSync(join(dirname(packagesDir), 'pnpm-workspace.yaml'))) {\n return packagesDir;\n }\n return null;\n }\n } catch {\n // Malformed package.json — keep walking.\n }\n }\n const parent = dirname(here);\n if (parent === here) break; // filesystem root\n here = parent;\n }\n return null;\n}\n\n/**\n * Permissive semver-ish regex for the advisory check. Accepts release and\n * pre-release/build forms (`1.0.0`, `1.1.0-beta.1`, `1.2.0+build.4`). Strict\n * enough to catch the obvious foot-guns (a git SHA, a literal \"latest\", a\n * missing patch) without pulling in the `semver` package for a warn-level\n * check. `npm publish` itself enforces rigor at pack time.\n */\nconst SEMVER_ISH = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$/;\n\n/** Best-effort `npm pack --dry-run` on the cli package — read-only (it does\n * NOT write the tarball). Returns `{ fileCount, distIncluded }` parsed from\n * npm's notice output, or `{ error }` when npm is unavailable / the pack\n * fails. Gated on npm being on PATH (the spec's expensive-but-optional leg):\n * we probe `npm --version` first and skip-with-note if it isn't resolvable. */\nfunction npmPackDryRun(\n cliPkgDir: string,\n): { fileCount: number | null; distIncluded: boolean | null } | { error: string } {\n // Probe npm availability first (cheap). shell:true so Windows finds npm.cmd.\n const probe = spawnSync('npm', ['--version'], { encoding: 'utf8', shell: true });\n if (probe.status !== 0 || probe.error) {\n return { error: probe.error ? probe.error.message : 'npm not on PATH' };\n }\n // `npm pack --dry-run` lists the tarball contents without writing the file.\n // It IS read-only (npm's own docs: \"--dry-run ... does not write anything\").\n const res = spawnSync('npm', ['pack', '--dry-run'], {\n cwd: cliPkgDir,\n encoding: 'utf8',\n shell: true,\n });\n if (res.status !== 0 || res.error) {\n return { error: res.error ? res.error.message : `npm pack exited ${res.status ?? '?'}` };\n }\n const out = `${res.stdout ?? ''}\\n${res.stderr ?? ''}`;\n // `npm notice total files: 42` — the canonical summary line.\n const totalMatch = out.match(/total files:\\s*(\\d+)/i);\n const fileCount = totalMatch?.[1] ? Number.parseInt(totalMatch[1], 10) : null;\n // `dist/` appears in the tarball contents (`npm notice <size> dist/<file>`).\n const distIncluded = /\\bdist\\//.test(out);\n return { fileCount, distIncluded };\n}\n\n/**\n * S11 publish-readiness check. For every workspace `package.json` under\n * `packagesDir`, validate publishability:\n * - `name` starts with `@noir-ai/`\n * - `version` matches {@link SEMVER_ISH}\n * - `files` is a non-empty array\n * - the cli package has a `bin` field\n *\n * Optionally runs `npm pack --dry-run` on the cli package (best-effort, gated\n * on npm being available) to report the file count + confirm `dist/` ships.\n *\n * Severity is ALWAYS `ok` or `warn` — NEVER `fail`: a missing/odd package.json\n * field does not break the local install (this is an advisory nudge to repo\n * developers, not a project-facing health check). The check runs ALWAYS (it\n * produces a row in every doctor report) but returns `null` from\n * `data.publish` when not running from a monorepo (`packagesDir === null`).\n */\nexport function checkPublish(\n checks: CheckResult[],\n packagesDir: string | null,\n opts: { skipNpmPack?: boolean } = {},\n): { checked: number; issues: string[] } | null {\n if (packagesDir === null) {\n checks.push({\n name: 'publish',\n status: 'ok',\n detail: 'skipped — not a monorepo checkout (no packages/* workspace)',\n });\n return null;\n }\n // Discover workspace package.json files (depth-1 dirs only — no glob dep).\n let entries: string[] = [];\n try {\n entries = readdirSync(packagesDir, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => d.name);\n } catch (e) {\n checks.push({\n name: 'publish',\n status: 'warn',\n detail: `could not read ${packagesDir}: ${msg(e)}`,\n });\n return { checked: 0, issues: [`unreadable packages dir: ${msg(e)}`] };\n }\n\n const issues: string[] = [];\n let checked = 0;\n let cliDir: string | null = null;\n for (const name of entries) {\n const pkgPath = join(packagesDir, name, 'package.json');\n if (!existsSync(pkgPath)) continue;\n let pkg: Record<string, unknown>;\n try {\n pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as Record<string, unknown>;\n } catch (e) {\n issues.push(`${name}/package.json: unparseable (${msg(e)})`);\n checked++;\n continue;\n }\n checked++;\n const pkgName = typeof pkg.name === 'string' ? pkg.name : '';\n if (!pkgName.startsWith('@noir-ai/')) {\n issues.push(`${name}/package.json: name '${pkgName}' is not @noir-ai/*`);\n }\n const version = typeof pkg.version === 'string' ? pkg.version : '';\n if (!SEMVER_ISH.test(version)) {\n issues.push(`${name}/package.json: version '${version}' is not semver`);\n }\n if (!Array.isArray(pkg.files) || pkg.files.length === 0) {\n issues.push(`${name}/package.json: files missing or empty`);\n }\n if (pkgName === '@noir-ai/cli') {\n if (pkg.bin == null || (typeof pkg.bin === 'object' && Object.keys(pkg.bin).length === 0)) {\n issues.push(`${name}/package.json: bin missing (cli package must declare a bin)`);\n }\n cliDir = join(packagesDir, name);\n }\n }\n\n // Best-effort npm pack --dry-run on the cli package. Gated on npm being\n // available; a failure here is surfaced in the detail but does NOT add an\n // issue (it is environmental, not a package.json defect). `skipNpmPack` is a\n // testability seam — synthetic test fixtures have no real `dist/`, so the\n // dist-absent signal would otherwise pollute the package.json-validation\n // assertions. Production (`doctor()`) leaves it unset.\n let packNote = '';\n if (cliDir && opts.skipNpmPack !== true) {\n const pack = npmPackDryRun(cliDir);\n if ('error' in pack) {\n packNote = ` · npm pack skipped (${pack.error})`;\n } else {\n const count = pack.fileCount == null ? '?' : String(pack.fileCount);\n const dist = pack.distIncluded ? 'dist/ included' : 'dist/ NOT in tarball';\n packNote = ` · npm pack: ${count} files, ${dist}`;\n if (pack.distIncluded === false) {\n issues.push('cli npm pack --dry-run: dist/ absent from the tarball');\n }\n }\n }\n\n const status: Severity = issues.length > 0 ? 'warn' : 'ok';\n const detail =\n issues.length === 0\n ? `${checked}/${entries.length} packages publish-ready${packNote}`\n : `${issues.length} issue${issues.length === 1 ? '' : 's'} across ${checked} package${checked === 1 ? '' : 's'}${packNote}`;\n checks.push({ name: 'publish', status, detail });\n return { checked, issues };\n}\n\n/** Repo-relative POSIX form of `abs` under `root`. Defensive (N2): rejects\n * paths that escape `root` — mirrors `hostRel` in @noir-ai/create/manifest.ts.\n * A future adapter that returns a stray path fails loudly here instead of\n * producing a misleading \"missing\" row for a path doctor would never probe.\n * Relative literals (e.g. `'AGENTS.md'`, `'.mcp.json'`) pass through unchanged\n * AFTER the `..`-escape check so a buggy literal could not exfiltrate either. */\nfunction relOr(abs: string, root: string): string {\n const posix = abs.replace(/\\\\/g, '/');\n if (posix.startsWith('/')) {\n const rel = relative(root, abs).replace(/\\\\/g, '/');\n if (rel.length === 0 || rel.startsWith('..') || rel.startsWith('/')) {\n throw new Error(`doctor: host path '${abs}' is not under root '${root}'`);\n }\n return rel;\n }\n // Already-relative literal: still reject `..` segments (a `../etc/passwd`\n // literal from a buggy adapter must not silently pass through).\n if (posix.split('/').some((seg) => seg === '..')) {\n throw new Error(`doctor: host path '${abs}' escapes root '${root}'`);\n }\n return posix;\n}\n\n/** Join `root` with a repo-relative POSIX path for an existsSync probe. */\nfunction joinRoot(root: string, relPath: string): string {\n return `${root}/${relPath}`;\n}\n\nfunction severityBadge(s: Severity): string {\n // Doctor's vocabulary is ok/warn/fail; the badge palette is ok/warn/error/info.\n // `fail` is the CRITICAL state → red (`error`), so RED stays reserved for the\n // broken-host states. The TEXT label keeps doctor's own uppercase word so the\n // JSON envelope (`status: 'fail'`) and the human row agree on vocabulary.\n const state: BadgeState = s === 'fail' ? 'error' : s;\n return badge(state, s.toUpperCase());\n}\n\nfunction renderHuman(payload: DoctorPayload, opts: CliOptions): void {\n log(\n `noir doctor — ${payload.checks.length} check${payload.checks.length === 1 ? '' : 's'}`,\n opts,\n );\n table(\n payload.checks.map((c) => ({\n Check: c.name,\n Status: severityBadge(c.status),\n Detail: c.detail,\n })),\n ['Check', 'Status', 'Detail'],\n opts,\n );\n const { ok, warn: warnN, fail } = payload.summary;\n if (fail > 0) {\n err(\n `${fail} critical check${fail === 1 ? '' : 's'} failed (${warnN} warning${warnN === 1 ? '' : 's'}, ${ok} ok)`,\n opts,\n );\n } else if (warnN > 0) {\n warn(`all critical checks passed (${warnN} warning${warnN === 1 ? '' : 's'}, ${ok} ok)`, opts);\n } else {\n success(`all ${ok} check${ok === 1 ? '' : 's'} passed`, opts);\n }\n}\n\n/**\n * `noir doctor`: run all checks, render, and exit.\n *\n * Under `--json` writes `{ok:true, data: DoctorPayload}` to stdout (always —\n * the command produced a diagnosis). The exit code carries health: 0 when no\n * check failed, 1 when any CRITICAL (`fail`) check is unhealthy.\n */\nexport async function doctor(opts: DoctorOptions = {}): Promise<void> {\n const root = process.cwd();\n const checks: CheckResult[] = [];\n\n await checkRuntime(checks);\n const { project, result: configResult } = checkConfig(root);\n checks.push(configResult);\n await checkDaemon(checks);\n const { vecOk } = await checkNativeDeps(checks);\n await checkStore(checks, project, root);\n checkEmbedder(checks, project, vecOk);\n checkProvider(checks, project);\n const scaffold = checkScaffoldVersion(checks, root);\n const rules = checkRulesMdBudget(checks, root, project);\n const host = checkHostArtifacts(checks, root, project);\n checkNestedNoir(checks, root);\n if (opts.dedup === true) {\n await checkSemanticDupDoctor(checks, root, project);\n }\n const publish = checkPublish(checks, resolveWorkspacePackagesDir());\n\n // C1 — install-method check (advisory, cache-only, never fail).\n const installCheck = buildInstallCheck({\n method: detectActiveMethod(),\n version: readInstallRecord()?.version ?? null,\n latestKnown: latestVersionFromCache(readUpdateCache(), 'latest'),\n });\n checks.push(installCheck);\n\n const summary = summarize(checks);\n const payload: DoctorPayload = {\n noir: NOIR_VERSION,\n checks,\n summary,\n scaffold,\n rules,\n host,\n publish,\n install: {\n method: installCheck.method,\n installedVersion: installCheck.installedVersion,\n latestKnown: installCheck.latestKnown,\n },\n };\n\n if (opts.json === true) {\n process.stdout.write(`${JSON.stringify({ ok: true, data: payload })}\\n`);\n } else {\n renderHuman(payload, opts);\n }\n\n if (summary.fail > 0) {\n // Under --json the data envelope is already on stdout; throw with an empty\n // message so handleError sets exitCode=1 without a redundant stderr line.\n // Under human mode the summary line above already named the failures.\n throw new NoirCliError(\n EXIT.ERROR,\n opts.json === true ? '' : `noir doctor: ${summary.fail} critical check(s) failed`,\n );\n }\n}\n","// SP-C — conflict-resolution UX seam. The engine (@noir-ai/create) is UI-free;\n// this module builds the scaffold conflict options (policy + a lazy @clack\n// resolver) per the S9 interactivity contract:\n// - TTY + interactive → @clack select menu (Replace/Rename/Duplicate/Keep/Cancel,\n// plus a 6th \"merge (with conflict markers)\" when `mergedWithMarkers` is set)\n// - non-TTY / CI / NO_COLOR → preserve (never silently clobber in a pipe)\n// - --force → overwrite (explicit re-scaffold; no prompt)\n//\n// Universal contract: this seam now drives EVERY file-producing path. The\n// engine's regenerate path consults `onConflict` (apply-to-all keyed by artifact\n// CLASS so an N-pointer `noir init --upgrade` → 1 prompt); managedBlock conflicts\n// stay PER-FILE. A unified line-diff preview (LCS-based, from\n// @noir-ai/create's `lineDiff`) renders to STDERR before the prompt (via the\n// A2 theme — `+` green / `-` red / context dim), so `--json`/piped stdout stays\n// pristine. The 6th \"merge\" option writes the engine-provided zdiff3-marked\n// bytes when a 3-way merge hit an overlap.\nimport {\n type ConflictContext,\n type ConflictResolution,\n type ConflictResolverReturn,\n lineDiff,\n} from '@noir-ai/create';\nimport { isInteractive } from './output.js';\nimport { c } from './theme.js';\n\nexport interface ConflictOptsInput {\n /** `--force`: explicit re-scaffold — overwrite differing files, no prompt. */\n force?: boolean;\n /** B1: explicit interactivity signal. When set, wins over the env/TTY\n * heuristic. `false` ⇒ preserve (never prompt); `true` ⇒ allow the @clack\n * resolver. The CLI derives this from the `NOIR_NON_INTERACTIVE` bridge\n * bin.ts owns + the `isInteractive()` TTY/CI/NO_COLOR gate. */\n interactive?: boolean;\n}\n\nexport type ScaffoldConflictOpts = {\n conflictPolicy: 'overwrite' | 'preserve';\n onConflict?: (ctx: ConflictContext) => Promise<ConflictResolverReturn> | ConflictResolverReturn;\n};\n\n/**\n * Build the scaffold conflict options for the current invocation. The engine\n * calls `onConflict` only when a `regenerate` file exists AND differs from the\n * template, so passing this on every init/create/sync is harmless on a first\n * run (no existing files → no conflict).\n *\n * B1 + SP-G: the `interactive` flag (explicit > env bridge > TTY/CI/NO_COLOR\n * gate) drives the prompt decision so the engine never reads `process.env` for\n * interactivity. The `NOIR_NON_INTERACTIVE` bridge (--json/--no-input) and the\n * `isInteractive()` gate both reduce to `preserve` (never prompt).\n *\n * B2 apply-to-all: when the resolver returns `{resolution, applyToAll: true}`\n * the engine stores the choice in its per-run memory keyed by artifact CLASS\n * (regenerate shares one decision; managedBlock/managedBlocks stay per-file).\n */\n/** True when the bin's preAction hook flagged this invocation non-interactive\n * (`--json` / `--no-input`) so the conflict resolver never prompts under those. */\nfunction flaggedNonInteractive(): boolean {\n const v = process.env.NOIR_NON_INTERACTIVE;\n return v !== undefined && v !== '';\n}\n\nexport function buildConflictOpts(input: ConflictOptsInput = {}): ScaffoldConflictOpts {\n if (input.force === true) {\n return { conflictPolicy: 'overwrite' };\n }\n // Explicit interactive flag wins over the env/TTY heuristic.\n if (input.interactive === false) {\n return { conflictPolicy: 'preserve' };\n }\n if (input.interactive === true) {\n return { conflictPolicy: 'preserve', onConflict: clackConflictResolver };\n }\n // Fallback: NOIR_NON_INTERACTIVE bridge (--json/--no-input) OR the\n // isInteractive() TTY/CI/NO_COLOR gate — either ⇒ preserve, never prompt.\n if (flaggedNonInteractive() || !isInteractive()) {\n return { conflictPolicy: 'preserve' };\n }\n return { conflictPolicy: 'preserve', onConflict: clackConflictResolver };\n}\n\n/**\n * B2 — render a colored unified line-diff of `{existing, proposed}` to STDERR\n * before the @clack prompt. Stderr (not stdout) so `--json` / piped stdout\n * stays pristine. The A2 theme colors `+` lines green (`c.ok`), `-` lines red\n * (`c.error`), and dims context lines (`c.dim`); all color is gated through\n * `useColor()` so NO_COLOR / non-TTY / CLICOLOR_FORCE behave consistently.\n * Honored by `theme.test.ts`'s NO_COLOR gate.\n */\nfunction renderDiffPreview(ctx: ConflictContext): void {\n // `lineDiff` is imported statically at the top of this module. It used to be\n // a lazy `require('@noir-ai/create')`, but tsup bundles every @noir-ai/*\n // internal into dist/bin.js — a dynamic `require` survives bundling as an\n // ESM-incompatible `__require` shim that throws \"Dynamic require of\n // @noir-ai/create is not supported\" at runtime (broke `init --upgrade`\n // whenever a file conflict triggered this preview). The static import is\n // bundled correctly and runs in pure ESM with no side effects here.\n const lines = lineDiff(ctx.existing, ctx.proposed);\n if (lines.length === 0) return;\n const out: string[] = [`noir: conflict in ${ctx.relPath}`];\n for (const dl of lines) {\n if (dl.type === 'add') out.push(c.ok(`+ ${dl.line}`));\n else if (dl.type === 'del') out.push(c.error(`- ${dl.line}`));\n else out.push(c.dim(` ${dl.line}`));\n }\n process.stderr.write(`${out.join('\\n')}\\n`);\n}\n\n/**\n * SP-C + B2 — @clack-based conflict resolver. Renders a unified diff to stderr\n * (via {@link renderDiffPreview}) BEFORE the select prompt, then offers the\n * usual Replace/Rename/Duplicate/Keep/Cancel set PLUS a 6th \"merge (with\n * conflict markers)\" option when `ctx.mergedWithMarkers` is set (a 3-way merge\n * hit an overlap and the engine has the marked bytes ready).\n *\n * Apply-to-all: when the user picks an option, @clack asks whether to reuse the\n * choice for the rest of the run; if yes, the resolver returns the rich\n * `{resolution, applyToAll: true}` shape and the engine stores it in per-class\n * memory. `managedBlock`/`managedBlocks` modes bypass apply-to-all (per-file).\n */\nasync function clackConflictResolver(ctx: ConflictContext): Promise<ConflictResolverReturn> {\n renderDiffPreview(ctx);\n const clack = await import('@clack/prompts');\n const options: Array<{ value: ConflictResolution; label: string; hint: string }> = [\n {\n value: 'replace',\n label: 'Replace',\n hint: 'overwrite with the template (discard your edits)',\n },\n { value: 'rename', label: 'Rename', hint: 'keep yours as <file>.local; write the template' },\n {\n value: 'duplicate',\n label: 'Create duplicate',\n hint: 'write template to <file>.noir; keep yours',\n },\n { value: 'preserve', label: 'Keep mine', hint: 'skip; leave your file unchanged' },\n { value: 'cancel', label: 'Cancel', hint: 'abort the scaffold' },\n ];\n // 6th \"merge\" option only when the engine has marked bytes ready.\n if (ctx.mergedWithMarkers !== undefined) {\n options.splice(4, 0, {\n value: 'merge',\n label: 'Merge (with conflict markers)',\n hint: 'write the 3-way merge with <<<<<<< / ||||||| / ======= / >>>>>>> markers',\n });\n }\n const choice = await clack.select({\n message: `${ctx.relPath} was edited and differs from the Noir template.`,\n initialValue: 'preserve',\n options,\n });\n if (clack.isCancel(choice)) return 'cancel';\n const resolution = choice as ConflictResolution;\n // Apply-to-all. The `regenerate` scaffold path AND the `skill` emit path both\n // key their per-run memory by a single class, so one \"Yes\" reuses the choice\n // across the whole run. The `managedBlock`/`managedBlocks` paths are per-file\n // (the engine keys memory by path::block, not by class), so they must NOT\n // offer \"apply to all\" — reusing a per-file decision there would silently\n // clobber unrelated blocks.\n const mode = (ctx.mode ?? 'regenerate') as string;\n const supportsApplyToAll = mode !== 'managedBlock' && mode !== 'managedBlocks';\n if (supportsApplyToAll) {\n const remember = await clack.select({\n message: `Apply \"${labelFor(resolution)}\" to all remaining conflicts this run?`,\n initialValue: 'no',\n options: [\n { value: 'yes', label: 'Yes', hint: 'reuse this choice for the rest of this run' },\n { value: 'no', label: 'No', hint: 'prompt again for each conflict' },\n ],\n });\n if (!clack.isCancel(remember) && remember === 'yes') {\n return { resolution, applyToAll: true };\n }\n }\n return resolution;\n}\n\nfunction labelFor(resolution: ConflictResolution): string {\n switch (resolution) {\n case 'replace':\n return 'Replace';\n case 'rename':\n return 'Rename';\n case 'duplicate':\n return 'Create duplicate';\n case 'preserve':\n return 'Keep mine';\n case 'merge':\n return 'Merge (with conflict markers)';\n case 'cancel':\n return 'Cancel';\n }\n}\n","// Write-path semantic dedup. After `scaffold()` writes host-context\n// files (CLAUDE.md / AGENTS.md / GEMINI.md / .noir/rules/RULES.md), embed the\n// just-written file and compare it against the OTHER existing host-context\n// files. Surface a near-duplicate as a NON-BLOCKING recommendation:\n//\n// - cosine ≥ 0.95 → ACTION prompt (interactive): Replace the existing /\n// Mirror into it / Skip (delete the new file) / Create\n// anyway. Under --json/--no-input the action tier records\n// the near-dup in ScaffoldResult.conflicts[] (mode=\n// 'artifact', similarity set) and proceeds with the\n// default (Create anyway); NEVER prompts.\n// - 0.85–0.95 → INFO-only hint to stderr, write proceeds.\n// - < 0.85 → silent.\n//\n// GRACEFUL DEGRADATION (the make-orreak constraint): the embedder (ONNX\n// Runtime + the ~22 MB all-MiniLM-L6-v2 model) is lazy-loaded with a 5s ping\n// timeout; if it is unavailable, slow, OR throws, the hook prints a one-line\n// stderr warn-skip and returns an empty result. `noir init`/`sync`/`create`\n// NEVER block on a model download or fail because the embedder is missing —\n// dedup is an ENHANCEMENT, not a gate. A fresh project with no existing\n// host-context candidates short-circuits BEFORE any embedding (fast path).\n//\n// CONTENT-HASH GATE: candidate embeddings are cached under\n// `.noir/dedup-cache.json` keyed by SHA-256(content); only changed candidates\n// re-embed. The proposed file's embedding is cached the same way, so a repeat\n// `noir sync` that re-emits byte-identical content does NOT re-embed. Cache\n// entries are scoped to the resolved model id (a model change invalidates them).\n//\n// When a near-dup is found, the conflict record (mode 'artifact'\n// — the closest generic in ConflictContext['mode']) is appended to the\n// ScaffoldResult.conflicts[] the caller returns, so `--json` consumers see the\n// near-dup with its cosine `similarity` without a prompt.\n//\n// Stream discipline: every hint / warn-skip → STDERR; the CLI's `json()` is the\n// sole stdout writer. Colors via the A2 theme (`c.warn`/`c.dim`) so NO_COLOR /\n// non-TTY strip cleanly.\n\nimport { createHash } from 'node:crypto';\nimport { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ProjectInfo } from '@noir-ai/core';\nimport type { ConflictRecord, ScaffoldResult } from '@noir-ai/create';\nimport { collectDedupCandidates } from './commands/doctor.js';\nimport { c } from './theme.js';\n\n/** Local embedder shape (@noir-ai/context's `EmbedFn`). */\nexport type EmbedLike = (text: string) => Promise<Float32Array>;\n\n/** Host-context files dedup scans (mirrors `collectDedupCandidates`). */\nconst HOST_CONTEXT_RELS = new Set(['CLAUDE.md', 'AGENTS.md', 'GEMINI.md', '.noir/rules/RULES.md']);\n\n/** Cosine ≥ this → ACTION prompt (interactive) or conflict record (--json). */\nexport const ACTION_THRESHOLD = 0.95;\n/** Cosine ≥ this (and < ACTION) → INFO-only hint. Below → silent. */\nexport const INFO_THRESHOLD = 0.85;\n/** The first embed() call triggers ONNX load + possibly a model download; bound\n * it so init/sync/create never block indefinitely on a cold cache. */\nexport const EMBEDDER_PING_TIMEOUT_MS = 5000;\n/** Bump on disk-format changes; an older file is discarded (no migration). */\nconst DEDUP_CACHE_VERSION = 1;\n\nexport interface DedupWriteOpts {\n /** Explicit interactivity flag (the engine + skills emit read the same). */\n interactive: boolean;\n /** Project info — read from `.noir/config.yml` by the caller. When undefined\n * OR when the resolved embedder is `kind:'none'`, the hook warn-skips. */\n project?: ProjectInfo;\n /** Testability seam (mirrors `checkSemanticDupDoctor`'s `opts.embed`): when\n * set, the hook uses this embedder directly and skips the lazy ONNX load +\n * ping-timeout. Production omits it. */\n embed?: EmbedLike;\n}\n\nexport interface DedupWriteResult {\n /** Near-duplicates found, one entry per (proposed, matched) pair. */\n found: Array<{ proposedRel: string; matchedRel: string; similarity: number }>;\n /** Conflict records to append to {@link ScaffoldResult.conflicts}. */\n conflicts: ConflictRecord[];\n}\n\n/**\n * Run the write-path semantic dedup over the host-context files in\n * `res.written`. Does NOT mutate `res`; the caller decides whether to splice\n * `result.conflicts` into `res.conflicts`. Non-blocking: any embedder failure\n * degrades to a stderr warn-skip and an empty result.\n */\nexport async function checkWritePathDedup(\n root: string,\n res: ScaffoldResult,\n opts: DedupWriteOpts,\n): Promise<DedupWriteResult> {\n const result: DedupWriteResult = { found: [], conflicts: [] };\n // Fast path: only host-context files we JUST wrote are eligible. Fresh\n // projects (first init/create) typically write ONE host pointer + RULES.md;\n // if no other host-context file exists, candidates is empty → no embedding.\n const writtenHostFiles = res.written.filter((p) => HOST_CONTEXT_RELS.has(p));\n if (writtenHostFiles.length === 0) return result;\n\n const cache = loadCache(root);\n const embed = await resolveEmbedder(opts, cache);\n if (embed === undefined) return result; // warn-skip already emitted\n\n // Wrap the embedder with the content-hash cache so unchanged candidates (and\n // a repeat-emit of byte-identical proposed bytes) skip the underlying call.\n const cachedEmbed = makeCachedEmbed(cache, embed);\n\n for (const proposedRel of writtenHostFiles) {\n const proposedAbs = join(root, proposedRel);\n let proposedText: string;\n try {\n proposedText = readFileSync(proposedAbs, 'utf8');\n } catch {\n continue;\n }\n if (proposedText.trim().length === 0) continue;\n\n // Candidates = existing host-context files MINUS this proposed path. A\n // first-run init writing CLAUDE.md while AGENTS.md already exists compares\n // the new CLAUDE.md against AGENTS.md + RULES.md (not against itself).\n const candidates = collectDedupCandidates(root).filter((c) => c.path !== proposedRel);\n if (candidates.length === 0) continue; // fresh project, nothing to compare\n\n const ctx = await import('@noir-ai/context');\n const proposed = { path: proposedRel, text: proposedText };\n // Graceful degradation INSIDE the loop too: an embedder that loads (passes\n // the ping) but throws mid-batch (transient remote failure, OOM, …) must\n // never break the write. Catch, warn-skip this proposal, keep going.\n let best: Awaited<ReturnType<typeof ctx.findNearestDuplicate>>;\n try {\n best = await ctx.findNearestDuplicate(\n proposed,\n candidates,\n async (t) => cachedEmbed(t),\n INFO_THRESHOLD,\n );\n } catch (e) {\n process.stderr.write(\n `${c.dim(`noir: semantic-dedup skipped for ${proposedRel} (${errMsg(e)}).`)}\\n`,\n );\n continue;\n }\n if (best === null) continue;\n\n const matchedRel = best.a === proposedRel ? best.b : best.a;\n const sim = best.similarity;\n result.found.push({ proposedRel, matchedRel, similarity: sim });\n\n if (sim >= ACTION_THRESHOLD) {\n if (opts.interactive) {\n await promptDedupAction(root, proposedRel, matchedRel, proposedText, sim);\n } else {\n // --json / --no-input: record + proceed with the default (Create\n // anyway). Never prompt under non-interactive.\n process.stderr.write(\n `${c.warn('noir: near-duplicate')} ${proposedRel} ≈ ${matchedRel} (cosine ${sim.toFixed(2)}); creating anyway.\\n`,\n );\n }\n } else {\n // 0.85 – 0.95: INFO-only hint. Write already succeeded; just surface.\n process.stderr.write(\n `${c.dim(`noir: hint ${proposedRel} looks similar to existing ${matchedRel} (cosine ${sim.toFixed(2)}).`)}\\n`,\n );\n }\n\n // Append a structured record so --json consumers see the\n // near-dup with its cosine similarity without a prompt. The path is the\n // file we just wrote (consistent with the \"path = the file being\n // written\" convention); existingSha is the MATCHED file's hash, proposedSha the new\n // bytes'. Resolution 'preserve' = we kept both as-is under Create-anyway.\n const matchedText = safeRead(join(root, matchedRel));\n result.conflicts.push({\n path: proposedRel,\n mode: 'artifact',\n similarity: sim,\n existingSha: sha256Hex12(matchedText ?? ''),\n proposedSha: sha256Hex12(proposedText),\n resolution: 'preserve',\n });\n }\n\n saveCache(root, cache);\n return result;\n}\n\n/**\n * Resolve the embedder: injected (test seam) > local-embedder from the user's\n * `context.embedder` config. Returns undefined (after a stderr warn-skip) when\n * kind='none', the factory throws, OR the first ping exceeds the timeout.\n *\n * Model-id tracking runs in BOTH paths so a config change invalidates the\n * on-disk cache even when the caller injected an embedder (the test seam\n * still flows through the same cache discipline as production).\n */\nasync function resolveEmbedder(\n opts: DedupWriteOpts,\n cache: DedupCache,\n): Promise<EmbedLike | undefined> {\n const ctx = await import('@noir-ai/context');\n // Resolve the model id from config when available; an injected embedder\n // without project info skips invalidation (cache stays model-scoped to '' —\n // a fresh cache, which is fine for tests that always inject).\n let modelId: string | undefined;\n if (opts.project !== undefined) {\n const cfg = ctx.resolveEmbedderConfig(opts.project.config.context);\n if (cfg.kind === 'none') {\n process.stderr.write(`${c.dim('noir: semantic-dedup skipped (embedder kind=none).')}\\n`);\n return undefined;\n }\n modelId =\n cfg.kind === 'local' ? (cfg.model ?? ctx.DEFAULT_LOCAL_MODEL) : (cfg.model ?? cfg.kind);\n // Invalidate cache on a model change BEFORE deciding the embedder source.\n if (modelId !== undefined && cache.model !== modelId) {\n cache.model = modelId;\n cache.entries = {};\n }\n }\n // Test seam: an injected embedder bypasses the lazy ONNX load + ping.\n if (opts.embed !== undefined) return opts.embed;\n if (modelId === undefined) return undefined; // no project + no inject → skip\n\n let embed: EmbedLike;\n try {\n const project = opts.project;\n if (project === undefined) return undefined;\n const cfg = ctx.resolveEmbedderConfig(project.config.context);\n embed = ctx.createEmbedFn(cfg).embed as EmbedLike;\n } catch (e) {\n process.stderr.write(\n `${c.dim(`noir: semantic-dedup skipped (embedder unavailable: ${errMsg(e)}).`)}\\n`,\n );\n return undefined;\n }\n // Pre-warm with a ping under a timeout. The first call triggers the ONNX\n // load + (on a cold ~/.noir/models/) a ~22 MB model download; bound it so\n // init/sync never block on the network. A throw OR a timeout → warn-skip.\n const ping = await withTimeout(\n embed('ping').then(() => true),\n EMBEDDER_PING_TIMEOUT_MS,\n );\n if (ping !== true) {\n process.stderr.write(\n `${c.dim('noir: semantic-dedup skipped (embedder load too slow or unavailable).')}\\n`,\n );\n return undefined;\n }\n return embed;\n}\n\n/** ACTION prompt for the ≥0.95 tier (interactive only). */\nasync function promptDedupAction(\n root: string,\n proposedRel: string,\n matchedRel: string,\n proposedText: string,\n sim: number,\n): Promise<void> {\n const clack = await import('@clack/prompts');\n type Choice = 'create' | 'skip' | 'replace' | 'mirror';\n const choice = await clack.select({\n message: `${proposedRel} is near-duplicate of existing ${matchedRel} (cosine ${sim.toFixed(2)}). Create anyway?`,\n initialValue: 'create' as Choice,\n options: [\n {\n value: 'create' as Choice,\n label: 'Create anyway',\n hint: 'keep the new file as-is (default)',\n },\n { value: 'skip' as Choice, label: 'Skip', hint: `delete ${proposedRel}; keep ${matchedRel}` },\n {\n value: 'replace' as Choice,\n label: 'Replace existing',\n hint: `overwrite ${matchedRel} with this content`,\n },\n {\n value: 'mirror' as Choice,\n label: 'Mirror into existing',\n hint: `append this content to ${matchedRel}`,\n },\n ],\n });\n if (clack.isCancel(choice)) return; // cancel = keep the default (Create anyway)\n if (choice === 'skip') {\n try {\n rmSync(join(root, proposedRel), { force: true });\n } catch {\n /* best-effort; the write already happened */\n }\n } else if (choice === 'replace') {\n try {\n writeFileSync(join(root, matchedRel), proposedText, 'utf8');\n } catch {\n /* best-effort */\n }\n } else if (choice === 'mirror') {\n try {\n const existing = readFileSync(join(root, matchedRel), 'utf8');\n writeFileSync(\n join(root, matchedRel),\n `${existing}\\n\\n<!-- mirrored from ${proposedRel} -->\\n${proposedText}\\n`,\n 'utf8',\n );\n } catch {\n /* best-effort */\n }\n }\n // 'create' → no-op (the just-written file stands).\n}\n\n// ---------------------------------------------------------------------------\n// Content-hash cache (.noir/dedup-cache.json).\n// ---------------------------------------------------------------------------\n\ninterface DedupCache {\n version: number;\n model: string;\n entries: Record<string, { v: number[] }>;\n}\n\nfunction emptyCache(): DedupCache {\n return { version: DEDUP_CACHE_VERSION, model: '', entries: {} };\n}\n\nfunction cachePath(root: string): string {\n return join(root, '.noir', 'dedup-cache.json');\n}\n\nfunction loadCache(root: string): DedupCache {\n try {\n const raw = readFileSync(cachePath(root), 'utf8');\n const parsed = JSON.parse(raw) as Partial<DedupCache>;\n if (parsed.version !== DEDUP_CACHE_VERSION) return emptyCache();\n if (typeof parsed.entries !== 'object' || parsed.entries === null) return emptyCache();\n return {\n version: DEDUP_CACHE_VERSION,\n model: typeof parsed.model === 'string' ? parsed.model : '',\n entries: parsed.entries,\n };\n } catch {\n return emptyCache();\n }\n}\n\nfunction saveCache(root: string, cache: DedupCache): void {\n try {\n mkdirSync(join(root, '.noir'), { recursive: true });\n writeFileSync(cachePath(root), JSON.stringify(cache), 'utf8');\n } catch {\n // Best-effort; cache persistence is an enhancement, never a gate.\n }\n}\n\n/** Wrap `embed` with a SHA-256 content-hash cache. Cache hits return the\n * stored vector WITHOUT calling `embed`; misses call `embed` and store. */\nfunction makeCachedEmbed(cache: DedupCache, embed: EmbedLike): EmbedLike {\n return async (text: string): Promise<Float32Array> => {\n const key = createHash('sha256').update(text, 'utf8').digest('hex');\n const cached = cache.entries[key];\n if (cached !== undefined) return Float32Array.from(cached.v);\n const v = await embed(text);\n cache.entries[key] = { v: Array.from(v) };\n return v;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Small helpers.\n// ---------------------------------------------------------------------------\n\n/** SHA-256 hex of content, first 12 chars (matches the sha convention). */\nfunction sha256Hex12(text: string): string {\n return createHash('sha256').update(text, 'utf8').digest('hex').slice(0, 12);\n}\n\nfunction safeRead(abs: string): string | undefined {\n try {\n return readFileSync(abs, 'utf8');\n } catch {\n return undefined;\n }\n}\n\n/** Resolve `p` OR undefined on timeout/throw (never rejects). The underlying\n * promise keeps running in the background; we just stop awaiting it. */\nfunction withTimeout<T>(p: Promise<T>, ms: number): Promise<T | undefined> {\n return new Promise((resolve) => {\n const t = setTimeout(() => resolve(undefined), ms);\n p.then(\n (v) => {\n clearTimeout(t);\n resolve(v);\n },\n () => {\n clearTimeout(t);\n resolve(undefined);\n },\n );\n });\n}\n\nfunction errMsg(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n","// `noir init` — first-run + `--upgrade`.\n//\n// Slice S-T2 refactor: the ad-hoc writers (syncIgnores, writeManagedRegion,\n// writeFileSync for .mcp.json/project.id/config.yml/NOIR.md/RULES.md) are\n// replaced by a single call into `@noir-ai/create`'s `scaffold({mode:'init'})`.\n// The manifest + three-mode writer are the source of truth for what init\n// emits; this module is now a thin caller that owns ONLY:\n// - the transport/url precondition (the localhost security gate, whose\n// error strings are locked by url-validation.test.ts), and\n// - skills emission (out-of-manifest by design — composed after scaffold()).\n//\n// S10 multi-host: the 8 direct `claudeAdapter` imports across init/sync/create\n// collapsed to `resolveAdapter(host)` where `host` comes from `--host <id>`\n// (default `'claude'`). The adapter drives (a) the manifest via\n// `scaffold({host})` and (b) skills emission — claude/cursor have a skill dir;\n// gemini/agents-md/opencode have no skill concept and the call is skipped.\n//\n// Deliberate behavior changes vs the predecessor (spec-aligned latent-bug\n// fixes; see S-T1 contract notes + CHANGELOG):\n// - `.noir/project.id` → skipIfExists (predecessor overwrote on every init,\n// orphaning the store DB named after the id). Re-init now preserves it.\n// - `.noir/config.yml` → skipIfExists (predecessor overwrote).\n// - `.noir/NOIR.md` → managedBlock with BRIEF_BLOCK markers (predecessor\n// wrote the whole file with no markers). First-run output GAINS markers;\n// user notes outside the markers survive re-runs.\n// - `.noir/scaffold-version` is now stamped on init/create (engine-owned).\n\nimport { type HostId, resolveAdapter } from '@noir-ai/adapters';\nimport { loadProjectInfo, type ProjectInfo } from '@noir-ai/core';\nimport { type ScaffoldResult, scaffold } from '@noir-ai/create';\nimport { type CompileTarget, emitSkillsToDir } from '@noir-ai/skills';\nimport { buildConflictOpts, type ScaffoldConflictOpts } from './conflict.js';\nimport { checkWritePathDedup } from './dedup-write.js';\nimport { log, resolveInteractive } from './output.js';\n\nexport interface InitOptions {\n transport: 'stdio' | 'streamable-http';\n url?: string;\n /** `noir init --upgrade`: run scaffold migrations from the on-disk\n * scaffold-version to current, then re-emit ONLY the runtime subset\n * (regenerate + managedBlock). skipIfExists seeds are left alone so user\n * edits survive. */\n upgrade?: boolean;\n /** S10 target host. Defaults to `'claude'` (the regression anchor). Drives\n * both scaffold emission (the manifest's host-specific half) and skills\n * emission (skipped for hosts with no `skillsDir`). */\n host?: HostId;\n /** SP-A: re-scaffold even if already initialized (bypasses the\n * already-initialized no-op guard in scaffold()). */\n force?: boolean;\n /** F1: `--dry-run`/`--preview` — report the planned writes to stderr\n * (via {@link reportPlannedWrites}) without touching disk. The scaffold\n * engine already supports this; the CLI just surfaces it. */\n dryRun?: boolean;\n /** F1: alias for `--dry-run`. Kept on the options bag so direct callers can\n * pass either spelling; the bin collapses both flags before dispatch. */\n preview?: boolean;\n}\n\n/**\n * Initialize Noir in `root`. Returns the {@link ScaffoldResult} (with\n * structured `conflicts[]` + any dedup records appended) so `--json`\n * callers can surface conflict detail. `undefined` when the already-initialized\n * guard short-circuited (a no-op).\n */\nexport async function init(root: string, opts: InitOptions): Promise<ScaffoldResult | undefined> {\n assertTransportUrl(opts);\n\n const host: HostId = opts.host ?? 'claude';\n // The engine reads ScaffoldOptions.interactive (hermetic — never\n // process.env). The CLI derives it once from the bridge + TTY/CI/NO_COLOR gate.\n const interactive = resolveInteractive();\n const conflictOpts = buildConflictOpts({ force: opts.force, interactive });\n // F1: --dry-run/--preview collapse to a single dryRun boolean. The engine\n // skips every write and returns the PLANNED lists; skills emission + dedup are\n // skipped too (they would touch disk / load the embedder).\n const dryRun = opts.dryRun === true || opts.preview === true;\n\n const res = await scaffold({\n root,\n mode: 'init',\n host,\n transport: opts.transport,\n interactive,\n ...(opts.url !== undefined ? { url: opts.url } : {}),\n ...(opts.upgrade === true ? { upgrade: true } : {}),\n ...(opts.force === true ? { force: true } : {}),\n ...(dryRun ? { dryRun: true } : {}),\n ...conflictOpts,\n });\n // F1: dry-run reports the planned writes (result's written/skipped/identical)\n // and stops BEFORE skills emission + the \"initialized\" message — nothing was\n // written, so the host skill dir must stay untouched and we must not claim\n // init. Under --json the bin emits the planned list as the `{ok, data}`\n // envelope on stdout (the data channel) instead.\n if (dryRun) {\n reportPlannedWrites(res);\n return res;\n }\n // SP-A: if the already-initialized guard no-op'd scaffold, stop — don't re-emit\n // skills or print \"initialized\" (scaffold already printed the no-op message).\n if (res.noop) return res;\n\n // Thread the SAME conflictOpts into skills emission so the\n // skills-emit conflict flow is LIVE in interactive mode (the producer\n // accepts conflict opts; this closes the wiring gap). The safe-default\n // `assertNotUserOwned` runs unconditionally inside the producer.\n await emitHostSkills(root, host, conflictOpts, interactive);\n\n // Write-path semantic dedup. Non-blocking; degrades to a\n // stderr warn-skip when the embedder is unavailable. Records near-dups on\n // `res.conflicts` so `--json` consumers see them without a prompt.\n // Best-effort project read: first-run init has no .noir/config.yml yet →\n // undefined → resolveEmbedder warn-skips. `init --force` on an existing\n // project reads the config and runs the dedup against existing host files.\n let projectInfo: ProjectInfo | undefined;\n try {\n projectInfo = loadProjectInfo(root);\n } catch {\n projectInfo = undefined;\n }\n const dedup = await checkWritePathDedup(root, res, { interactive, project: projectInfo });\n if (dedup.conflicts.length > 0) res.conflicts.push(...dedup.conflicts);\n\n process.stderr.write(\n `Noir initialized in ${root} (host: ${host}, transport: ${opts.transport}).\\n`,\n );\n process.stderr.write(\n 'Next: run `noir` to open the home menu (or `noir status` for a snapshot).\\n',\n );\n return res;\n}\n\n/**\n * F1 — report a dry-run (--dry-run/--preview) scaffold result. After\n * `scaffold({dryRun:true})` the result's `written`/`skipped`/`identical` carry\n * the PLANNED paths (nothing touched disk): `written` = files that WOULD be\n * written, `skipped` = skipIfExists files already present (left alone),\n * `identical` = files whose bytes would match the template (no rewrite).\n * `conflicts` is always empty under dryRun (no writes → no conflicts).\n *\n * Emitted via the `log()` stderr helper — a HUMAN diagnostic, so under `--json`\n * the bin emits the planned list as the structured `{ok, data}` envelope on\n * stdout (the data channel) instead, matching the S9 stream discipline used by\n * the other init/create/sync diagnostics. Shared by init/create/sync (the same\n * dryRun surface on all three scaffold modes).\n */\nexport function reportPlannedWrites(res: ScaffoldResult): void {\n log('Dry run — no files were written.');\n if (res.written.length > 0) {\n log('Planned writes:');\n for (const p of res.written) log(` ${p}`);\n }\n if (res.skipped.length > 0) {\n log('Would leave as-is (already present):');\n for (const p of res.skipped) log(` ${p}`);\n }\n if (res.identical.length > 0) {\n log('Would rewrite (byte-identical, no-op):');\n for (const p of res.identical) log(` ${p}`);\n }\n}\n\n/**\n * Compose skill emission onto the resolved adapter's `skillsDir` (claude →\n * `.claude/skills/`; cursor → `.cursor/rules/` compiled as `.mdc`; the other\n * three hosts have no skill concept and are skipped with a stderr note).\n *\n * The `CompileTarget` matches the host id (S10 foundation widened the enum to\n * the same union) so cursor skills compile to the `.mdc` rule shape via\n * `compileSkill(_, 'cursor')`; the others keep the verbatim SKILL.md format.\n */\nasync function emitHostSkills(\n root: string,\n host: HostId,\n conflictOpts: ScaffoldConflictOpts,\n interactive: boolean,\n): Promise<void> {\n const adapter = resolveAdapter(host);\n const skillsDir = adapter.skillsDir?.({ root });\n if (skillsDir === undefined) {\n // N1: standardized wording — same phrase across init/sync/create so logs\n // grep uniformly. (Pre-N1 each command phrased this differently.)\n process.stderr.write(`host '${host}' has no skill emitter; skipping skills\\n`);\n return;\n }\n const target: CompileTarget = host;\n // Forward conflictPolicy + onConflict + interactive so an\n // interactive `noir init` with a conflicting skill emit consults the\n // resolver (clack menu + diff preview + apply-to-all); --json/--no-input\n // stays prompt-free via the `interactive: false` guard. The resolver shape\n // is structurally compatible with the skill emit seam (see skills/types.ts);\n // the cast isolates the literal-narrowing mismatch on the return type.\n type SkillEmitOpts = NonNullable<Parameters<typeof emitSkillsToDir>[1]>;\n const skillOpts: SkillEmitOpts = {\n includeIntegrations: true,\n target,\n conflictPolicy: conflictOpts.conflictPolicy,\n interactive,\n };\n if (conflictOpts.onConflict !== undefined) {\n skillOpts.onConflict = conflictOpts.onConflict as SkillEmitOpts['onConflict'];\n }\n const summary = await emitSkillsToDir(skillsDir, skillOpts);\n const relDir = skillsDir.replace(`${root}/`, '');\n process.stderr.write(\n `Emitted ${summary.emitted.length} Noir skills to ${relDir}/ (target: ${target}).\\n`,\n );\n}\n\n/**\n * Transport + URL precondition. The error strings here are the SECURITY GATE\n * locked by `url-validation.test.ts` — do not rephrase. Thrown errors are\n * plain `Error`s (bin.ts's `main().catch` maps them to stderr + exitCode 1).\n *\n * Exported so `noir create` (commands/create.ts) reuses the SAME gate instead\n * of duplicating the localhost allowlist.\n */\nexport function assertTransportUrl(opts: {\n transport: 'stdio' | 'streamable-http';\n url?: string;\n}): void {\n if (opts.transport === 'streamable-http' && opts.url === undefined) {\n throw new Error('--transport streamable-http requires --url');\n }\n if (opts.url !== undefined) {\n assertLocalhostUrl(opts.url);\n }\n}\n\n// Validate a streamable-http --url is http(s) and localhost-only. Gate 2's\n// daemon binds 127.0.0.1, so persisting a non-localhost URL is a footgun.\n// Not exported: the single external surface is assertTransportUrl(); thrown\n// errors propagate to bin.ts's main().catch (stderr + exitCode 1).\nfunction assertLocalhostUrl(raw: string): void {\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new Error(`Invalid URL: ${raw}`);\n }\n if (!['http:', 'https:'].includes(url.protocol)) {\n throw new Error('Only http/https URLs are supported');\n }\n if (!['localhost', '127.0.0.1', '::1'].includes(url.hostname)) {\n throw new Error(`Only localhost URLs are supported (got ${url.hostname})`);\n }\n}\n"],"mappings":"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","names":["c","scaffold","readFileSync","join","join","readFileSync","c","resolveAdapter","loadProjectInfo","loadProjectInfo","resolveAdapter"]}
@@ -22,7 +22,9 @@ var c = {
22
22
  /** Dim — secondary text, table borders when color is on. */
23
23
  dim: (s) => useColor() ? FORCE.dim(s) : s,
24
24
  /** Bold — emphasis (table headers compose this with `info`). */
25
- bold: (s) => useColor() ? FORCE.bold(s) : s
25
+ bold: (s) => useColor() ? FORCE.bold(s) : s,
26
+ /** Reverse video (SGR 7) — inverted background for the active list row. */
27
+ inverse: (s) => useColor() ? FORCE.inverse(s) : s
26
28
  };
27
29
  var BADGE_SYMBOL = {
28
30
  ok: "\u2713",
@@ -364,4 +366,4 @@ export {
364
366
  inferExitCode,
365
367
  handleError
366
368
  };
367
- //# sourceMappingURL=chunk-S2PDLS4C.js.map
369
+ //# sourceMappingURL=chunk-I2R55ACD.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/output.ts","../src/theme.ts"],"sourcesContent":["// S9 t2 — centralized output + exit-code infrastructure for `@noir-ai/cli`.\n//\n// Stream discipline (S9): machine-readable data → stdout; every other\n// diagnostic (progress, warnings, errors, tables, help) → stderr. Under `--json`\n// the decorated stderr helpers are silenced so stdout stays pristine JSON, and\n// `fail()` emits a structured `{ok:false,error}` envelope to stdout instead.\n//\n// Decoration (picocolors / cli-table3 / ora) auto-disables under `--json`,\n// `--quiet`, `CI`, `NO_COLOR`, or a non-TTY, so the same code path is safe in an\n// interactive shell, a pipe, and CI.\n//\n// Exit codes (S9): 0 ok · 1 error · 2 usage · 3 not-found · 4 daemon-down ·\n// 5 cancelled. These constants live HERE (the single source of truth); bin.ts\n// re-exports them so existing imports from `./bin.js` keep working.\n\nimport Table from 'cli-table3';\nimport { CommanderError } from 'commander';\nimport ora, { type Ora } from 'ora';\nimport { c, isCiEnv, terminalWidth } from './theme.js';\n\n// ---------------------------------------------------------------------------\n// Exit-code contract + error types\n// ---------------------------------------------------------------------------\nexport const EXIT = {\n OK: 0,\n ERROR: 1,\n USAGE: 2,\n NOT_FOUND: 3,\n DAEMON_DOWN: 4,\n CANCELLED: 5,\n} as const;\n\nexport type ExitCode = (typeof EXIT)[keyof typeof EXIT];\n\n/**\n * Application-level CLI error carrying an S9 exit code. Thrown directly by\n * bin.ts for legacy usage paths; `fail()` is the ergonomic throw helper.\n * `inferExitCode` / `handleError` map it onto `process.exitCode`.\n */\nexport class NoirCliError extends Error {\n readonly exitCode: number;\n constructor(exitCode: number, message: string) {\n super(message);\n this.name = 'NoirCliError';\n this.exitCode = exitCode;\n }\n}\n\n/**\n * The `code` stamped onto every CommanderError our own `fail()` throws, so\n * `inferExitCode` can tell \"we threw this on purpose with an authoritative\n * exitCode\" apart from \"commander threw this for help/usage/unknown-command\"\n * (which must be remapped onto the S9 contract by `commanderExitCode`).\n */\nconst NOIR_ERROR_CODE = 'noir.error';\n\n// ---------------------------------------------------------------------------\n// Global-option shape. Commander stores `--no-input` under the attribute\n// `input` (default `true`; the flag sets it `false`) — see commander's\n// `Option.attributeName()` which strips the leading `no-`. Both `input`\n// (commander's real key) and `noInput` (the spec's intent) are accepted so\n// callers may pass either commander's raw globals or a hand-built object.\n// ---------------------------------------------------------------------------\nexport interface CliOptions {\n readonly json?: boolean;\n readonly quiet?: boolean;\n readonly verbose?: boolean;\n /** Commander's storage for `--no-input` (`false` ⇒ no input). */\n readonly input?: boolean;\n /** Convenience alias matching the `--no-input` intent (`true` ⇒ no input). */\n readonly noInput?: boolean;\n /** TUI-policy flag. `false` (`--no-tui`) forces bare `noir` onto the\n * non-interactive `status` path even in a TTY; `true` (`--tui`) is an\n * advisory hint that still requires a TTY; absent (auto) defers to\n * {@link isInteractive}. Advisory only — never hard-gates a subcommand. */\n readonly tui?: boolean;\n /** Hint-suppression flag (`--no-tips`). When `true`, redirect /\n * deprecation hints (and any other {@link tip} output) are silenced for\n * CI / log-friendly runs. */\n readonly noTips?: boolean;\n}\n\nfunction isJsonMode(opts: CliOptions): boolean {\n return opts.json === true;\n}\n\nfunction isQuietMode(opts: CliOptions): boolean {\n return opts.quiet === true;\n}\n\n/** True when the user has asked for no interactive input (either spelling). */\nexport function isNoInput(opts: CliOptions = {}): boolean {\n return opts.noInput === true || opts.input === false;\n}\n\nfunction envFlagSet(name: string): boolean {\n // NO_COLOR spec: present AND non-empty (regardless of value) disables color.\n const v = process.env[name];\n return v !== undefined && v !== '';\n}\n\n// `isCiEnv` is imported from ./theme.js (the color authority) — see note there.\n\n// ---------------------------------------------------------------------------\n// Interactivity gate. Drives both the @clack home menu and the\n// decoration of every helper below. Requires BOTH stdin and stdout to be TTYs:\n// @clack reads keypresses from stdin while ora/picocolors render to stdout.\n// ---------------------------------------------------------------------------\nexport function isInteractive(opts: CliOptions = {}): boolean {\n if (isJsonMode(opts) || isNoInput(opts)) return false;\n if (isCiEnv() || envFlagSet('NO_COLOR')) return false;\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n}\n\n/**\n * Derive the explicit `interactive` flag the scaffold engine reads (via\n * {@link ScaffoldOptions.interactive}) from the bridge bin.ts owns. The bin's\n * `preAction` sets `NOIR_NON_INTERACTIVE=1` under `--json`/`--no-input`; this\n * helper folds that with the {@link isInteractive} TTY/CI/NO_COLOR gate so a\n * single boolean flows into the engine + `buildConflictOpts`. The ENGINE itself\n * never reads `process.env` for interactivity — only this CLI helper does.\n */\nexport function resolveInteractive(): boolean {\n if (flaggedNonInteractiveCli()) return false;\n return isInteractive();\n}\n\n/** True when the bin's `preAction` flagged this invocation non-interactive\n * (`--json` / `--no-input` → `NOIR_NON_INTERACTIVE`). CLI-internal; the engine\n * reads {@link ScaffoldOptions.interactive} instead. */\nfunction flaggedNonInteractiveCli(): boolean {\n const v = process.env.NOIR_NON_INTERACTIVE;\n return v !== undefined && v !== '';\n}\n\n// ---------------------------------------------------------------------------\n// Data output (stdout). The ONLY helper here that writes to stdout; under\n// `--json` a command calls this exactly once with its full payload and never\n// mixes other stdout writes, keeping machine output pristine.\n// ---------------------------------------------------------------------------\nexport function json(obj: unknown): void {\n process.stdout.write(`${JSON.stringify(obj)}\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Human diagnostics (stderr). picocolors auto-strips ANSI under NO_COLOR /\n// non-TTY, and each helper additionally short-circuits under `--json`\n// (decoration off, payload is the output) — and `info`/`log`/`success` also\n// under `--quiet`. `warn`/`error` survive `--quiet` (they carry signal).\n// ---------------------------------------------------------------------------\nexport function log(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts) || isQuietMode(opts)) return;\n process.stderr.write(`${msg}\\n`);\n}\n\nexport function info(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts) || isQuietMode(opts)) return;\n process.stderr.write(`${c.info(msg)}\\n`);\n}\n\nexport function success(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts) || isQuietMode(opts)) return;\n process.stderr.write(`${c.ok(msg)}\\n`);\n}\n\nexport function warn(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts)) return;\n process.stderr.write(`${c.warn(msg)}\\n`);\n}\n\nexport function error(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts)) return;\n process.stderr.write(`${c.error(msg)}\\n`);\n}\n\n/**\n * Redirect / deprecation hint (stderr). Silenced under `--json` (a CI\n * consumer's stdout envelope must stay pristine) AND under `--no-tips`\n * ({@link CliOptions.noTips}). This is the ONLY helper deprecation / redirect\n * notices should use, so a single `--no-tips` flag quiets them all in CI / logs.\n * Style matches {@link warn} (these are advisory, not errors).\n */\nexport function tip(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts) || opts.noTips === true) return;\n process.stderr.write(`${c.warn(msg)}\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Tables (stderr). Suppressed entirely under `--json` — the command has\n// already emitted the rows as a JSON array via `json()`.\n//\n// Design: cli-table3 styles headers/borders via `@colors/colors`\n// (whose NO_COLOR/TTY semantics differ from picocolors), which painted EVERY\n// header red while the body stripped — RED is now reserved strictly for ERROR.\n// We bypass `@colors/colors` entirely by passing EMPTY `style.head`/`style.border`\n// arrays (cli-table3's `wrapWithStyleColors` early-returns on length 0) and\n// PRE-COLOR the header strings ourselves via `theme.c` (picocolors). Result:\n// the header, border, and body all strip consistently under NO_COLOR / non-TTY,\n// because picocolors is the SOLE color authority. Responsive `colWidths` are\n// computed from `terminalWidth()` so tables never overflow; `wordWrap` keeps\n// free-text cells readable, and `truncate: '…'` caps any single over-long token.\n// ---------------------------------------------------------------------------\nexport function table(\n rows: readonly Record<string, unknown>[],\n cols: readonly string[],\n opts: CliOptions = {},\n): void {\n if (isJsonMode(opts)) return;\n if (rows.length === 0) {\n info('(no rows)', opts);\n return;\n }\n const t = new Table({\n head: cols.map((col) => c.bold(c.info(col))),\n // Empty arrays ⇒ cli-table3 applies NO @colors/colors wrap (neither the\n // default red header nor a colored border). All color is ours, via theme.\n style: { head: [], border: [] },\n colWidths: computeColWidths(rows, cols),\n wordWrap: true,\n truncate: '…',\n });\n for (const row of rows) {\n t.push(cols.map((col) => formatCell(row[col])));\n }\n process.stderr.write(`${t.toString()}\\n`);\n}\n\n/**\n * Compute per-column widths (cli-table3 `colWidths`, which INCLUDE each column's\n * 2 padding chars) that fit `terminalWidth()`. Strategy:\n *\n * 1. Measure each column's NATURAL content width (longest cell, at least the\n * header label so a header never wraps/truncates).\n * 2. If the natural total fits, use it as-is — every cell renders whole.\n * 3. Otherwise GREEDILY TRIM ONLY THE WIDEST column (down to its header width)\n * until the row fits. Narrow columns (paths, ids, statuses) keep their full\n * width — they're single tokens that can't word-wrap, so truncating them\n * loses information. The widest column is the free-text one (descriptions,\n * details, snippets); it has spaces and absorbs the shrink via `wordWrap`.\n *\n * cli-table3 accepts only positive integers (negatives collapse). Each returned\n * width is `content + 2` to reserve the column's padding, so the content area\n * equals the measured width and the math against `terminalWidth()` is exact.\n */\nfunction computeColWidths(\n rows: readonly Record<string, unknown>[],\n cols: readonly string[],\n): number[] {\n const n = cols.length;\n if (n === 0) return [];\n const headerLen = cols.map((col) => col.length);\n const natural = cols.map((col, i) => {\n let max = Math.max(headerLen[i] ?? col.length, 3);\n for (const row of rows) {\n const len = formatCell(row[col]).length;\n if (len > max) max = len;\n }\n return max;\n });\n // cli-table3 row overhead: per column 1 left border + 2 padding, plus a final\n // right border ⇒ 3n + 1 non-content chars. `contentBudget` is what remains for\n // the sum of column CONTENT areas.\n const contentBudget = Math.max(n * 4, terminalWidth() - 3 * n - 1);\n const naturalSum = natural.reduce((a, b) => a + b, 0);\n if (naturalSum <= contentBudget) {\n return natural.map((w) => w + 2);\n }\n // Overflow: greedily cut the widest reducible column (≥ its header width).\n const content = natural.slice();\n let sum = naturalSum;\n const floor = headerLen.map((h) => Math.max(h, 3));\n while (sum > contentBudget) {\n let idx = -1;\n let widest = -1;\n for (let i = 0; i < n; i++) {\n const c = content[i] ?? 0;\n const f = floor[i] ?? 3;\n if (c > f && c > widest) {\n widest = c;\n idx = i;\n }\n }\n if (idx === -1) break; // every column is at its header floor — stop\n const overshoot = sum - contentBudget;\n const reducible = (content[idx] ?? 0) - (floor[idx] ?? 3);\n const cut = Math.min(overshoot, reducible);\n content[idx] = (content[idx] ?? 0) - cut;\n sum -= cut;\n }\n return content.map((w) => w + 2);\n}\n\nfunction formatCell(value: unknown): string {\n if (value === undefined || value === null) return '';\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value);\n }\n return JSON.stringify(value);\n}\n\n// ---------------------------------------------------------------------------\n// Definition list (stderr). A two-column Field/Value rendering for snapshot-\n// shaped output (status / task / context-status). `status.ts` hand-rolled this\n// exact shape; this generalizes it. Rendered via the same responsive `table()`,\n// so it inherits the clean (non-red) header + NO_COLOR behavior automatically.\n// ---------------------------------------------------------------------------\nexport interface DefinitionRow {\n /** Field label (left column). */\n label: string;\n /** Field value; coerced to a string the same way table cells are. */\n value: unknown;\n}\n\nexport function definitionList(rows: readonly DefinitionRow[], opts: CliOptions = {}): void {\n table(\n rows.map((r) => ({ Field: r.label, Value: formatCell(r.value) })),\n ['Field', 'Value'],\n opts,\n );\n}\n\n/** Render a single `label: value` line to stderr (human only). */\nexport function kv(label: string, value: unknown, opts: CliOptions = {}): void {\n log(`${c.bold(label)}: ${formatCell(value)}`, opts);\n}\n\n// ---------------------------------------------------------------------------\n// Spinner (stderr). ora when interactive; a no-op otherwise so scriptable /\n// CI / `--json` / `--quiet` runs pay nothing and never animate a pipe.\n// ---------------------------------------------------------------------------\nexport interface Spinner {\n start(text?: string): Spinner;\n stop(): Spinner;\n clear(): Spinner;\n succeed(text?: string): void;\n fail(text?: string): void;\n warn(text?: string): void;\n info(text?: string): void;\n text: string;\n}\n\nclass NoopSpinner implements Spinner {\n text = '';\n start(_text?: string): Spinner {\n return this;\n }\n stop(): Spinner {\n return this;\n }\n clear(): Spinner {\n return this;\n }\n succeed(_text?: string): void {}\n fail(_text?: string): void {}\n warn(_text?: string): void {}\n info(_text?: string): void {}\n}\n\n/** Wraps an `ora` instance so the `Spinner` interface stays decoupled from ora's exact type. */\nclass OraSpinner implements Spinner {\n private readonly handle: Ora;\n\n constructor(initialText: string) {\n this.handle = ora(initialText);\n }\n\n get text(): string {\n return this.handle.text;\n }\n set text(value: string) {\n this.handle.text = value;\n }\n\n start(text?: string): Spinner {\n if (text !== undefined) this.handle.text = text;\n this.handle.start();\n return this;\n }\n\n stop(): Spinner {\n this.handle.stop();\n return this;\n }\n\n clear(): Spinner {\n this.handle.clear();\n return this;\n }\n\n succeed(text?: string): void {\n this.handle.succeed(text);\n }\n\n fail(text?: string): void {\n this.handle.fail(text);\n }\n\n warn(text?: string): void {\n this.handle.warn(text);\n }\n\n info(text?: string): void {\n this.handle.info(text);\n }\n}\n\nexport function spinner(text = '', opts: CliOptions = {}): Spinner {\n if (isJsonMode(opts) || isQuietMode(opts) || !isInteractive(opts)) {\n return new NoopSpinner();\n }\n return new OraSpinner(text);\n}\n\n// ---------------------------------------------------------------------------\n// Failure + exit-code mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Write a diagnostic and throw a `CommanderError` carrying the S9 exit code,\n * so bin.ts's `exitOverride` + `handleError` surface it as `process.exitCode`\n * without any mid-action `process.exit`. Under `--json` the message becomes a\n * structured `{ok:false,error}` envelope on STDOUT; otherwise plain text on\n * STDERR. Always throws (`never`).\n */\nexport function fail(exitCode: number, message: string, opts: CliOptions = {}): never {\n if (isJsonMode(opts)) {\n process.stdout.write(`${JSON.stringify({ ok: false, error: { code: exitCode, message } })}\\n`);\n } else if (message.length > 0) {\n process.stderr.write(`${message}\\n`);\n }\n throw new CommanderError(exitCode, NOIR_ERROR_CODE, message);\n}\n\n/**\n * Guard for commands that would otherwise block on a prompt. Honors the S9\n * hard rule: \"no blocking prompts when !isTTY or --no-input — error exit 2\n * naming the missing flag instead\". `hint` describes the operation so the\n * message points the user at the flag-based alternative.\n */\nexport function requireInteractive(opts: CliOptions, hint: string): void {\n if (!isInteractive(opts)) {\n fail(\n EXIT.USAGE,\n `${hint} needs an interactive terminal, but input is disabled (non-TTY, --no-input, --json, CI, or NO_COLOR). Re-run in a TTY or supply the value as a flag.`,\n opts,\n );\n }\n}\n\n// Remap commander's own exit codes onto the S9 contract:\n// help/version → 0 · unknown command → 3 · every other usage error → 2\nfunction commanderExitCode(err: CommanderError): number {\n switch (err.code) {\n case 'commander.helpDisplayed':\n case 'commander.versionDisplayed':\n case 'commander.version': // commander v12 uses this code for --version (exit 0)\n return EXIT.OK;\n case 'commander.unknownCommand':\n return EXIT.NOT_FOUND;\n default:\n return EXIT.USAGE;\n }\n}\n\n/** Map any thrown value onto the S9 exit-code contract. */\nexport function inferExitCode(err: unknown): number {\n if (err instanceof NoirCliError) return err.exitCode;\n if (err instanceof CommanderError) {\n // Errors our own `fail()` threw carry a `noir.*` code and an authoritative\n // exitCode — respect it. Commander's own errors are remapped by `code`.\n if (err.code.startsWith('noir.')) return err.exitCode;\n return commanderExitCode(err);\n }\n return EXIT.ERROR;\n}\n\n/**\n * Map a thrown error onto stderr + `process.exitCode`. Never throws, never\n * calls `process.exit` (commander's `exitOverride` already prevented that for\n * commander's own errors). Used by the bin entry's `run()`.\n */\nexport function handleError(err: unknown): void {\n if (err instanceof NoirCliError) {\n if (err.message.length > 0) process.stderr.write(`${err.message}\\n`);\n } else if (err instanceof CommanderError) {\n // If `fail()` threw it, the message is already written (stderr or the JSON\n // stdout envelope); if commander threw it, `configureOutput.writeErr`\n // already wrote it during parse. Don't double-write either way.\n } else {\n process.stderr.write(`noir: ${err instanceof Error ? err.message : String(err)}\\n`);\n }\n process.exitCode = inferExitCode(err);\n}\n","// Single owner of the Noir CLI's semantic palette + status badges.\n//\n// Built on `picocolors` (the SAME lib the rest of the CLI already uses), so the\n// decoration strips consistently under NO_COLOR / non-TTY. cli-table3 styles\n// via a SECOND lib (@colors/colors) whose NO_COLOR/TTY semantics differ — that\n// split is what painted every table header red while the body stripped. Routing\n// ALL color through this module (the `c` object + `badge()`) closes that leak:\n// there is exactly one color authority, and it honors NO_COLOR / CLICOLOR_FORCE\n// / TTY uniformly.\n//\n// Accessibility:\n// - `badge()` ALWAYS returns SYMBOL + TEXT LABEL (e.g. `⚠ warn`), so NO_COLOR\n// and colorblind users get identical information. Color is decorative.\n// - `NOIR_ACCESSIBLE` disables the banner gradient (→ solid accent) and is the\n// documented opt-in for users who want the most legible form; badges already\n// carry symbol+text unconditionally, so the flag is a no-op for them.\n\nimport pc from 'picocolors';\n\n// A private always-on colorizer. The PUBLIC `c` object gates every call through\n// `useColor()`, so `useColor()` is the single live authority over whether ANY\n// ANSI leaves this module. (The default `picocolors` export captures its\n// `isColorSupported` snapshot once at module load and does NOT honor\n// CLICOLOR_FORCE; gating a forced instance here fixes both.)\nconst FORCE = pc.createColors(true);\n\n/** Badge state → semantic color role. */\nexport type BadgeState = 'ok' | 'warn' | 'error' | 'info';\n\n/**\n * The semantic palette. Each method returns its input unchanged when color is\n * off (NO_COLOR / non-TTY / not forced), so callers can wrap freely without\n * guarding. Compose with `c.bold(c.info(s))` etc.\n */\nexport const c = {\n /** Green — success / healthy. */\n ok: (s: string): string => (useColor() ? FORCE.green(s) : s),\n /** Yellow — degraded but usable. */\n warn: (s: string): string => (useColor() ? FORCE.yellow(s) : s),\n /** Red — reserved strictly for ERROR. Never a table header. */\n error: (s: string): string => (useColor() ? FORCE.red(s) : s),\n /** Cyan — informational / brand-neutral accent for labels. */\n info: (s: string): string => (useColor() ? FORCE.cyan(s) : s),\n /** Blue — the Midnight Cobalt brand role (banner / accent). */\n accent: (s: string): string => (useColor() ? FORCE.blue(s) : s),\n /** Dim — secondary text, table borders when color is on. */\n dim: (s: string): string => (useColor() ? FORCE.dim(s) : s),\n /** Bold — emphasis (table headers compose this with `info`). */\n bold: (s: string): string => (useColor() ? FORCE.bold(s) : s),\n};\n\n// @clack-style status symbols (the vocabulary Noir inherits via @clack/prompts\n// + ora). Paired 1:1 with a state name so a screen reader / pipe / colorblind\n// user reads the same fact a sighted interactive user sees.\nconst BADGE_SYMBOL: Readonly<Record<BadgeState, string>> = {\n ok: '✓',\n warn: '⚠',\n error: '✗',\n info: 'ℹ',\n};\n\n/**\n * Render a status badge: always `SYMBOL LABEL`, colored by `state` when color\n * is on. The TEXT LABEL is unconditional — this is the accessibility invariant.\n * `label` defaults to the state name, so `badge('ok')` reads `✓ ok`.\n *\n * @example badge('ok') // → green \"✓ ok\"\n * @example badge('warn','degraded') // → yellow \"⚠ degraded\"\n * @example badge('error','down') // → red \"✗ down\"\n */\nexport function badge(state: BadgeState, label: string = state): string {\n const text = `${BADGE_SYMBOL[state]} ${label}`;\n switch (state) {\n case 'ok':\n return c.ok(text);\n case 'warn':\n return c.warn(text);\n case 'error':\n return c.error(text);\n case 'info':\n return c.info(text);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Environment gates. Evaluated LIVE (not captured at module load) so tests that\n// flip NO_COLOR / CLICOLOR_FORCE between cases see the right answer, and so a\n// user exporting NO_COLOR mid-session is honored on the next render.\n// ---------------------------------------------------------------------------\n\nfunction envFlagSet(name: string): boolean {\n // NO_COLOR spec: present AND non-empty disables color (any value).\n const v = process.env[name];\n return v !== undefined && v !== '';\n}\n\n/**\n * True when running under a CI runner (GitHub Actions / GitLab / CircleCI /\n * Drone set `CI=true` at the conventional value). Treats the explicit opt-outs\n * `0` / `false` as \"not CI\" so a user can force-disable the guard. Owned here\n * (the color authority) and re-exported so output.ts doesn't duplicate it.\n */\nexport function isCiEnv(): boolean {\n const v = process.env.CI;\n if (v === undefined || v === '') return false;\n return v !== '0' && v !== 'false';\n}\n\n/**\n * The single authority on whether decoration should emit ANSI. Honors:\n * - `NO_COLOR` → always off (spec: present + non-empty).\n * - `CLICOLOR_FORCE=1` → always on (forces color on a redirected stream).\n * - `CI=true` (alone) → always OFF. picocolors' `isColorSupported`\n * snapshot turns color ON under `!!env.CI` (it\n * assumes a CI viewer that renders ANSI). But ANSI\n * escapes inflate the raw byte length of rendered\n * lines, which breaks the responsive-width\n * guarantee `max(line.length) <= terminalWidth()`\n * that `table()` exists to uphold — and the\n * regression test that locks it (fits 60/80/120 in\n * theme.test.ts). The CLI's own top-of-file\n * contract states decoration auto-disables under\n * CI / non-TTY; this honors that. A CI viewer that\n * wants color sets `CLICOLOR_FORCE=1` (above).\n * - otherwise → picocolors' detection (TTY / FORCE_COLOR).\n */\nexport function useColor(): boolean {\n if (envFlagSet('NO_COLOR')) return false;\n if (process.env.CLICOLOR_FORCE === '1') return true;\n if (isCiEnv()) return false;\n return pc.isColorSupported;\n}\n\n/**\n * `NOIR_ACCESSIBLE`: opt-in maximum legibility. Badges already carry symbol+\n * text unconditionally (so the flag is a no-op for them); the material effect\n * is disabling the banner gradient (→ solid accent) and is reserved here for\n * future accessible-display tweaks.\n */\nexport function accessibleMode(): boolean {\n return envFlagSet('NOIR_ACCESSIBLE');\n}\n\n/**\n * Terminal width in columns. Honors `COLUMNS` (the conventional override) then\n * `process.stdout.columns`, defaulting to 80 when neither is set (the same\n * default `picocolors` / `cli-table3` assume). Floored at a small minimum so a\n * misconfigured shell never produces a degenerate zero/negative-width table.\n */\nexport function terminalWidth(): number {\n const fromEnv = process.env.COLUMNS;\n if (fromEnv !== undefined && fromEnv !== '') {\n const n = Number(fromEnv);\n if (Number.isInteger(n) && n > 0) return Math.max(n, 20);\n }\n const cols = process.stdout.columns;\n if (typeof cols === 'number' && cols > 0) return Math.max(cols, 20);\n return 80;\n}\n\n/**\n * Usable content width inside a bordered TUI panel. Ink's `borderStyle` adds a\n * 1-char border on each side, and `paddingX={1}` adds 1 char inside that — so a\n * full-width rounded panel has `terminalWidth - 4` columns of usable text.\n * Components that truncate long lines (OutputPane) MUST use this, not\n * {@link terminalWidth}, or content overflows the border.\n *\n * @param border chars consumed by the border (default 2 = left + right).\n * @param padding chars consumed by inner padding (default 2 = left + right).\n */\nexport function contentWidth(border = 2, padding = 2): number {\n return Math.max(20, terminalWidth() - border - padding);\n}\n\n/**\n * A horizontal divider line of `─` sized for the current content width. Used\n * inside bordered panels to separate regions (e.g. status bar ↔ output ↔ input)\n * without nesting Ink borders. Always dim so it reads as structure, not content.\n */\nexport function divider(): string {\n return c.dim('─'.repeat(Math.max(1, contentWidth())));\n}\n"],"mappings":"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","names":["envFlagSet","c"]}
1
+ {"version":3,"sources":["../src/output.ts","../src/theme.ts"],"sourcesContent":["// S9 t2 — centralized output + exit-code infrastructure for `@noir-ai/cli`.\n//\n// Stream discipline (S9): machine-readable data → stdout; every other\n// diagnostic (progress, warnings, errors, tables, help) → stderr. Under `--json`\n// the decorated stderr helpers are silenced so stdout stays pristine JSON, and\n// `fail()` emits a structured `{ok:false,error}` envelope to stdout instead.\n//\n// Decoration (picocolors / cli-table3 / ora) auto-disables under `--json`,\n// `--quiet`, `CI`, `NO_COLOR`, or a non-TTY, so the same code path is safe in an\n// interactive shell, a pipe, and CI.\n//\n// Exit codes (S9): 0 ok · 1 error · 2 usage · 3 not-found · 4 daemon-down ·\n// 5 cancelled. These constants live HERE (the single source of truth); bin.ts\n// re-exports them so existing imports from `./bin.js` keep working.\n\nimport Table from 'cli-table3';\nimport { CommanderError } from 'commander';\nimport ora, { type Ora } from 'ora';\nimport { c, isCiEnv, terminalWidth } from './theme.js';\n\n// ---------------------------------------------------------------------------\n// Exit-code contract + error types\n// ---------------------------------------------------------------------------\nexport const EXIT = {\n OK: 0,\n ERROR: 1,\n USAGE: 2,\n NOT_FOUND: 3,\n DAEMON_DOWN: 4,\n CANCELLED: 5,\n} as const;\n\nexport type ExitCode = (typeof EXIT)[keyof typeof EXIT];\n\n/**\n * Application-level CLI error carrying an S9 exit code. Thrown directly by\n * bin.ts for legacy usage paths; `fail()` is the ergonomic throw helper.\n * `inferExitCode` / `handleError` map it onto `process.exitCode`.\n */\nexport class NoirCliError extends Error {\n readonly exitCode: number;\n constructor(exitCode: number, message: string) {\n super(message);\n this.name = 'NoirCliError';\n this.exitCode = exitCode;\n }\n}\n\n/**\n * The `code` stamped onto every CommanderError our own `fail()` throws, so\n * `inferExitCode` can tell \"we threw this on purpose with an authoritative\n * exitCode\" apart from \"commander threw this for help/usage/unknown-command\"\n * (which must be remapped onto the S9 contract by `commanderExitCode`).\n */\nconst NOIR_ERROR_CODE = 'noir.error';\n\n// ---------------------------------------------------------------------------\n// Global-option shape. Commander stores `--no-input` under the attribute\n// `input` (default `true`; the flag sets it `false`) — see commander's\n// `Option.attributeName()` which strips the leading `no-`. Both `input`\n// (commander's real key) and `noInput` (the spec's intent) are accepted so\n// callers may pass either commander's raw globals or a hand-built object.\n// ---------------------------------------------------------------------------\nexport interface CliOptions {\n readonly json?: boolean;\n readonly quiet?: boolean;\n readonly verbose?: boolean;\n /** Commander's storage for `--no-input` (`false` ⇒ no input). */\n readonly input?: boolean;\n /** Convenience alias matching the `--no-input` intent (`true` ⇒ no input). */\n readonly noInput?: boolean;\n /** TUI-policy flag. `false` (`--no-tui`) forces bare `noir` onto the\n * non-interactive `status` path even in a TTY; `true` (`--tui`) is an\n * advisory hint that still requires a TTY; absent (auto) defers to\n * {@link isInteractive}. Advisory only — never hard-gates a subcommand. */\n readonly tui?: boolean;\n /** Hint-suppression flag (`--no-tips`). When `true`, redirect /\n * deprecation hints (and any other {@link tip} output) are silenced for\n * CI / log-friendly runs. */\n readonly noTips?: boolean;\n}\n\nfunction isJsonMode(opts: CliOptions): boolean {\n return opts.json === true;\n}\n\nfunction isQuietMode(opts: CliOptions): boolean {\n return opts.quiet === true;\n}\n\n/** True when the user has asked for no interactive input (either spelling). */\nexport function isNoInput(opts: CliOptions = {}): boolean {\n return opts.noInput === true || opts.input === false;\n}\n\nfunction envFlagSet(name: string): boolean {\n // NO_COLOR spec: present AND non-empty (regardless of value) disables color.\n const v = process.env[name];\n return v !== undefined && v !== '';\n}\n\n// `isCiEnv` is imported from ./theme.js (the color authority) — see note there.\n\n// ---------------------------------------------------------------------------\n// Interactivity gate. Drives both the @clack home menu and the\n// decoration of every helper below. Requires BOTH stdin and stdout to be TTYs:\n// @clack reads keypresses from stdin while ora/picocolors render to stdout.\n// ---------------------------------------------------------------------------\nexport function isInteractive(opts: CliOptions = {}): boolean {\n if (isJsonMode(opts) || isNoInput(opts)) return false;\n if (isCiEnv() || envFlagSet('NO_COLOR')) return false;\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n}\n\n/**\n * Derive the explicit `interactive` flag the scaffold engine reads (via\n * {@link ScaffoldOptions.interactive}) from the bridge bin.ts owns. The bin's\n * `preAction` sets `NOIR_NON_INTERACTIVE=1` under `--json`/`--no-input`; this\n * helper folds that with the {@link isInteractive} TTY/CI/NO_COLOR gate so a\n * single boolean flows into the engine + `buildConflictOpts`. The ENGINE itself\n * never reads `process.env` for interactivity — only this CLI helper does.\n */\nexport function resolveInteractive(): boolean {\n if (flaggedNonInteractiveCli()) return false;\n return isInteractive();\n}\n\n/** True when the bin's `preAction` flagged this invocation non-interactive\n * (`--json` / `--no-input` → `NOIR_NON_INTERACTIVE`). CLI-internal; the engine\n * reads {@link ScaffoldOptions.interactive} instead. */\nfunction flaggedNonInteractiveCli(): boolean {\n const v = process.env.NOIR_NON_INTERACTIVE;\n return v !== undefined && v !== '';\n}\n\n// ---------------------------------------------------------------------------\n// Data output (stdout). The ONLY helper here that writes to stdout; under\n// `--json` a command calls this exactly once with its full payload and never\n// mixes other stdout writes, keeping machine output pristine.\n// ---------------------------------------------------------------------------\nexport function json(obj: unknown): void {\n process.stdout.write(`${JSON.stringify(obj)}\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Human diagnostics (stderr). picocolors auto-strips ANSI under NO_COLOR /\n// non-TTY, and each helper additionally short-circuits under `--json`\n// (decoration off, payload is the output) — and `info`/`log`/`success` also\n// under `--quiet`. `warn`/`error` survive `--quiet` (they carry signal).\n// ---------------------------------------------------------------------------\nexport function log(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts) || isQuietMode(opts)) return;\n process.stderr.write(`${msg}\\n`);\n}\n\nexport function info(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts) || isQuietMode(opts)) return;\n process.stderr.write(`${c.info(msg)}\\n`);\n}\n\nexport function success(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts) || isQuietMode(opts)) return;\n process.stderr.write(`${c.ok(msg)}\\n`);\n}\n\nexport function warn(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts)) return;\n process.stderr.write(`${c.warn(msg)}\\n`);\n}\n\nexport function error(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts)) return;\n process.stderr.write(`${c.error(msg)}\\n`);\n}\n\n/**\n * Redirect / deprecation hint (stderr). Silenced under `--json` (a CI\n * consumer's stdout envelope must stay pristine) AND under `--no-tips`\n * ({@link CliOptions.noTips}). This is the ONLY helper deprecation / redirect\n * notices should use, so a single `--no-tips` flag quiets them all in CI / logs.\n * Style matches {@link warn} (these are advisory, not errors).\n */\nexport function tip(msg: string, opts: CliOptions = {}): void {\n if (isJsonMode(opts) || opts.noTips === true) return;\n process.stderr.write(`${c.warn(msg)}\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Tables (stderr). Suppressed entirely under `--json` — the command has\n// already emitted the rows as a JSON array via `json()`.\n//\n// Design: cli-table3 styles headers/borders via `@colors/colors`\n// (whose NO_COLOR/TTY semantics differ from picocolors), which painted EVERY\n// header red while the body stripped — RED is now reserved strictly for ERROR.\n// We bypass `@colors/colors` entirely by passing EMPTY `style.head`/`style.border`\n// arrays (cli-table3's `wrapWithStyleColors` early-returns on length 0) and\n// PRE-COLOR the header strings ourselves via `theme.c` (picocolors). Result:\n// the header, border, and body all strip consistently under NO_COLOR / non-TTY,\n// because picocolors is the SOLE color authority. Responsive `colWidths` are\n// computed from `terminalWidth()` so tables never overflow; `wordWrap` keeps\n// free-text cells readable, and `truncate: '…'` caps any single over-long token.\n// ---------------------------------------------------------------------------\nexport function table(\n rows: readonly Record<string, unknown>[],\n cols: readonly string[],\n opts: CliOptions = {},\n): void {\n if (isJsonMode(opts)) return;\n if (rows.length === 0) {\n info('(no rows)', opts);\n return;\n }\n const t = new Table({\n head: cols.map((col) => c.bold(c.info(col))),\n // Empty arrays ⇒ cli-table3 applies NO @colors/colors wrap (neither the\n // default red header nor a colored border). All color is ours, via theme.\n style: { head: [], border: [] },\n colWidths: computeColWidths(rows, cols),\n wordWrap: true,\n truncate: '…',\n });\n for (const row of rows) {\n t.push(cols.map((col) => formatCell(row[col])));\n }\n process.stderr.write(`${t.toString()}\\n`);\n}\n\n/**\n * Compute per-column widths (cli-table3 `colWidths`, which INCLUDE each column's\n * 2 padding chars) that fit `terminalWidth()`. Strategy:\n *\n * 1. Measure each column's NATURAL content width (longest cell, at least the\n * header label so a header never wraps/truncates).\n * 2. If the natural total fits, use it as-is — every cell renders whole.\n * 3. Otherwise GREEDILY TRIM ONLY THE WIDEST column (down to its header width)\n * until the row fits. Narrow columns (paths, ids, statuses) keep their full\n * width — they're single tokens that can't word-wrap, so truncating them\n * loses information. The widest column is the free-text one (descriptions,\n * details, snippets); it has spaces and absorbs the shrink via `wordWrap`.\n *\n * cli-table3 accepts only positive integers (negatives collapse). Each returned\n * width is `content + 2` to reserve the column's padding, so the content area\n * equals the measured width and the math against `terminalWidth()` is exact.\n */\nfunction computeColWidths(\n rows: readonly Record<string, unknown>[],\n cols: readonly string[],\n): number[] {\n const n = cols.length;\n if (n === 0) return [];\n const headerLen = cols.map((col) => col.length);\n const natural = cols.map((col, i) => {\n let max = Math.max(headerLen[i] ?? col.length, 3);\n for (const row of rows) {\n const len = formatCell(row[col]).length;\n if (len > max) max = len;\n }\n return max;\n });\n // cli-table3 row overhead: per column 1 left border + 2 padding, plus a final\n // right border ⇒ 3n + 1 non-content chars. `contentBudget` is what remains for\n // the sum of column CONTENT areas.\n const contentBudget = Math.max(n * 4, terminalWidth() - 3 * n - 1);\n const naturalSum = natural.reduce((a, b) => a + b, 0);\n if (naturalSum <= contentBudget) {\n return natural.map((w) => w + 2);\n }\n // Overflow: greedily cut the widest reducible column (≥ its header width).\n const content = natural.slice();\n let sum = naturalSum;\n const floor = headerLen.map((h) => Math.max(h, 3));\n while (sum > contentBudget) {\n let idx = -1;\n let widest = -1;\n for (let i = 0; i < n; i++) {\n const c = content[i] ?? 0;\n const f = floor[i] ?? 3;\n if (c > f && c > widest) {\n widest = c;\n idx = i;\n }\n }\n if (idx === -1) break; // every column is at its header floor — stop\n const overshoot = sum - contentBudget;\n const reducible = (content[idx] ?? 0) - (floor[idx] ?? 3);\n const cut = Math.min(overshoot, reducible);\n content[idx] = (content[idx] ?? 0) - cut;\n sum -= cut;\n }\n return content.map((w) => w + 2);\n}\n\nfunction formatCell(value: unknown): string {\n if (value === undefined || value === null) return '';\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value);\n }\n return JSON.stringify(value);\n}\n\n// ---------------------------------------------------------------------------\n// Definition list (stderr). A two-column Field/Value rendering for snapshot-\n// shaped output (status / task / context-status). `status.ts` hand-rolled this\n// exact shape; this generalizes it. Rendered via the same responsive `table()`,\n// so it inherits the clean (non-red) header + NO_COLOR behavior automatically.\n// ---------------------------------------------------------------------------\nexport interface DefinitionRow {\n /** Field label (left column). */\n label: string;\n /** Field value; coerced to a string the same way table cells are. */\n value: unknown;\n}\n\nexport function definitionList(rows: readonly DefinitionRow[], opts: CliOptions = {}): void {\n table(\n rows.map((r) => ({ Field: r.label, Value: formatCell(r.value) })),\n ['Field', 'Value'],\n opts,\n );\n}\n\n/** Render a single `label: value` line to stderr (human only). */\nexport function kv(label: string, value: unknown, opts: CliOptions = {}): void {\n log(`${c.bold(label)}: ${formatCell(value)}`, opts);\n}\n\n// ---------------------------------------------------------------------------\n// Spinner (stderr). ora when interactive; a no-op otherwise so scriptable /\n// CI / `--json` / `--quiet` runs pay nothing and never animate a pipe.\n// ---------------------------------------------------------------------------\nexport interface Spinner {\n start(text?: string): Spinner;\n stop(): Spinner;\n clear(): Spinner;\n succeed(text?: string): void;\n fail(text?: string): void;\n warn(text?: string): void;\n info(text?: string): void;\n text: string;\n}\n\nclass NoopSpinner implements Spinner {\n text = '';\n start(_text?: string): Spinner {\n return this;\n }\n stop(): Spinner {\n return this;\n }\n clear(): Spinner {\n return this;\n }\n succeed(_text?: string): void {}\n fail(_text?: string): void {}\n warn(_text?: string): void {}\n info(_text?: string): void {}\n}\n\n/** Wraps an `ora` instance so the `Spinner` interface stays decoupled from ora's exact type. */\nclass OraSpinner implements Spinner {\n private readonly handle: Ora;\n\n constructor(initialText: string) {\n this.handle = ora(initialText);\n }\n\n get text(): string {\n return this.handle.text;\n }\n set text(value: string) {\n this.handle.text = value;\n }\n\n start(text?: string): Spinner {\n if (text !== undefined) this.handle.text = text;\n this.handle.start();\n return this;\n }\n\n stop(): Spinner {\n this.handle.stop();\n return this;\n }\n\n clear(): Spinner {\n this.handle.clear();\n return this;\n }\n\n succeed(text?: string): void {\n this.handle.succeed(text);\n }\n\n fail(text?: string): void {\n this.handle.fail(text);\n }\n\n warn(text?: string): void {\n this.handle.warn(text);\n }\n\n info(text?: string): void {\n this.handle.info(text);\n }\n}\n\nexport function spinner(text = '', opts: CliOptions = {}): Spinner {\n if (isJsonMode(opts) || isQuietMode(opts) || !isInteractive(opts)) {\n return new NoopSpinner();\n }\n return new OraSpinner(text);\n}\n\n// ---------------------------------------------------------------------------\n// Failure + exit-code mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Write a diagnostic and throw a `CommanderError` carrying the S9 exit code,\n * so bin.ts's `exitOverride` + `handleError` surface it as `process.exitCode`\n * without any mid-action `process.exit`. Under `--json` the message becomes a\n * structured `{ok:false,error}` envelope on STDOUT; otherwise plain text on\n * STDERR. Always throws (`never`).\n */\nexport function fail(exitCode: number, message: string, opts: CliOptions = {}): never {\n if (isJsonMode(opts)) {\n process.stdout.write(`${JSON.stringify({ ok: false, error: { code: exitCode, message } })}\\n`);\n } else if (message.length > 0) {\n process.stderr.write(`${message}\\n`);\n }\n throw new CommanderError(exitCode, NOIR_ERROR_CODE, message);\n}\n\n/**\n * Guard for commands that would otherwise block on a prompt. Honors the S9\n * hard rule: \"no blocking prompts when !isTTY or --no-input — error exit 2\n * naming the missing flag instead\". `hint` describes the operation so the\n * message points the user at the flag-based alternative.\n */\nexport function requireInteractive(opts: CliOptions, hint: string): void {\n if (!isInteractive(opts)) {\n fail(\n EXIT.USAGE,\n `${hint} needs an interactive terminal, but input is disabled (non-TTY, --no-input, --json, CI, or NO_COLOR). Re-run in a TTY or supply the value as a flag.`,\n opts,\n );\n }\n}\n\n// Remap commander's own exit codes onto the S9 contract:\n// help/version → 0 · unknown command → 3 · every other usage error → 2\nfunction commanderExitCode(err: CommanderError): number {\n switch (err.code) {\n case 'commander.helpDisplayed':\n case 'commander.versionDisplayed':\n case 'commander.version': // commander v12 uses this code for --version (exit 0)\n return EXIT.OK;\n case 'commander.unknownCommand':\n return EXIT.NOT_FOUND;\n default:\n return EXIT.USAGE;\n }\n}\n\n/** Map any thrown value onto the S9 exit-code contract. */\nexport function inferExitCode(err: unknown): number {\n if (err instanceof NoirCliError) return err.exitCode;\n if (err instanceof CommanderError) {\n // Errors our own `fail()` threw carry a `noir.*` code and an authoritative\n // exitCode — respect it. Commander's own errors are remapped by `code`.\n if (err.code.startsWith('noir.')) return err.exitCode;\n return commanderExitCode(err);\n }\n return EXIT.ERROR;\n}\n\n/**\n * Map a thrown error onto stderr + `process.exitCode`. Never throws, never\n * calls `process.exit` (commander's `exitOverride` already prevented that for\n * commander's own errors). Used by the bin entry's `run()`.\n */\nexport function handleError(err: unknown): void {\n if (err instanceof NoirCliError) {\n if (err.message.length > 0) process.stderr.write(`${err.message}\\n`);\n } else if (err instanceof CommanderError) {\n // If `fail()` threw it, the message is already written (stderr or the JSON\n // stdout envelope); if commander threw it, `configureOutput.writeErr`\n // already wrote it during parse. Don't double-write either way.\n } else {\n process.stderr.write(`noir: ${err instanceof Error ? err.message : String(err)}\\n`);\n }\n process.exitCode = inferExitCode(err);\n}\n","// Single owner of the Noir CLI's semantic palette + status badges.\n//\n// Built on `picocolors` (the SAME lib the rest of the CLI already uses), so the\n// decoration strips consistently under NO_COLOR / non-TTY. cli-table3 styles\n// via a SECOND lib (@colors/colors) whose NO_COLOR/TTY semantics differ — that\n// split is what painted every table header red while the body stripped. Routing\n// ALL color through this module (the `c` object + `badge()`) closes that leak:\n// there is exactly one color authority, and it honors NO_COLOR / CLICOLOR_FORCE\n// / TTY uniformly.\n//\n// Accessibility:\n// - `badge()` ALWAYS returns SYMBOL + TEXT LABEL (e.g. `⚠ warn`), so NO_COLOR\n// and colorblind users get identical information. Color is decorative.\n// - `NOIR_ACCESSIBLE` disables the banner gradient (→ solid accent) and is the\n// documented opt-in for users who want the most legible form; badges already\n// carry symbol+text unconditionally, so the flag is a no-op for them.\n\nimport pc from 'picocolors';\n\n// A private always-on colorizer. The PUBLIC `c` object gates every call through\n// `useColor()`, so `useColor()` is the single live authority over whether ANY\n// ANSI leaves this module. (The default `picocolors` export captures its\n// `isColorSupported` snapshot once at module load and does NOT honor\n// CLICOLOR_FORCE; gating a forced instance here fixes both.)\nconst FORCE = pc.createColors(true);\n\n/** Badge state → semantic color role. */\nexport type BadgeState = 'ok' | 'warn' | 'error' | 'info';\n\n/**\n * The semantic palette. Each method returns its input unchanged when color is\n * off (NO_COLOR / non-TTY / not forced), so callers can wrap freely without\n * guarding. Compose with `c.bold(c.info(s))` etc.\n */\nexport const c = {\n /** Green — success / healthy. */\n ok: (s: string): string => (useColor() ? FORCE.green(s) : s),\n /** Yellow — degraded but usable. */\n warn: (s: string): string => (useColor() ? FORCE.yellow(s) : s),\n /** Red — reserved strictly for ERROR. Never a table header. */\n error: (s: string): string => (useColor() ? FORCE.red(s) : s),\n /** Cyan — informational / brand-neutral accent for labels. */\n info: (s: string): string => (useColor() ? FORCE.cyan(s) : s),\n /** Blue — the Midnight Cobalt brand role (banner / accent). */\n accent: (s: string): string => (useColor() ? FORCE.blue(s) : s),\n /** Dim — secondary text, table borders when color is on. */\n dim: (s: string): string => (useColor() ? FORCE.dim(s) : s),\n /** Bold — emphasis (table headers compose this with `info`). */\n bold: (s: string): string => (useColor() ? FORCE.bold(s) : s),\n /** Reverse video (SGR 7) — inverted background for the active list row. */\n inverse: (s: string): string => (useColor() ? FORCE.inverse(s) : s),\n};\n\n// @clack-style status symbols (the vocabulary Noir inherits via @clack/prompts\n// + ora). Paired 1:1 with a state name so a screen reader / pipe / colorblind\n// user reads the same fact a sighted interactive user sees.\nconst BADGE_SYMBOL: Readonly<Record<BadgeState, string>> = {\n ok: '✓',\n warn: '⚠',\n error: '✗',\n info: 'ℹ',\n};\n\n/**\n * Render a status badge: always `SYMBOL LABEL`, colored by `state` when color\n * is on. The TEXT LABEL is unconditional — this is the accessibility invariant.\n * `label` defaults to the state name, so `badge('ok')` reads `✓ ok`.\n *\n * @example badge('ok') // → green \"✓ ok\"\n * @example badge('warn','degraded') // → yellow \"⚠ degraded\"\n * @example badge('error','down') // → red \"✗ down\"\n */\nexport function badge(state: BadgeState, label: string = state): string {\n const text = `${BADGE_SYMBOL[state]} ${label}`;\n switch (state) {\n case 'ok':\n return c.ok(text);\n case 'warn':\n return c.warn(text);\n case 'error':\n return c.error(text);\n case 'info':\n return c.info(text);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Environment gates. Evaluated LIVE (not captured at module load) so tests that\n// flip NO_COLOR / CLICOLOR_FORCE between cases see the right answer, and so a\n// user exporting NO_COLOR mid-session is honored on the next render.\n// ---------------------------------------------------------------------------\n\nfunction envFlagSet(name: string): boolean {\n // NO_COLOR spec: present AND non-empty disables color (any value).\n const v = process.env[name];\n return v !== undefined && v !== '';\n}\n\n/**\n * True when running under a CI runner (GitHub Actions / GitLab / CircleCI /\n * Drone set `CI=true` at the conventional value). Treats the explicit opt-outs\n * `0` / `false` as \"not CI\" so a user can force-disable the guard. Owned here\n * (the color authority) and re-exported so output.ts doesn't duplicate it.\n */\nexport function isCiEnv(): boolean {\n const v = process.env.CI;\n if (v === undefined || v === '') return false;\n return v !== '0' && v !== 'false';\n}\n\n/**\n * The single authority on whether decoration should emit ANSI. Honors:\n * - `NO_COLOR` → always off (spec: present + non-empty).\n * - `CLICOLOR_FORCE=1` → always on (forces color on a redirected stream).\n * - `CI=true` (alone) → always OFF. picocolors' `isColorSupported`\n * snapshot turns color ON under `!!env.CI` (it\n * assumes a CI viewer that renders ANSI). But ANSI\n * escapes inflate the raw byte length of rendered\n * lines, which breaks the responsive-width\n * guarantee `max(line.length) <= terminalWidth()`\n * that `table()` exists to uphold — and the\n * regression test that locks it (fits 60/80/120 in\n * theme.test.ts). The CLI's own top-of-file\n * contract states decoration auto-disables under\n * CI / non-TTY; this honors that. A CI viewer that\n * wants color sets `CLICOLOR_FORCE=1` (above).\n * - otherwise → picocolors' detection (TTY / FORCE_COLOR).\n */\nexport function useColor(): boolean {\n if (envFlagSet('NO_COLOR')) return false;\n if (process.env.CLICOLOR_FORCE === '1') return true;\n if (isCiEnv()) return false;\n return pc.isColorSupported;\n}\n\n/**\n * `NOIR_ACCESSIBLE`: opt-in maximum legibility. Badges already carry symbol+\n * text unconditionally (so the flag is a no-op for them); the material effect\n * is disabling the banner gradient (→ solid accent) and is reserved here for\n * future accessible-display tweaks.\n */\nexport function accessibleMode(): boolean {\n return envFlagSet('NOIR_ACCESSIBLE');\n}\n\n/**\n * Terminal width in columns. Honors `COLUMNS` (the conventional override) then\n * `process.stdout.columns`, defaulting to 80 when neither is set (the same\n * default `picocolors` / `cli-table3` assume). Floored at a small minimum so a\n * misconfigured shell never produces a degenerate zero/negative-width table.\n */\nexport function terminalWidth(): number {\n const fromEnv = process.env.COLUMNS;\n if (fromEnv !== undefined && fromEnv !== '') {\n const n = Number(fromEnv);\n if (Number.isInteger(n) && n > 0) return Math.max(n, 20);\n }\n const cols = process.stdout.columns;\n if (typeof cols === 'number' && cols > 0) return Math.max(cols, 20);\n return 80;\n}\n\n/**\n * Usable content width inside a bordered TUI panel. Ink's `borderStyle` adds a\n * 1-char border on each side, and `paddingX={1}` adds 1 char inside that — so a\n * full-width rounded panel has `terminalWidth - 4` columns of usable text.\n * Components that truncate long lines (OutputPane) MUST use this, not\n * {@link terminalWidth}, or content overflows the border.\n *\n * @param border chars consumed by the border (default 2 = left + right).\n * @param padding chars consumed by inner padding (default 2 = left + right).\n */\nexport function contentWidth(border = 2, padding = 2): number {\n return Math.max(20, terminalWidth() - border - padding);\n}\n\n/**\n * A horizontal divider line of `─` sized for the current content width. Used\n * inside bordered panels to separate regions (e.g. status bar ↔ output ↔ input)\n * without nesting Ink borders. Always dim so it reads as structure, not content.\n */\nexport function divider(): string {\n return c.dim('─'.repeat(Math.max(1, contentWidth())));\n}\n"],"mappings":";;;AAeA,OAAO,WAAW;AAClB,SAAS,sBAAsB;AAC/B,OAAO,SAAuB;;;ACA9B,OAAO,QAAQ;AAOf,IAAM,QAAQ,GAAG,aAAa,IAAI;AAU3B,IAAM,IAAI;AAAA;AAAA,EAEf,IAAI,CAAC,MAAuB,SAAS,IAAI,MAAM,MAAM,CAAC,IAAI;AAAA;AAAA,EAE1D,MAAM,CAAC,MAAuB,SAAS,IAAI,MAAM,OAAO,CAAC,IAAI;AAAA;AAAA,EAE7D,OAAO,CAAC,MAAuB,SAAS,IAAI,MAAM,IAAI,CAAC,IAAI;AAAA;AAAA,EAE3D,MAAM,CAAC,MAAuB,SAAS,IAAI,MAAM,KAAK,CAAC,IAAI;AAAA;AAAA,EAE3D,QAAQ,CAAC,MAAuB,SAAS,IAAI,MAAM,KAAK,CAAC,IAAI;AAAA;AAAA,EAE7D,KAAK,CAAC,MAAuB,SAAS,IAAI,MAAM,IAAI,CAAC,IAAI;AAAA;AAAA,EAEzD,MAAM,CAAC,MAAuB,SAAS,IAAI,MAAM,KAAK,CAAC,IAAI;AAAA;AAAA,EAE3D,SAAS,CAAC,MAAuB,SAAS,IAAI,MAAM,QAAQ,CAAC,IAAI;AACnE;AAKA,IAAM,eAAqD;AAAA,EACzD,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AAWO,SAAS,MAAM,OAAmB,QAAgB,OAAe;AACtE,QAAM,OAAO,GAAG,aAAa,KAAK,CAAC,IAAI,KAAK;AAC5C,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO,EAAE,GAAG,IAAI;AAAA,IAClB,KAAK;AACH,aAAO,EAAE,KAAK,IAAI;AAAA,IACpB,KAAK;AACH,aAAO,EAAE,MAAM,IAAI;AAAA,IACrB,KAAK;AACH,aAAO,EAAE,KAAK,IAAI;AAAA,EACtB;AACF;AAQA,SAAS,WAAW,MAAuB;AAEzC,QAAM,IAAI,QAAQ,IAAI,IAAI;AAC1B,SAAO,MAAM,UAAa,MAAM;AAClC;AAQO,SAAS,UAAmB;AACjC,QAAM,IAAI,QAAQ,IAAI;AACtB,MAAI,MAAM,UAAa,MAAM,GAAI,QAAO;AACxC,SAAO,MAAM,OAAO,MAAM;AAC5B;AAoBO,SAAS,WAAoB;AAClC,MAAI,WAAW,UAAU,EAAG,QAAO;AACnC,MAAI,QAAQ,IAAI,mBAAmB,IAAK,QAAO;AAC/C,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO,GAAG;AACZ;AAQO,SAAS,iBAA0B;AACxC,SAAO,WAAW,iBAAiB;AACrC;AAQO,SAAS,gBAAwB;AACtC,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,YAAY,UAAa,YAAY,IAAI;AAC3C,UAAM,IAAI,OAAO,OAAO;AACxB,QAAI,OAAO,UAAU,CAAC,KAAK,IAAI,EAAG,QAAO,KAAK,IAAI,GAAG,EAAE;AAAA,EACzD;AACA,QAAM,OAAO,QAAQ,OAAO;AAC5B,MAAI,OAAO,SAAS,YAAY,OAAO,EAAG,QAAO,KAAK,IAAI,MAAM,EAAE;AAClE,SAAO;AACT;;;ADzIO,IAAM,OAAO;AAAA,EAClB,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,OAAO;AAAA,EACP,WAAW;AAAA,EACX,aAAa;AAAA,EACb,WAAW;AACb;AASO,IAAM,eAAN,cAA2B,MAAM;AAAA,EAC7B;AAAA,EACT,YAAY,UAAkB,SAAiB;AAC7C,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,WAAW;AAAA,EAClB;AACF;AAQA,IAAM,kBAAkB;AA4BxB,SAAS,WAAW,MAA2B;AAC7C,SAAO,KAAK,SAAS;AACvB;AAEA,SAAS,YAAY,MAA2B;AAC9C,SAAO,KAAK,UAAU;AACxB;AAGO,SAAS,UAAU,OAAmB,CAAC,GAAY;AACxD,SAAO,KAAK,YAAY,QAAQ,KAAK,UAAU;AACjD;AAEA,SAASA,YAAW,MAAuB;AAEzC,QAAM,IAAI,QAAQ,IAAI,IAAI;AAC1B,SAAO,MAAM,UAAa,MAAM;AAClC;AASO,SAAS,cAAc,OAAmB,CAAC,GAAY;AAC5D,MAAI,WAAW,IAAI,KAAK,UAAU,IAAI,EAAG,QAAO;AAChD,MAAI,QAAQ,KAAKA,YAAW,UAAU,EAAG,QAAO;AAChD,SAAO,QAAQ,MAAM,UAAU,QAAQ,QAAQ,OAAO,UAAU;AAClE;AAUO,SAAS,qBAA8B;AAC5C,MAAI,yBAAyB,EAAG,QAAO;AACvC,SAAO,cAAc;AACvB;AAKA,SAAS,2BAAoC;AAC3C,QAAM,IAAI,QAAQ,IAAI;AACtB,SAAO,MAAM,UAAa,MAAM;AAClC;AAOO,SAAS,KAAK,KAAoB;AACvC,UAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,GAAG,CAAC;AAAA,CAAI;AACjD;AAQO,SAAS,IAAI,KAAa,OAAmB,CAAC,GAAS;AAC5D,MAAI,WAAW,IAAI,KAAK,YAAY,IAAI,EAAG;AAC3C,UAAQ,OAAO,MAAM,GAAG,GAAG;AAAA,CAAI;AACjC;AAEO,SAAS,KAAK,KAAa,OAAmB,CAAC,GAAS;AAC7D,MAAI,WAAW,IAAI,KAAK,YAAY,IAAI,EAAG;AAC3C,UAAQ,OAAO,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAAA,CAAI;AACzC;AAEO,SAAS,QAAQ,KAAa,OAAmB,CAAC,GAAS;AAChE,MAAI,WAAW,IAAI,KAAK,YAAY,IAAI,EAAG;AAC3C,UAAQ,OAAO,MAAM,GAAG,EAAE,GAAG,GAAG,CAAC;AAAA,CAAI;AACvC;AAEO,SAAS,KAAK,KAAa,OAAmB,CAAC,GAAS;AAC7D,MAAI,WAAW,IAAI,EAAG;AACtB,UAAQ,OAAO,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAAA,CAAI;AACzC;AAEO,SAAS,MAAM,KAAa,OAAmB,CAAC,GAAS;AAC9D,MAAI,WAAW,IAAI,EAAG;AACtB,UAAQ,OAAO,MAAM,GAAG,EAAE,MAAM,GAAG,CAAC;AAAA,CAAI;AAC1C;AASO,SAAS,IAAI,KAAa,OAAmB,CAAC,GAAS;AAC5D,MAAI,WAAW,IAAI,KAAK,KAAK,WAAW,KAAM;AAC9C,UAAQ,OAAO,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAAA,CAAI;AACzC;AAiBO,SAAS,MACd,MACA,MACA,OAAmB,CAAC,GACd;AACN,MAAI,WAAW,IAAI,EAAG;AACtB,MAAI,KAAK,WAAW,GAAG;AACrB,SAAK,aAAa,IAAI;AACtB;AAAA,EACF;AACA,QAAM,IAAI,IAAI,MAAM;AAAA,IAClB,MAAM,KAAK,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC;AAAA;AAAA;AAAA,IAG3C,OAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,EAAE;AAAA,IAC9B,WAAW,iBAAiB,MAAM,IAAI;AAAA,IACtC,UAAU;AAAA,IACV,UAAU;AAAA,EACZ,CAAC;AACD,aAAW,OAAO,MAAM;AACtB,MAAE,KAAK,KAAK,IAAI,CAAC,QAAQ,WAAW,IAAI,GAAG,CAAC,CAAC,CAAC;AAAA,EAChD;AACA,UAAQ,OAAO,MAAM,GAAG,EAAE,SAAS,CAAC;AAAA,CAAI;AAC1C;AAmBA,SAAS,iBACP,MACA,MACU;AACV,QAAM,IAAI,KAAK;AACf,MAAI,MAAM,EAAG,QAAO,CAAC;AACrB,QAAM,YAAY,KAAK,IAAI,CAAC,QAAQ,IAAI,MAAM;AAC9C,QAAM,UAAU,KAAK,IAAI,CAAC,KAAK,MAAM;AACnC,QAAI,MAAM,KAAK,IAAI,UAAU,CAAC,KAAK,IAAI,QAAQ,CAAC;AAChD,eAAW,OAAO,MAAM;AACtB,YAAM,MAAM,WAAW,IAAI,GAAG,CAAC,EAAE;AACjC,UAAI,MAAM,IAAK,OAAM;AAAA,IACvB;AACA,WAAO;AAAA,EACT,CAAC;AAID,QAAM,gBAAgB,KAAK,IAAI,IAAI,GAAG,cAAc,IAAI,IAAI,IAAI,CAAC;AACjE,QAAM,aAAa,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AACpD,MAAI,cAAc,eAAe;AAC/B,WAAO,QAAQ,IAAI,CAAC,MAAM,IAAI,CAAC;AAAA,EACjC;AAEA,QAAM,UAAU,QAAQ,MAAM;AAC9B,MAAI,MAAM;AACV,QAAM,QAAQ,UAAU,IAAI,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,CAAC;AACjD,SAAO,MAAM,eAAe;AAC1B,QAAI,MAAM;AACV,QAAI,SAAS;AACb,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,YAAMC,KAAI,QAAQ,CAAC,KAAK;AACxB,YAAM,IAAI,MAAM,CAAC,KAAK;AACtB,UAAIA,KAAI,KAAKA,KAAI,QAAQ;AACvB,iBAASA;AACT,cAAM;AAAA,MACR;AAAA,IACF;AACA,QAAI,QAAQ,GAAI;AAChB,UAAM,YAAY,MAAM;AACxB,UAAM,aAAa,QAAQ,GAAG,KAAK,MAAM,MAAM,GAAG,KAAK;AACvD,UAAM,MAAM,KAAK,IAAI,WAAW,SAAS;AACzC,YAAQ,GAAG,KAAK,QAAQ,GAAG,KAAK,KAAK;AACrC,WAAO;AAAA,EACT;AACA,SAAO,QAAQ,IAAI,CAAC,MAAM,IAAI,CAAC;AACjC;AAEA,SAAS,WAAW,OAAwB;AAC1C,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,YAAY,OAAO,UAAU,aAAa,OAAO,UAAU,UAAU;AACxF,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,SAAO,KAAK,UAAU,KAAK;AAC7B;AAeO,SAAS,eAAe,MAAgC,OAAmB,CAAC,GAAS;AAC1F;AAAA,IACE,KAAK,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,OAAO,WAAW,EAAE,KAAK,EAAE,EAAE;AAAA,IAChE,CAAC,SAAS,OAAO;AAAA,IACjB;AAAA,EACF;AACF;AAsBA,IAAM,cAAN,MAAqC;AAAA,EACnC,OAAO;AAAA,EACP,MAAM,OAAyB;AAC7B,WAAO;AAAA,EACT;AAAA,EACA,OAAgB;AACd,WAAO;AAAA,EACT;AAAA,EACA,QAAiB;AACf,WAAO;AAAA,EACT;AAAA,EACA,QAAQ,OAAsB;AAAA,EAAC;AAAA,EAC/B,KAAK,OAAsB;AAAA,EAAC;AAAA,EAC5B,KAAK,OAAsB;AAAA,EAAC;AAAA,EAC5B,KAAK,OAAsB;AAAA,EAAC;AAC9B;AAGA,IAAM,aAAN,MAAoC;AAAA,EACjB;AAAA,EAEjB,YAAY,aAAqB;AAC/B,SAAK,SAAS,IAAI,WAAW;AAAA,EAC/B;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA,EACA,IAAI,KAAK,OAAe;AACtB,SAAK,OAAO,OAAO;AAAA,EACrB;AAAA,EAEA,MAAM,MAAwB;AAC5B,QAAI,SAAS,OAAW,MAAK,OAAO,OAAO;AAC3C,SAAK,OAAO,MAAM;AAClB,WAAO;AAAA,EACT;AAAA,EAEA,OAAgB;AACd,SAAK,OAAO,KAAK;AACjB,WAAO;AAAA,EACT;AAAA,EAEA,QAAiB;AACf,SAAK,OAAO,MAAM;AAClB,WAAO;AAAA,EACT;AAAA,EAEA,QAAQ,MAAqB;AAC3B,SAAK,OAAO,QAAQ,IAAI;AAAA,EAC1B;AAAA,EAEA,KAAK,MAAqB;AACxB,SAAK,OAAO,KAAK,IAAI;AAAA,EACvB;AAAA,EAEA,KAAK,MAAqB;AACxB,SAAK,OAAO,KAAK,IAAI;AAAA,EACvB;AAAA,EAEA,KAAK,MAAqB;AACxB,SAAK,OAAO,KAAK,IAAI;AAAA,EACvB;AACF;AAEO,SAAS,QAAQ,OAAO,IAAI,OAAmB,CAAC,GAAY;AACjE,MAAI,WAAW,IAAI,KAAK,YAAY,IAAI,KAAK,CAAC,cAAc,IAAI,GAAG;AACjE,WAAO,IAAI,YAAY;AAAA,EACzB;AACA,SAAO,IAAI,WAAW,IAAI;AAC5B;AAaO,SAAS,KAAK,UAAkB,SAAiB,OAAmB,CAAC,GAAU;AACpF,MAAI,WAAW,IAAI,GAAG;AACpB,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,UAAU,QAAQ,EAAE,CAAC,CAAC;AAAA,CAAI;AAAA,EAC/F,WAAW,QAAQ,SAAS,GAAG;AAC7B,YAAQ,OAAO,MAAM,GAAG,OAAO;AAAA,CAAI;AAAA,EACrC;AACA,QAAM,IAAI,eAAe,UAAU,iBAAiB,OAAO;AAC7D;AAQO,SAAS,mBAAmB,MAAkB,MAAoB;AACvE,MAAI,CAAC,cAAc,IAAI,GAAG;AACxB;AAAA,MACE,KAAK;AAAA,MACL,GAAG,IAAI;AAAA,MACP;AAAA,IACF;AAAA,EACF;AACF;AAIA,SAAS,kBAAkB,KAA6B;AACtD,UAAQ,IAAI,MAAM;AAAA,IAChB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,KAAK;AAAA,IACd,KAAK;AACH,aAAO,KAAK;AAAA,IACd;AACE,aAAO,KAAK;AAAA,EAChB;AACF;AAGO,SAAS,cAAc,KAAsB;AAClD,MAAI,eAAe,aAAc,QAAO,IAAI;AAC5C,MAAI,eAAe,gBAAgB;AAGjC,QAAI,IAAI,KAAK,WAAW,OAAO,EAAG,QAAO,IAAI;AAC7C,WAAO,kBAAkB,GAAG;AAAA,EAC9B;AACA,SAAO,KAAK;AACd;AAOO,SAAS,YAAY,KAAoB;AAC9C,MAAI,eAAe,cAAc;AAC/B,QAAI,IAAI,QAAQ,SAAS,EAAG,SAAQ,OAAO,MAAM,GAAG,IAAI,OAAO;AAAA,CAAI;AAAA,EACrE,WAAW,eAAe,gBAAgB;AAAA,EAI1C,OAAO;AACL,YAAQ,OAAO,MAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AAAA,EACpF;AACA,UAAQ,WAAW,cAAc,GAAG;AACtC;","names":["envFlagSet","c"]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  installManagedNode
4
- } from "./chunk-US567S5T.js";
4
+ } from "./chunk-RON2XESF.js";
5
5
  import {
6
6
  EXIT,
7
7
  fail,
@@ -9,7 +9,7 @@ import {
9
9
  spinner,
10
10
  success,
11
11
  warn
12
- } from "./chunk-S2PDLS4C.js";
12
+ } from "./chunk-I2R55ACD.js";
13
13
 
14
14
  // src/commands/update.ts
15
15
  import {
@@ -221,4 +221,4 @@ export {
221
221
  buildUpdateNotice,
222
222
  runAsyncUpdateCheck
223
223
  };
224
- //# sourceMappingURL=chunk-Y4M2YC6U.js.map
224
+ //# sourceMappingURL=chunk-QIVHIKYD.js.map
@@ -6,7 +6,7 @@ import {
6
6
  spinner,
7
7
  success,
8
8
  warn
9
- } from "./chunk-S2PDLS4C.js";
9
+ } from "./chunk-I2R55ACD.js";
10
10
 
11
11
  // src/commands/install.ts
12
12
  import { chmodSync, mkdirSync } from "fs";
@@ -207,4 +207,4 @@ export {
207
207
  installManagedNode,
208
208
  install
209
209
  };
210
- //# sourceMappingURL=chunk-US567S5T.js.map
210
+ //# sourceMappingURL=chunk-RON2XESF.js.map
@@ -4,10 +4,10 @@ import {
4
4
  buildConflictOpts,
5
5
  checkWritePathDedup,
6
6
  reportPlannedWrites
7
- } from "./chunk-CN3KUSQT.js";
7
+ } from "./chunk-CT6TYLHN.js";
8
8
  import {
9
9
  resolveInteractive
10
- } from "./chunk-S2PDLS4C.js";
10
+ } from "./chunk-I2R55ACD.js";
11
11
 
12
12
  // src/commands/create.ts
13
13
  import { resolve } from "path";
@@ -77,4 +77,4 @@ async function create(dir, opts) {
77
77
  export {
78
78
  create
79
79
  };
80
- //# sourceMappingURL=create-DJ6B5K36.js.map
80
+ //# sourceMappingURL=create-VK2QMXYS.js.map
package/dist/index.js CHANGED
@@ -5,8 +5,8 @@ import {
5
5
  import {
6
6
  doctor,
7
7
  init
8
- } from "./chunk-CN3KUSQT.js";
9
- import "./chunk-S2PDLS4C.js";
8
+ } from "./chunk-CT6TYLHN.js";
9
+ import "./chunk-I2R55ACD.js";
10
10
  export {
11
11
  doctor,
12
12
  init,
@@ -3,11 +3,11 @@ import {
3
3
  buildMigrationPlan,
4
4
  install,
5
5
  installManagedNode
6
- } from "./chunk-US567S5T.js";
7
- import "./chunk-S2PDLS4C.js";
6
+ } from "./chunk-RON2XESF.js";
7
+ import "./chunk-I2R55ACD.js";
8
8
  export {
9
9
  buildMigrationPlan,
10
10
  install,
11
11
  installManagedNode
12
12
  };
13
- //# sourceMappingURL=install-X7QSYLLS.js.map
13
+ //# sourceMappingURL=install-4U4UDDNE.js.map
@@ -3,10 +3,10 @@ import {
3
3
  buildConflictOpts,
4
4
  checkWritePathDedup,
5
5
  reportPlannedWrites
6
- } from "./chunk-CN3KUSQT.js";
6
+ } from "./chunk-CT6TYLHN.js";
7
7
  import {
8
8
  resolveInteractive
9
- } from "./chunk-S2PDLS4C.js";
9
+ } from "./chunk-I2R55ACD.js";
10
10
 
11
11
  // src/sync.ts
12
12
  import { existsSync } from "fs";
@@ -88,4 +88,4 @@ function resolveSyncHost(root, opts) {
88
88
  export {
89
89
  sync
90
90
  };
91
- //# sourceMappingURL=sync-FKWXQOWT.js.map
91
+ //# sourceMappingURL=sync-VXZKODQA.js.map