@smeltjs/core 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/cli/bin.d.ts.map +1 -1
  2. package/dist/cli/bin.js +9 -4
  3. package/dist/cli/bin.js.map +1 -1
  4. package/dist/cli/doctor.d.ts +66 -0
  5. package/dist/cli/doctor.d.ts.map +1 -0
  6. package/dist/cli/doctor.js +137 -0
  7. package/dist/cli/doctor.js.map +1 -0
  8. package/dist/cli/hooks.d.ts +36 -2
  9. package/dist/cli/hooks.d.ts.map +1 -1
  10. package/dist/cli/hooks.js +108 -64
  11. package/dist/cli/hooks.js.map +1 -1
  12. package/dist/cli/init.d.ts.map +1 -1
  13. package/dist/cli/init.js +8 -14
  14. package/dist/cli/init.js.map +1 -1
  15. package/dist/cli/installed.d.ts +56 -0
  16. package/dist/cli/installed.d.ts.map +1 -0
  17. package/dist/cli/installed.js +105 -0
  18. package/dist/cli/installed.js.map +1 -0
  19. package/dist/cli/lava.d.ts +31 -0
  20. package/dist/cli/lava.d.ts.map +1 -0
  21. package/dist/cli/lava.js +82 -0
  22. package/dist/cli/lava.js.map +1 -0
  23. package/dist/cli/setup.d.ts +87 -0
  24. package/dist/cli/setup.d.ts.map +1 -0
  25. package/dist/cli/setup.js +513 -0
  26. package/dist/cli/setup.js.map +1 -0
  27. package/dist/cli/shell.d.ts +11 -4
  28. package/dist/cli/shell.d.ts.map +1 -1
  29. package/dist/cli/shell.js.map +1 -1
  30. package/dist/cli/subcommands/agents.d.ts.map +1 -1
  31. package/dist/cli/subcommands/agents.js +2 -1
  32. package/dist/cli/subcommands/agents.js.map +1 -1
  33. package/dist/cli/subcommands/doctor.d.ts +15 -0
  34. package/dist/cli/subcommands/doctor.d.ts.map +1 -0
  35. package/dist/cli/subcommands/doctor.js +43 -0
  36. package/dist/cli/subcommands/doctor.js.map +1 -0
  37. package/dist/cli/subcommands/flags.d.ts +13 -0
  38. package/dist/cli/subcommands/flags.d.ts.map +1 -1
  39. package/dist/cli/subcommands/flags.js +27 -1
  40. package/dist/cli/subcommands/flags.js.map +1 -1
  41. package/dist/cli/subcommands/hooks.d.ts.map +1 -1
  42. package/dist/cli/subcommands/hooks.js +10 -2
  43. package/dist/cli/subcommands/hooks.js.map +1 -1
  44. package/dist/cli/subcommands/init.d.ts.map +1 -1
  45. package/dist/cli/subcommands/init.js +2 -1
  46. package/dist/cli/subcommands/init.js.map +1 -1
  47. package/dist/cli/subcommands/registry.d.ts +3 -1
  48. package/dist/cli/subcommands/registry.d.ts.map +1 -1
  49. package/dist/cli/subcommands/registry.js +6 -0
  50. package/dist/cli/subcommands/registry.js.map +1 -1
  51. package/dist/cli/subcommands/setup.d.ts +23 -0
  52. package/dist/cli/subcommands/setup.d.ts.map +1 -0
  53. package/dist/cli/subcommands/setup.js +87 -0
  54. package/dist/cli/subcommands/setup.js.map +1 -0
  55. package/dist/cli/subcommands/subcommand.d.ts +1 -1
  56. package/dist/cli/subcommands/subcommand.d.ts.map +1 -1
  57. package/dist/cli/subcommands/subcommand.js.map +1 -1
  58. package/dist/cli/wizard.d.ts +72 -0
  59. package/dist/cli/wizard.d.ts.map +1 -0
  60. package/dist/cli/wizard.js +86 -0
  61. package/dist/cli/wizard.js.map +1 -0
  62. package/dist/harness/claude-code.d.ts.map +1 -1
  63. package/dist/harness/claude-code.js +10 -0
  64. package/dist/harness/claude-code.js.map +1 -1
  65. package/dist/harness/codex.d.ts.map +1 -1
  66. package/dist/harness/codex.js +1 -0
  67. package/dist/harness/codex.js.map +1 -1
  68. package/dist/harness/grok.d.ts.map +1 -1
  69. package/dist/harness/grok.js +4 -1
  70. package/dist/harness/grok.js.map +1 -1
  71. package/dist/harness/kilocode.d.ts.map +1 -1
  72. package/dist/harness/kilocode.js +1 -1
  73. package/dist/harness/kilocode.js.map +1 -1
  74. package/dist/harness/opencode.d.ts.map +1 -1
  75. package/dist/harness/opencode.js +10 -0
  76. package/dist/harness/opencode.js.map +1 -1
  77. package/dist/harness/profile.d.ts +23 -2
  78. package/dist/harness/profile.d.ts.map +1 -1
  79. package/dist/harness/profile.js.map +1 -1
  80. package/dist/harness/snippet.d.ts +14 -1
  81. package/dist/harness/snippet.d.ts.map +1 -1
  82. package/dist/harness/snippet.js +18 -2
  83. package/dist/harness/snippet.js.map +1 -1
  84. package/dist/index.d.ts +12 -0
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +9 -0
  87. package/dist/index.js.map +1 -1
  88. package/dist/setup/recipe.d.ts +73 -0
  89. package/dist/setup/recipe.d.ts.map +1 -0
  90. package/dist/setup/recipe.js +76 -0
  91. package/dist/setup/recipe.js.map +1 -0
  92. package/dist/text/json-edit.d.ts +15 -1
  93. package/dist/text/json-edit.d.ts.map +1 -1
  94. package/dist/text/json-edit.js +76 -5
  95. package/dist/text/json-edit.js.map +1 -1
  96. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/cli/hooks.ts"],"names":[],"mappings":"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","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nimport { CliUsageError } from '../errors.ts';\nimport { nodeCommand, portablePath, shimScriptPath, smeltBinPath } from '../harness/paths.ts';\nimport { hasShim, TIER_HONESTY } from '../harness/profile.ts';\nimport type {\n HarnessInstallContext,\n HarnessJsonHooks,\n HarnessProfile,\n} from '../harness/profile.ts';\nimport {\n GUARD_EVENTS,\n GUARD_ONLY_FILES,\n HARNESSES,\n harnessById,\n harnessNames,\n JSON_HOOK_FILES,\n LIFECYCLE_EVENTS,\n lifecycleHarnesses,\n MANAGED_EVENTS,\n} from '../harness/registry.ts';\nimport {\n instructionSnippet,\n OURS_TOKEN,\n SNIPPET_END_MD,\n SNIPPET_START_MD,\n} from '../harness/snippet.ts';\nimport { DEFAULT_SUGGESTION_BUDGET_BYTES, DEFAULT_THRESHOLD_BYTES } from '../hooks/guard-core.ts';\nimport type { EnforcementMode } from '../hooks/guard-core.ts';\nimport {\n editTopLevelProperty,\n jsonStyle,\n stripMarkerBlock,\n upsertMarkerBlock,\n} from '../text/json-edit.ts';\n\nimport { answerReader, CLI_NAME } from './shell.ts';\nimport type { AnswerStream } from './shell.ts';\nimport {\n CONFIG_FILE_NAME,\n CONFIG_VERSION,\n findConfigFile,\n parseConfig,\n renderConfig,\n} from './config.ts';\nimport type { SmeltConfig, SmeltConfigHooks } from './config.ts';\n\n/**\n * `smelt hooks install` / `smelt hooks remove` — the multi-harness guard preset.\n *\n * The design: one zero-dependency guard core\n * (`src/hooks/guard-core.ts`), thin per-harness shims mapping each harness's native\n * hook schema onto it, and this installer, which writes the harness config that wires\n * a shim in — plus an instruction-file snippet as belt and braces, because the\n * snippet is also what teaches the model to run `smelt retrieve` after a deny.\n *\n * Every per-harness fact lives in that harness's {@link HarnessProfile}\n * (`src/harness/<id>.ts`), including what to write and how to take it back out. This\n * module owns only what is the *same* for every harness: the hooks merge (which entries\n * are ours, what a re-run replaces), the wizard, and the two plans below — folds over\n * `profile.install`, with no case list of its own. The byte-faithful editing itself —\n * one top-level JSON property, one delimited text block — is `src/text/json-edit.ts`,\n * which knows nothing about harnesses.\n *\n * Harnesses come in three honesty tiers (docs/research/2026-09-02-harness-capability-matrix.md),\n * and which harness sits at which is `HarnessProfile.tier` — read through\n * `harnessesByTier()`, never listed again here:\n *\n * - **verified** — schemas verified against primary docs and exercised against\n * recorded fixtures; first-class targets.\n * - **experimental** — hook schemas mapped from the capability matrix but not yet\n * smoke-tested green against the real binary. Labelled as such in code, docs, and\n * this installer's output.\n * - **advisory** — no usable hook API, so what ships is instructions (and, for\n * KiloCode, a permissions/MCP sketch). Nothing enforces them, and the output says\n * so rather than implying a guard exists.\n *\n * The wizard discipline is `smelt init`'s, verbatim: every step accepts `back`,\n * nothing is written until a final confirm that lists every file, and an existing\n * file is never overwritten without an explicit per-file `yes` — guarded by\n * `test/guards/hooks-preset.test.ts`, with mutation `hooks-install-overwrite-without-consent`\n * proving the guard goes red.\n */\n\n/** Where the wizard's bytes come from and go. Injected so `runHooks` tests in-process. */\nexport interface HooksIo {\n /**\n * Scripted answers in, one line at a time. Structural on purpose; see\n * {@link AnswerStream}.\n */\n readonly input: AnswerStream;\n readonly output: (text: string) => void;\n /** Project directory: detection, config discovery, and every write are relative to it. */\n readonly cwd: string;\n /** Home directory for detection only. Tests point it at a temp dir; nothing writes here. */\n readonly home?: string;\n}\n\nexport { instructionSnippet, SNIPPET_END_MD, SNIPPET_START_MD };\n\n/** A harness whose config directory exists in the project or the home directory. */\nexport function detectedHarnesses(cwd: string, home: string): readonly HarnessProfile[] {\n return HARNESSES.filter(\n (profile) =>\n profile.detect.some((path) => existsSync(join(cwd, path))) ||\n profile.detectHome.some((path) => existsSync(join(home, path))),\n );\n}\n\n/* ------------------------------------------------------------------------------------\n * Generated content\n * ---------------------------------------------------------------------------------- */\n\n/** Claude-style hook entry: one command under an optional matcher. */\nfunction commandEntry(matcher: string | undefined, command: string): unknown {\n return {\n ...(matcher === undefined ? {} : { matcher }),\n hooks: [{ type: 'command', command }],\n };\n}\n\n/**\n * The hook command a harness's entries run: its own shim script, through node.\n *\n * @throws {Error} when a profile declares a JSON hook file but ships no shim — a\n * registry bug, pinned by `test/guards/harness-registry.test.ts`, not a user error.\n */\nfunction shimCommand(profile: HarnessProfile, cwd: string): string {\n /* v8 ignore next 5 -- unreachable: pinned by the harness-registry guard */\n if (!hasShim(profile)) {\n throw new Error(\n `smelt: harness \"${profile.id}\" wires a hook command but ships no shim script.`,\n );\n }\n return nodeCommand(cwd, shimScriptPath(profile));\n}\n\n/**\n * One harness's hook entries: the guard under each matcher its schema spells, plus\n * the session-lifecycle hooks for the harnesses whose schema carries them. Every\n * toggle the wizard offers is a key that is present or absent here — an absent key is\n * how a re-run turns a toggle *off*, because the merge deletes what it no longer sees.\n *\n * The two `SessionStart` toggles — the opening map and the instruction-file lint —\n * are **concatenated into one array**, not spread as two objects. Spreading would put\n * the same computed key twice in one literal, and the second would silently replace\n * the first: turning the lint on would turn the map off, with no error anywhere. It is\n * the shape of bug this file exists to refuse, one layer up from the config it writes.\n */\nfunction jsonHookEvents(\n step: HarnessJsonHooks,\n ctx: HarnessInstallContext,\n command: string,\n): Record<string, readonly unknown[]> {\n // The trailing shell comment tags the entry as this installer's (see isOursEntry):\n // a bare `cli/bin.js` substring would also match some other npm CLI's built binary.\n const stats = `${nodeCommand(ctx.cwd, smeltBinPath(), 'stats')} 2>/dev/null || true # ${OURS_TOKEN}`;\n const map = `${nodeCommand(\n ctx.cwd,\n smeltBinPath(),\n `${MAP_ON_START_ARGS} --budget ${String(ctx.budgetBytes)} --cache .smelt/tags`,\n )} 2>/dev/null || true # ${OURS_TOKEN}`;\n const lint = `${nodeCommand(\n ctx.cwd,\n smeltBinPath(),\n AGENTS_LINT_ARGS,\n )} 2>/dev/null || true # ${OURS_TOKEN}`;\n\n const sessionStart = [\n ...(ctx.mapOnStart ? [commandEntry(SESSION_START_MATCHER, map)] : []),\n ...(ctx.lintOnStart ? [commandEntry(SESSION_START_MATCHER, lint)] : []),\n ];\n\n return {\n ...(ctx.guard\n ? {\n [step.event]: step.matchers.map((matcher) =>\n step.entry === 'bare-command' ? { command } : commandEntry(matcher, command),\n ),\n }\n : {}),\n ...(step.lifecycle && ctx.statsOnStop\n ? { [LIFECYCLE_EVENTS.stats]: [commandEntry(undefined, stats)] }\n : {}),\n ...(step.lifecycle && sessionStart.length > 0 ? { [LIFECYCLE_EVENTS.map]: sessionStart } : {}),\n };\n}\n\n/** The matcher both `SessionStart` entries fire under — a session opening, however. */\nconst SESSION_START_MATCHER = 'startup|resume|clear|compact';\n\n/**\n * The two `SessionStart` commands' distinguishing arguments, and **the substrings a\n * re-run recognises each entry by**. Spelled once so the writer and the reader cannot\n * drift: `presetToggles` tells the two entries apart by the command each one runs, and\n * a wizard that wrote `agents lint .` while its reader looked for `agents lint` would\n * read every re-run's lint toggle back as off and quietly delete it.\n */\nconst MAP_ON_START_ARGS = 'map .';\nexport const AGENTS_LINT_ARGS = 'agents lint .';\n\n/**\n * True for a hook entry this installer wrote. Matched on the shim script paths and the\n * `smelt:hooks` token the stats/map commands carry — never on a substring as generic\n * as `cli/bin.js`, which another npm CLI's built binary could share: remove and\n * re-install may only ever touch entries that are provably smelt's.\n */\nfunction isOursEntry(entry: unknown): boolean {\n const text = JSON.stringify(entry) ?? '';\n return text.includes('hooks/shims/') || text.includes(OURS_TOKEN);\n}\n\n/**\n * Merge our hook entries into a JSON settings file, preserving everything foreign\n * **byte-faithfully**: the merged `hooks` value is spliced into the original text, so\n * unknown top-level keys, string escapes, number spellings, indentation and key order\n * outside the `hooks` property ride through verbatim (an installer\n * that reformats somebody's settings file has edited what it was never asked to).\n * Inside `hooks`, unmanaged events and other people's entries under managed events\n * are preserved; our previous entries are replaced (that is what makes a re-run edit\n * toggles), and events left with no entries disappear. A semantic no-op returns the\n * input text unchanged. Returns `undefined` when the existing file is not a JSON\n * object — the caller skips the file rather than clobbering something it cannot\n * understand.\n */\nexport function mergeJsonHooks(\n existingText: string | undefined,\n events: Record<string, readonly unknown[]>,\n shape: { readonly version?: number } = {},\n): string | undefined {\n let root: Record<string, unknown> = {};\n if (existingText !== undefined) {\n try {\n const parsed: unknown = JSON.parse(existingText);\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return undefined;\n root = parsed as Record<string, unknown>;\n } catch {\n return undefined;\n }\n }\n const hooksValue = root['hooks'];\n const existingHooks =\n typeof hooksValue === 'object' && hooksValue !== null && !Array.isArray(hooksValue)\n ? (hooksValue as Record<string, unknown>)\n : undefined;\n const hooks = { ...existingHooks };\n\n for (const event of MANAGED_EVENTS) {\n const existing = Array.isArray(hooks[event]) ? (hooks[event] as unknown[]) : [];\n const foreign = existing.filter((entry) => !isOursEntry(entry));\n const ours = events[event] ?? [];\n const merged = [...foreign, ...ours];\n if (merged.length > 0) hooks[event] = merged;\n else delete hooks[event];\n }\n\n const mergedHooks = Object.keys(hooks).length > 0 ? hooks : undefined;\n\n // A brand-new file: nothing to preserve, render fresh two-space JSON.\n if (existingText === undefined) {\n const fresh: Record<string, unknown> = {};\n if (mergedHooks !== undefined) fresh['hooks'] = mergedHooks;\n if (shape.version !== undefined) fresh['version'] = shape.version;\n return `${JSON.stringify(fresh, null, 2)}\\n`;\n }\n\n const hooksChanged =\n JSON.stringify(existingHooks ?? null) !== JSON.stringify(mergedHooks ?? null);\n const needsVersion = shape.version !== undefined && root['version'] === undefined;\n if (!hooksChanged && !needsVersion) return existingText;\n\n // The style is read once, off the original: a second edit must match the first.\n const style = jsonStyle(existingText);\n let text: string | undefined = existingText;\n\n if (hooksChanged) {\n text = editTopLevelProperty(text, 'hooks', mergedHooks, style);\n /* v8 ignore next -- unreachable: JSON.parse accepted the same text above */\n if (text === undefined) return undefined;\n }\n if (needsVersion) {\n text = editTopLevelProperty(text, 'version', shape.version, style);\n /* v8 ignore next -- unreachable: every splice above keeps the text valid JSON */\n if (text === undefined) return undefined;\n }\n return text;\n}\n\n/* ------------------------------------------------------------------------------------\n * Planning\n * ---------------------------------------------------------------------------------- */\n\ninterface PlannedFile {\n /** Display path, relative to the project. */\n readonly name: string;\n readonly path: string;\n readonly content: string;\n readonly exists: boolean;\n readonly unchanged: boolean;\n /** chmod after writing (the cline hook must be executable). */\n readonly mode?: number;\n}\n\ninterface SkippedFile {\n readonly name: string;\n readonly why: string;\n}\n\ninterface PlannedRemoval {\n readonly name: string;\n readonly path: string;\n /** `'delete'` removes the file; `'modify'` writes `content` (ours stripped out). */\n readonly action: 'delete' | 'modify';\n readonly content?: string;\n}\n\nexport interface HooksChoices {\n harnesses: HarnessProfile[];\n guard: boolean;\n statsOnStop: boolean;\n mapOnStart: boolean;\n lintOnStart: boolean;\n enforcement: EnforcementMode;\n thresholdBytes: number;\n}\n\ninterface InstallPlan {\n readonly files: readonly PlannedFile[];\n readonly skipped: readonly SkippedFile[];\n readonly notes: readonly string[];\n}\n\nfunction planFile(cwd: string, name: string, content: string, mode?: number): PlannedFile {\n const path = join(cwd, name);\n const exists = existsSync(path);\n const unchanged = exists && readFileSync(path, 'utf8') === content;\n return { name, path, content, exists, unchanged, ...(mode === undefined ? {} : { mode }) };\n}\n\nfunction readIfExists(path: string): string | undefined {\n return existsSync(path) ? readFileSync(path, 'utf8') : undefined;\n}\n\n/**\n * Every file `install` would write, computed against the current disk state — pure\n * planning, nothing written. A fold over each chosen profile's `install` list and its\n * instruction layer; all per-harness knowledge is in the profiles. Shared instruction\n * files (several harnesses read AGENTS.md) are planned once.\n *\n * @throws {CliUsageError} when an existing `smelt.config.json` is malformed — the\n * same refusal every other subcommand makes; an installer that guessed around a\n * broken config would write settings the guard then ignores.\n */\nexport function planInstall(cwd: string, choices: HooksChoices): InstallPlan {\n const files = new Map<string, PlannedFile>();\n const skipped: SkippedFile[] = [];\n const notes: string[] = [];\n\n // -- smelt.config.json: the guard's runtime settings live here, not in any harness\n // file, so every shim reads one source of truth.\n const configPath = findConfigFile(cwd) ?? join(cwd, CONFIG_FILE_NAME);\n const existingConfig =\n readIfExists(configPath) === undefined\n ? undefined\n : parseConfig(readFileSync(configPath, 'utf8'), configPath);\n const hooksBlock: SmeltConfigHooks = {\n thresholdBytes: choices.thresholdBytes,\n enforcement: choices.enforcement,\n };\n const budgetBytes = existingConfig?.defaultBudgetBytes ?? DEFAULT_SUGGESTION_BUDGET_BYTES;\n files.set(configPath, {\n name: portablePath(cwd, configPath),\n path: configPath,\n content: renderConfigWithHooks(existingConfig, hooksBlock),\n exists: existsSync(configPath),\n unchanged: readIfExists(configPath) === renderConfigWithHooks(existingConfig, hooksBlock),\n });\n\n const ctx: HarnessInstallContext = {\n cwd,\n guard: choices.guard,\n statsOnStop: choices.statsOnStop,\n mapOnStart: choices.mapOnStart,\n lintOnStart: choices.lintOnStart,\n thresholdBytes: choices.thresholdBytes,\n budgetBytes,\n };\n const snippet = instructionSnippet(choices.thresholdBytes, budgetBytes);\n\n const planJsonHooks = (\n name: string,\n events: Record<string, readonly unknown[]>,\n shape: { readonly version?: number } = {},\n ): void => {\n const path = join(cwd, name);\n // Nothing to install and nothing to strip: don't create an empty hooks file.\n if (Object.keys(events).length === 0 && !existsSync(path)) return;\n const merged = mergeJsonHooks(readIfExists(path), events, shape);\n if (merged === undefined) {\n skipped.push({\n name,\n why: 'exists but is not a JSON object — fix or remove it, then re-run',\n });\n return;\n }\n files.set(path, planFile(cwd, name, merged));\n };\n\n const planBlockFile = (\n name: string,\n block: string,\n start: string,\n end: string,\n skipWhen?: { readonly contains: string; readonly why: string },\n ): void => {\n const path = join(cwd, name);\n if (files.has(path)) return; // a shared file (AGENTS.md), already planned\n const existing = readIfExists(path);\n // A file that already carries its owner's version of what this block does is\n // theirs to edit, not ours: say so, and touch nothing.\n if (\n skipWhen !== undefined &&\n existing !== undefined &&\n !existing.includes(start) &&\n existing.includes(skipWhen.contains)\n ) {\n skipped.push({ name, why: skipWhen.why });\n return;\n }\n files.set(path, planFile(cwd, name, upsertMarkerBlock(existing, block, start, end)));\n };\n\n for (const profile of choices.harnesses) {\n for (const step of profile.install) {\n switch (step.kind) {\n case 'json-hooks':\n planJsonHooks(\n step.file,\n jsonHookEvents(step, ctx, shimCommand(profile, cwd)),\n step.shape ?? {},\n );\n break;\n case 'marker-block':\n planBlockFile(step.file, step.block(ctx), step.start, step.end, step.skipWhen);\n break;\n case 'own-file':\n if (step.guardOnly && !ctx.guard) break;\n files.set(join(cwd, step.file), planFile(cwd, step.file, step.content(ctx), step.mode));\n break;\n }\n }\n\n if (profile.instructions === 'snippet') {\n planBlockFile(profile.instructionFile, snippet, SNIPPET_START_MD, SNIPPET_END_MD);\n } else {\n files.set(\n join(cwd, profile.instructionFile),\n planFile(cwd, profile.instructionFile, profile.instructions(ctx)),\n );\n }\n\n for (const caveat of profile.caveats) notes.push(`${profile.name}: ${caveat}`);\n }\n\n return { files: [...files.values()], skipped, notes };\n}\n\n/** Where the installed config points the persistent store, relative to the config file. */\nexport const DEFAULT_STORE_DIR = '.smelt/store';\n\n/**\n * Existing config re-rendered with the hooks block, other fields carried verbatim —\n * except that a config with **no** store block gains a directory store. The deny\n * reasons and the instruction snippet teach `smelt retrieve <hash>`, and retrieval\n * across processes needs a persistent store (`smelt retrieve` refuses a memory\n * store, exit 2) — an install whose own guard promises a command the installed\n * config cannot run would be the exact silent-failure shape this project refuses.\n * An *explicit* `{\"kind\":\"memory\"}` is respected; the guard then conditions its\n * retrieve promise on the store kind instead (`retrieveSentence` in guard-core).\n *\n * That store injection is this verb's **policy**, which is why it lives here; the\n * bytes are written by `renderConfig` in `config.ts`, the one writer, so a key added\n * to the schema reaches this file and `init`'s together or not at all.\n *\n * \"Carried verbatim\" is spelled as a spread rather than as a list of the fields to\n * copy, and that is load-bearing: the list version silently dropped every key nobody\n * remembered to add to it — `agents` was added to the schema and this function kept\n * writing configs without it, which is a setting the user believed was in force,\n * caught by `test/guards/config-writer.test.ts`. Only the two fields this verb\n * actually decides are named.\n */\nexport function renderConfigWithHooks(\n existing: SmeltConfig | undefined,\n hooks: SmeltConfigHooks,\n): string {\n return renderConfig({\n ...existing,\n smeltConfig: CONFIG_VERSION,\n store: existing?.store ?? { kind: 'directory', path: DEFAULT_STORE_DIR },\n hooks,\n });\n}\n\n/**\n * Everything `remove` would delete or strip, computed against the current disk state.\n * The mirror image of {@link planInstall}, over the same data: each install step's\n * kind is also how it comes back out — a JSON hook file is strip-merged, a marker\n * block is stripped, a file that is entirely ours is deleted.\n */\nexport function planRemove(\n cwd: string,\n harnesses: readonly HarnessProfile[],\n): readonly PlannedRemoval[] {\n const removals = new Map<string, PlannedRemoval>();\n\n const planJsonStrip = (name: string): void => {\n const path = join(cwd, name);\n const existing = readIfExists(path);\n if (existing === undefined) return;\n const stripped = mergeJsonHooks(existing, {});\n if (stripped === undefined || stripped === existing) return;\n const remains: unknown = JSON.parse(stripped);\n const empty =\n typeof remains === 'object' &&\n remains !== null &&\n Object.keys(remains as Record<string, unknown>).filter((key) => key !== 'version').length ===\n 0;\n removals.set(\n path,\n empty && existing.includes('hooks')\n ? { name, path, action: 'delete' }\n : { name, path, action: 'modify', content: stripped },\n );\n };\n\n const planBlockStrip = (name: string, start: string, end: string): void => {\n const path = join(cwd, name);\n const existing = readIfExists(path);\n if (existing === undefined || !existing.includes(start)) return;\n const stripped = stripMarkerBlock(existing, start, end);\n removals.set(\n path,\n stripped === undefined\n ? { name, path, action: 'delete' }\n : { name, path, action: 'modify', content: stripped },\n );\n };\n\n const planWholeFileDelete = (name: string): void => {\n const path = join(cwd, name);\n const existing = readIfExists(path);\n if (existing === undefined || !existing.includes(OURS_TOKEN)) return;\n removals.set(path, { name, path, action: 'delete' });\n };\n\n for (const profile of harnesses) {\n for (const step of profile.install) {\n switch (step.kind) {\n case 'json-hooks':\n planJsonStrip(step.file);\n break;\n case 'marker-block':\n planBlockStrip(step.file, step.start, step.end);\n break;\n case 'own-file':\n planWholeFileDelete(step.file);\n break;\n }\n }\n if (profile.instructions === 'snippet') {\n planBlockStrip(profile.instructionFile, SNIPPET_START_MD, SNIPPET_END_MD);\n } else {\n planWholeFileDelete(profile.instructionFile);\n }\n }\n\n return [...removals.values()];\n}\n\n/* ------------------------------------------------------------------------------------\n * The wizard\n * ---------------------------------------------------------------------------------- */\n\ntype Asker = (prompt: string) => Promise<string>;\n\n/**\n * `smelt hooks <install|remove>`, start to finish. The same testability pattern as\n * `runInit`: a pure function over an input/output pair, exit code returned.\n */\nexport async function runHooks(\n action: 'install' | 'remove',\n harnessFlag: string | undefined,\n io: HooksIo,\n): Promise<number> {\n // Same adapter, same reason, as `runInit` — see the note on `answerReader`.\n const lines = answerReader(io.input);\n const ask = async (prompt: string): Promise<string> => {\n io.output(prompt);\n const next = await lines.next();\n if (next === undefined) {\n throw new CliUsageError(\n `${CLI_NAME} hooks: input ended before the wizard finished. ` +\n `Files already confirmed and written stay; nothing further was written.`,\n );\n }\n return next.trim();\n };\n\n try {\n return action === 'install'\n ? await installFlow(io, ask, harnessFlag)\n : await removeFlow(io, ask, harnessFlag);\n } finally {\n await lines.release();\n }\n}\n\nfunction resolveHarnessFlag(flag: string): HarnessProfile {\n const profile = harnessById(flag);\n if (profile === undefined) {\n throw new CliUsageError(\n `${CLI_NAME} hooks: unknown harness \"${flag}\". ` +\n `Known: ${HARNESSES.map((h) => h.id).join(', ')}.`,\n );\n }\n return profile;\n}\n\nfunction tierLabel(profile: HarnessProfile): string {\n return `${profile.id.padEnd(12)} ${profile.name.padEnd(14)} [${profile.tier}] — ${TIER_HONESTY[profile.tier]}`;\n}\n\nasync function installFlow(\n io: HooksIo,\n ask: Asker,\n harnessFlag: string | undefined,\n): Promise<number> {\n const home = io.home ?? homedir();\n const detected = detectedHarnesses(io.cwd, home);\n\n io.output(\n `${CLI_NAME} hooks install — wires the smelt guard into agent-harness hooks.\\n` +\n `Answer \\`back\\` at any step to return to the previous one. Nothing is written ` +\n `until you confirm at the end.\\n\\n`,\n );\n\n const choices: HooksChoices = {\n harnesses: harnessFlag !== undefined ? [resolveHarnessFlag(harnessFlag)] : [...detected],\n ...presetToggles(io.cwd),\n enforcement: 'deny',\n thresholdBytes: DEFAULT_THRESHOLD_BYTES,\n };\n\n // With --harness the selection step is skipped, so the tier label — and its one\n // line of honesty about what the tier means — is printed here instead.\n if (harnessFlag !== undefined) {\n for (const profile of choices.harnesses) io.output(` ${tierLabel(profile)}\\n`);\n }\n\n const steps: readonly ((io_: HooksIo, ask_: Asker) => Promise<'ok' | 'back'>)[] = [\n async (io_, ask_) =>\n harnessFlag !== undefined ? 'ok' : stepHarnesses(io_, ask_, choices, detected),\n async (io_, ask_) =>\n stepToggle(io_, ask_, 'PreToolUse size-guard', guardCopy(), choices.guard, (on) => {\n choices.guard = on;\n }),\n async (io_, ask_) =>\n stepToggle(\n io_,\n ask_,\n 'stats on Stop',\n `\\`smelt stats\\` runs when a session ends — the honest signal (expansion rate) ` +\n `surfaced where the turn ends. Observation only; never blocks. Wired for the ` +\n `harnesses whose hooks carry session events (${harnessNames(lifecycleHarnesses())}).`,\n choices.statsOnStop,\n (on) => {\n choices.statsOnStop = on;\n },\n ),\n async (io_, ask_) =>\n stepToggle(\n io_,\n ask_,\n 'repo map on SessionStart',\n `\\`smelt map . --budget …\\` runs at session start and its output opens the ` +\n `context — the agent starts oriented. Costs one map build per session. ` +\n `Wired for the harnesses whose hooks carry session events ` +\n `(${harnessNames(lifecycleHarnesses())}).`,\n choices.mapOnStart,\n (on) => {\n choices.mapOnStart = on;\n },\n ),\n async (io_, ask_) =>\n stepToggle(\n io_,\n ask_,\n 'instruction-file lint on SessionStart',\n `\\`smelt agents lint .\\` runs at session start and reports on the AGENTS.md, ` +\n `CLAUDE.md and GEMINI.md this session is about to load on every request — ` +\n `bytes per level, and any path or link in them that no longer resolves. ` +\n `Advisory: it never blocks, and it exits 0 unless you set ` +\n `agents.budgetBytes in ${CONFIG_FILE_NAME}. Wired for verified-tier ` +\n `harnesses (Claude Code, Codex).`,\n choices.lintOnStart,\n (on) => {\n choices.lintOnStart = on;\n },\n ),\n async (io_, ask_) => stepEnforcement(io_, ask_, choices),\n async (io_, ask_) => stepThreshold(io_, ask_, choices),\n ];\n\n let index = 0;\n for (;;) {\n while (index < steps.length) {\n const outcome = await steps[index]!(io, ask);\n if (outcome === 'back') {\n if (index === 0) io.output(`This is the first step — there is nothing before it.\\n`);\n else index -= 1;\n } else {\n index += 1;\n }\n }\n if (choices.harnesses.length === 0) {\n io.output(`No harness selected. Nothing to do; nothing was written.\\n`);\n return 0;\n }\n const verdict = await confirmAndInstall(io, ask, choices);\n if (verdict !== 'back') return 0;\n index = steps.length - 1;\n }\n}\n\nfunction guardCopy(): string {\n return (\n `Denies raw Reads (and simple \\`cat\\`s) of files over the size threshold, with a ` +\n `reason naming the exact \\`smelt\\` replacement — the model still sees everything: ` +\n `smelted first, \\`smelt retrieve\\` for the rest. Windowed reads (offset/limit) ` +\n `always pass.`\n );\n}\n\nasync function stepHarnesses(\n io: HooksIo,\n ask: Asker,\n choices: HooksChoices,\n detected: readonly HarnessProfile[],\n): Promise<'ok' | 'back'> {\n io.output(`\\nHarnesses — detected by their config directories (project or home):\\n`);\n for (const profile of HARNESSES) {\n const mark = detected.includes(profile) ? '*' : ' ';\n io.output(` ${mark} ${tierLabel(profile)}\\n`);\n }\n io.output(`(* = detected here)\\n`);\n for (;;) {\n const current = choices.harnesses.map((profile) => profile.id).join(',') || '(none)';\n const answer = await ask(\n `install for which? (comma-separated ids, Enter = ${current}, or back)\\n> `,\n );\n if (answer === 'back') return 'back';\n if (answer === '') return 'ok';\n const ids = answer\n .split(',')\n .map((id) => id.trim())\n .filter((id) => id !== '');\n const chosen: HarnessProfile[] = [];\n let bad: string | undefined;\n for (const id of ids) {\n const profile = harnessById(id);\n if (profile === undefined) bad = id;\n else if (!chosen.includes(profile)) chosen.push(profile);\n }\n if (bad !== undefined) {\n io.output(`Unknown harness \"${bad}\". Known: ${HARNESSES.map((h) => h.id).join(', ')}.\\n`);\n continue;\n }\n choices.harnesses = chosen;\n return 'ok';\n }\n}\n\nasync function stepToggle(\n io: HooksIo,\n ask: Asker,\n name: string,\n copy: string,\n current: boolean,\n set: (on: boolean) => void,\n): Promise<'ok' | 'back'> {\n io.output(`\\n${name} — ${copy}\\n`);\n for (;;) {\n const answer = await ask(`${name}? (on/off) [${current ? 'on' : 'off'}] (or back)> `);\n if (answer === 'back') return 'back';\n if (answer === '') return 'ok';\n if (answer === 'on' || answer === 'off') {\n set(answer === 'on');\n return 'ok';\n }\n io.output(`on, off, or back.\\n`);\n }\n}\n\nasync function stepEnforcement(\n io: HooksIo,\n ask: Asker,\n choices: HooksChoices,\n): Promise<'ok' | 'back'> {\n io.output(\n `\\nEnforcement — what happens when the guard catches an oversized raw read:\\n` +\n ` 1. deny — refuse with a reason naming the exact replacement command. The\\n` +\n ` transcript stays truthful; the model runs the replacement itself.\\n` +\n ` 2. rewrite — on harnesses whose hooks can modify tool input, substitute the\\n` +\n ` replacement in-flight (grep/cat piped through smelt). Never\\n` +\n ` silent: the substitution is announced in the decision reason\\n` +\n ` where the harness has one, on stderr where it does not.\\n` +\n ` Harnesses that cannot rewrite fall back to deny.\\n`,\n );\n for (;;) {\n const current = choices.enforcement === 'deny' ? '1' : '2';\n const answer = await ask(`enforcement (1/2) [${current}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? current : answer;\n if (pick === '1' || pick === '2') {\n choices.enforcement = pick === '1' ? 'deny' : 'rewrite';\n return 'ok';\n }\n io.output(`1 for deny, 2 for rewrite, or back.\\n`);\n }\n}\n\nasync function stepThreshold(\n io: HooksIo,\n ask: Asker,\n choices: HooksChoices,\n): Promise<'ok' | 'back'> {\n io.output(\n `\\nSize threshold — reads at or under this many bytes always pass. The ${String(\n DEFAULT_THRESHOLD_BYTES,\n )}-byte default comes from the measured validation in ` +\n `docs/research/2026-09-02-agent-enforcement.md § 5.\\n`,\n );\n for (;;) {\n const answer = await ask(`threshold in bytes [${String(choices.thresholdBytes)}] (or back)> `);\n if (answer === 'back') return 'back';\n if (answer === '') return 'ok';\n if (/^\\d+$/.test(answer) && Number(answer) > 0) {\n choices.thresholdBytes = Number(answer);\n return 'ok';\n }\n io.output(`A whole number of bytes greater than zero, e.g. 8192.\\n`);\n }\n}\n\n/**\n * A re-run reads the toggles back off what is actually installed — every JSON hook\n * file this installer writes, plus the guard-only shim files, both derived from the\n * registry — so it edits instead of resetting. Harnesses that only wire the guard\n * (gemini, grok, cursor, hermes, opencode, cline) persist no stats/map entries, so\n * after a re-run scoped to them those toggles read back as off; the defaults below\n * apply only when nothing of smelt's is installed at all.\n *\n * The two `SessionStart` toggles share one event, so they are told apart by **the\n * command each entry runs**, not by the key it sits under. Reading `SessionStart` as\n * one boolean would make a re-run with the map on and the lint off write both back —\n * or neither — which is a toggle the user believed they had set.\n */\nfunction presetToggles(\n cwd: string,\n): Pick<HooksChoices, 'guard' | 'statsOnStop' | 'mapOnStart' | 'lintOnStart'> {\n const defaults = { guard: true, statsOnStop: true, mapOnStart: false, lintOnStart: false };\n let anyOurs = false;\n let guard = false;\n let statsOnStop = false;\n let mapOnStart = false;\n let lintOnStart = false;\n\n for (const name of JSON_HOOK_FILES) {\n const text = readIfExists(join(cwd, name));\n if (text === undefined) continue;\n let hooks: Record<string, unknown> | undefined;\n try {\n const parsed: unknown = JSON.parse(text);\n const hooksValue =\n typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)['hooks']\n : undefined;\n hooks =\n typeof hooksValue === 'object' && hooksValue !== null && !Array.isArray(hooksValue)\n ? (hooksValue as Record<string, unknown>)\n : undefined;\n } catch {\n hooks = undefined;\n }\n if (hooks === undefined) continue;\n const installed = hooks;\n const oursUnder = (event: string): readonly unknown[] =>\n Array.isArray(installed[event])\n ? (installed[event] as unknown[]).filter((entry) => isOursEntry(entry))\n : [];\n const hasOurs = (event: string): boolean => oursUnder(event).length > 0;\n /** One of ours under `event` whose command carries `needle`. */\n const hasOursRunning = (event: string, needle: string): boolean =>\n oursUnder(event).some((entry) => (JSON.stringify(entry) ?? '').includes(needle));\n if (!MANAGED_EVENTS.some((event) => hasOurs(event))) continue;\n anyOurs = true;\n guard ||= GUARD_EVENTS.some((event) => hasOurs(event));\n statsOnStop ||= hasOurs(LIFECYCLE_EVENTS.stats);\n mapOnStart ||= hasOursRunning(LIFECYCLE_EVENTS.map, MAP_ON_START_ARGS);\n lintOnStart ||= hasOursRunning(LIFECYCLE_EVENTS.lint, AGENTS_LINT_ARGS);\n }\n\n for (const name of GUARD_ONLY_FILES) {\n const text = readIfExists(join(cwd, name));\n if (text !== undefined && text.includes(OURS_TOKEN)) {\n anyOurs = true;\n guard = true;\n }\n }\n\n return anyOurs ? { guard, statsOnStop, mapOnStart, lintOnStart } : defaults;\n}\n\nconst fileLabel = (file: PlannedFile): string => {\n if (file.unchanged) return 'unchanged — nothing to write';\n return file.exists ? 'exists — will ask before overwriting' : 'new';\n};\n\nasync function confirmAndInstall(\n io: HooksIo,\n ask: Asker,\n choices: HooksChoices,\n): Promise<'done' | 'back'> {\n const plan = planInstall(io.cwd, choices);\n\n io.output(\n `\\nAbout to write, into ${io.cwd}:\\n` +\n plan.files.map((file) => ` ${file.name.padEnd(32)} (${fileLabel(file)})\\n`).join('') +\n plan.skipped.map((skip) => ` ${skip.name.padEnd(32)} (SKIPPED: ${skip.why})\\n`).join('') +\n `Nothing has been written yet.\\n`,\n );\n\n for (;;) {\n const answer = await ask(`confirm (yes / no / back)> `);\n if (answer === 'back') return 'back';\n if (answer === 'no') {\n io.output(`Nothing was written.\\n`);\n return 'done';\n }\n if (answer === 'yes') break;\n io.output(`yes to write, no to leave everything untouched, back to change a setting.\\n`);\n }\n\n for (const file of plan.files) {\n if (file.unchanged) {\n io.output(` ${file.name} — unchanged, not rewritten\\n`);\n continue;\n }\n if (file.exists) {\n // The one hard rule, same as `smelt init`: an existing file is never touched\n // without an explicit per-file yes — not `y`, not Enter, a literal `yes`.\n const answer = await ask(` ${file.name} exists — overwrite it? (yes/no)> `);\n if (answer !== 'yes') {\n io.output(` skipped ${file.name} — the existing file was not touched\\n`);\n continue;\n }\n }\n mkdirSync(dirname(file.path), { recursive: true });\n writeFileSync(file.path, file.content);\n if (file.mode !== undefined) chmodSync(file.path, file.mode);\n io.output(` wrote ${file.name}\\n`);\n }\n\n for (const note of plan.notes) io.output(`note: ${note}\\n`);\n io.output(\n `Done. Re-run \\`${CLI_NAME} hooks install\\` to edit toggles; ` +\n `\\`${CLI_NAME} hooks remove\\` takes it all back out.\\n`,\n );\n return 'done';\n}\n\nasync function removeFlow(\n io: HooksIo,\n ask: Asker,\n harnessFlag: string | undefined,\n): Promise<number> {\n const harnesses = harnessFlag !== undefined ? [resolveHarnessFlag(harnessFlag)] : [...HARNESSES];\n const removals = planRemove(io.cwd, harnesses);\n\n if (removals.length === 0) {\n io.output(`${CLI_NAME} hooks remove: nothing of smelt's found to remove in ${io.cwd}.\\n`);\n return 0;\n }\n\n io.output(\n `${CLI_NAME} hooks remove — takes smelt's hook wiring back out.\\n\\nPlanned:\\n` +\n removals\n .map(\n (removal) =>\n ` ${removal.name.padEnd(32)} (${\n removal.action === 'delete' ? 'delete' : 'remove smelt entries, keep the rest'\n })\\n`,\n )\n .join('') +\n `${CONFIG_FILE_NAME} is left untouched — its hooks block is your config now; ` +\n `edit or remove it there.\\nNothing has been changed yet.\\n`,\n );\n\n for (;;) {\n const answer = await ask(`confirm (yes / no)> `);\n if (answer === 'no') {\n io.output(`Nothing was changed.\\n`);\n return 0;\n }\n if (answer === 'yes') break;\n io.output(`yes to proceed, no to leave everything untouched.\\n`);\n }\n\n for (const removal of removals) {\n const verb = removal.action === 'delete' ? 'delete' : 'modify';\n const answer = await ask(` ${removal.name} — ${verb} it? (yes/no)> `);\n if (answer !== 'yes') {\n io.output(` skipped ${removal.name} — not touched\\n`);\n continue;\n }\n if (removal.action === 'delete') {\n unlinkSync(removal.path);\n io.output(` deleted ${removal.name}\\n`);\n } else {\n writeFileSync(removal.path, removal.content ?? '');\n io.output(` cleaned ${removal.name}\\n`);\n }\n }\n io.output(`Done.\\n`);\n return 0;\n}\n"]}
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/cli/hooks.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { CliUsageError } from '../errors.ts';\nimport { nodeCommand, portablePath, shimScriptPath, smeltBinPath } from '../harness/paths.ts';\nimport { hasShim, TIER_HONESTY } from '../harness/profile.ts';\nimport type {\n HarnessInstallContext,\n HarnessJsonHooks,\n HarnessProfile,\n} from '../harness/profile.ts';\nimport {\n GUARD_EVENTS,\n GUARD_ONLY_FILES,\n HARNESSES,\n harnessById,\n harnessNames,\n JSON_HOOK_FILES,\n LIFECYCLE_EVENTS,\n lifecycleHarnesses,\n MANAGED_EVENTS,\n} from '../harness/registry.ts';\nimport {\n instructionSnippet,\n OURS_TOKEN,\n SNIPPET_END_MD,\n SNIPPET_START_MD,\n} from '../harness/snippet.ts';\nimport { DEFAULT_SUGGESTION_BUDGET_BYTES, DEFAULT_THRESHOLD_BYTES } from '../hooks/guard-core.ts';\nimport type { EnforcementMode } from '../hooks/guard-core.ts';\nimport {\n editJsonProperty,\n editTopLevelProperty,\n jsonStyle,\n stripMarkerBlock,\n upsertMarkerBlock,\n} from '../text/json-edit.ts';\n\nimport { SETUP_RECIPE } from '../setup/recipe.ts';\nimport {\n confirmLoop,\n confirmYesNo,\n listPlannedFiles,\n walkSteps,\n wizardAsk,\n writePlannedFile,\n} from './wizard.ts';\nimport type { Ask } from './wizard.ts';\nimport { CLI_NAME } from './shell.ts';\nimport type { AnswerStream } from './shell.ts';\nimport {\n CONFIG_FILE_NAME,\n CONFIG_VERSION,\n findConfigFile,\n parseConfig,\n renderConfig,\n} from './config.ts';\nimport type { SmeltConfig, SmeltConfigHooks } from './config.ts';\n\n/**\n * `smelt hooks install` / `smelt hooks remove` — the multi-harness guard preset.\n *\n * The design: one zero-dependency guard core\n * (`src/hooks/guard-core.ts`), thin per-harness shims mapping each harness's native\n * hook schema onto it, and this installer, which writes the harness config that wires\n * a shim in — plus an instruction-file snippet as belt and braces, because the\n * snippet is also what teaches the model to run `smelt retrieve` after a deny.\n *\n * Every per-harness fact lives in that harness's {@link HarnessProfile}\n * (`src/harness/<id>.ts`), including what to write and how to take it back out. This\n * module owns only what is the *same* for every harness: the hooks merge (which entries\n * are ours, what a re-run replaces), the wizard, and the two plans below — folds over\n * `profile.install`, with no case list of its own. The byte-faithful editing itself —\n * one top-level JSON property, one delimited text block — is `src/text/json-edit.ts`,\n * which knows nothing about harnesses.\n *\n * Harnesses come in three honesty tiers (docs/research/2026-09-02-harness-capability-matrix.md),\n * and which harness sits at which is `HarnessProfile.tier` — read through\n * `harnessesByTier()`, never listed again here:\n *\n * - **verified** — schemas verified against primary docs and exercised against\n * recorded fixtures; first-class targets.\n * - **experimental** — hook schemas mapped from the capability matrix but not yet\n * smoke-tested green against the real binary. Labelled as such in code, docs, and\n * this installer's output.\n * - **advisory** — no usable hook API, so what ships is instructions (and, for\n * KiloCode, a permissions/MCP sketch). Nothing enforces them, and the output says\n * so rather than implying a guard exists.\n *\n * The wizard discipline is `smelt init`'s, verbatim: every step accepts `back`,\n * nothing is written until a final confirm that lists every file, and an existing\n * file is never overwritten without an explicit per-file `yes` — guarded by\n * `test/guards/hooks-preset.test.ts`, with mutation `hooks-install-overwrite-without-consent`\n * proving the guard goes red.\n */\n\n/** Where the wizard's bytes come from and go. Injected so `runHooks` tests in-process. */\nexport interface HooksIo {\n /**\n * Scripted answers in, one line at a time. Structural on purpose; see\n * {@link AnswerStream}.\n */\n readonly input: AnswerStream;\n readonly output: (text: string) => void;\n /** Project directory: detection, config discovery, and every write are relative to it. */\n readonly cwd: string;\n /** Home directory for detection only. Tests point it at a temp dir; nothing writes here. */\n readonly home?: string;\n /**\n * The release running the install — stamped into the instruction block so\n * `smelt doctor` can tell what wrote it. Absent (legacy callers) writes no stamp.\n */\n readonly version?: string;\n}\n\nexport { instructionSnippet, SNIPPET_END_MD, SNIPPET_START_MD };\n\n/** A harness whose config directory exists in the project or the home directory. */\nexport function detectedHarnesses(cwd: string, home: string): readonly HarnessProfile[] {\n return HARNESSES.filter(\n (profile) =>\n profile.detect.some((path) => existsSync(join(cwd, path))) ||\n profile.detectHome.some((path) => existsSync(join(home, path))),\n );\n}\n\n/* ------------------------------------------------------------------------------------\n * Generated content\n * ---------------------------------------------------------------------------------- */\n\n/** Claude-style hook entry: one command under an optional matcher. */\nfunction commandEntry(matcher: string | undefined, command: string): unknown {\n return {\n ...(matcher === undefined ? {} : { matcher }),\n hooks: [{ type: 'command', command }],\n };\n}\n\n/**\n * The hook command a harness's entries run: its own shim script, through node.\n *\n * @throws {Error} when a profile declares a JSON hook file but ships no shim — a\n * registry bug, pinned by `test/guards/harness-registry.test.ts`, not a user error.\n */\nfunction shimCommand(profile: HarnessProfile, cwd: string): string {\n /* v8 ignore next 5 -- unreachable: pinned by the harness-registry guard */\n if (!hasShim(profile)) {\n throw new Error(\n `smelt: harness \"${profile.id}\" wires a hook command but ships no shim script.`,\n );\n }\n return nodeCommand(cwd, shimScriptPath(profile));\n}\n\n/**\n * One harness's hook entries: the guard under each matcher its schema spells, plus\n * the session-lifecycle hooks for the harnesses whose schema carries them. Every\n * toggle the wizard offers is a key that is present or absent here — an absent key is\n * how a re-run turns a toggle *off*, because the merge deletes what it no longer sees.\n *\n * The two `SessionStart` toggles — the opening map and the instruction-file lint —\n * are **concatenated into one array**, not spread as two objects. Spreading would put\n * the same computed key twice in one literal, and the second would silently replace\n * the first: turning the lint on would turn the map off, with no error anywhere. It is\n * the shape of bug this file exists to refuse, one layer up from the config it writes.\n */\nfunction jsonHookEvents(\n step: HarnessJsonHooks,\n ctx: HarnessInstallContext,\n command: string,\n): Record<string, readonly unknown[]> {\n // The trailing shell comment tags the entry as this installer's (see isOursEntry):\n // a bare `cli/bin.js` substring would also match some other npm CLI's built binary.\n const stats = `${nodeCommand(ctx.cwd, smeltBinPath(), 'stats')} 2>/dev/null || true # ${OURS_TOKEN}`;\n const map = `${nodeCommand(\n ctx.cwd,\n smeltBinPath(),\n `${MAP_ON_START_ARGS} --budget ${String(ctx.budgetBytes)} --cache .smelt/tags`,\n )} 2>/dev/null || true # ${OURS_TOKEN}`;\n const lint = `${nodeCommand(\n ctx.cwd,\n smeltBinPath(),\n AGENTS_LINT_ARGS,\n )} 2>/dev/null || true # ${OURS_TOKEN}`;\n\n const sessionStart = [\n ...(ctx.mapOnStart ? [commandEntry(SESSION_START_MATCHER, map)] : []),\n ...(ctx.lintOnStart ? [commandEntry(SESSION_START_MATCHER, lint)] : []),\n ];\n\n return {\n ...(ctx.guard\n ? {\n [step.event]: step.matchers.map((matcher) =>\n step.entry === 'bare-command' ? { command } : commandEntry(matcher, command),\n ),\n }\n : {}),\n ...(step.lifecycle && ctx.statsOnStop\n ? { [LIFECYCLE_EVENTS.stats]: [commandEntry(undefined, stats)] }\n : {}),\n ...(step.lifecycle && sessionStart.length > 0 ? { [LIFECYCLE_EVENTS.map]: sessionStart } : {}),\n };\n}\n\n/** The matcher both `SessionStart` entries fire under — a session opening, however. */\nconst SESSION_START_MATCHER = 'startup|resume|clear|compact';\n\n/**\n * The two `SessionStart` commands' distinguishing arguments, and **the substrings a\n * re-run recognises each entry by**. Spelled once so the writer and the reader cannot\n * drift: `presetToggles` tells the two entries apart by the command each one runs, and\n * a wizard that wrote `agents lint .` while its reader looked for `agents lint` would\n * read every re-run's lint toggle back as off and quietly delete it.\n */\nconst MAP_ON_START_ARGS = 'map .';\nexport const AGENTS_LINT_ARGS = 'agents lint .';\n\n/**\n * True for a hook entry this installer wrote. Matched on the shim script paths and the\n * `smelt:hooks` token the stats/map commands carry — never on a substring as generic\n * as `cli/bin.js`, which another npm CLI's built binary could share: remove and\n * re-install may only ever touch entries that are provably smelt's.\n */\nfunction isOursEntry(entry: unknown): boolean {\n const text = JSON.stringify(entry) ?? '';\n return text.includes('hooks/shims/') || text.includes(OURS_TOKEN);\n}\n\n/**\n * Merge our hook entries into a JSON settings file, preserving everything foreign\n * **byte-faithfully**: the merged `hooks` value is spliced into the original text, so\n * unknown top-level keys, string escapes, number spellings, indentation and key order\n * outside the `hooks` property ride through verbatim (an installer\n * that reformats somebody's settings file has edited what it was never asked to).\n * Inside `hooks`, unmanaged events and other people's entries under managed events\n * are preserved; our previous entries are replaced (that is what makes a re-run edit\n * toggles), and events left with no entries disappear. A semantic no-op returns the\n * input text unchanged. Returns `undefined` when the existing file is not a JSON\n * object — the caller skips the file rather than clobbering something it cannot\n * understand.\n */\nexport function mergeJsonHooks(\n existingText: string | undefined,\n events: Record<string, readonly unknown[]>,\n shape: { readonly version?: number } = {},\n): string | undefined {\n let root: Record<string, unknown> = {};\n if (existingText !== undefined) {\n try {\n const parsed: unknown = JSON.parse(existingText);\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return undefined;\n root = parsed as Record<string, unknown>;\n } catch {\n return undefined;\n }\n }\n const hooksValue = root['hooks'];\n const existingHooks =\n typeof hooksValue === 'object' && hooksValue !== null && !Array.isArray(hooksValue)\n ? (hooksValue as Record<string, unknown>)\n : undefined;\n const hooks = { ...existingHooks };\n\n for (const event of MANAGED_EVENTS) {\n const existing = Array.isArray(hooks[event]) ? (hooks[event] as unknown[]) : [];\n const foreign = existing.filter((entry) => !isOursEntry(entry));\n const ours = events[event] ?? [];\n const merged = [...foreign, ...ours];\n if (merged.length > 0) hooks[event] = merged;\n else delete hooks[event];\n }\n\n const mergedHooks = Object.keys(hooks).length > 0 ? hooks : undefined;\n\n // A brand-new file: nothing to preserve, render fresh two-space JSON.\n if (existingText === undefined) {\n const fresh: Record<string, unknown> = {};\n if (mergedHooks !== undefined) fresh['hooks'] = mergedHooks;\n if (shape.version !== undefined) fresh['version'] = shape.version;\n return `${JSON.stringify(fresh, null, 2)}\\n`;\n }\n\n const hooksChanged =\n JSON.stringify(existingHooks ?? null) !== JSON.stringify(mergedHooks ?? null);\n const needsVersion = shape.version !== undefined && root['version'] === undefined;\n if (!hooksChanged && !needsVersion) return existingText;\n\n // The style is read once, off the original: a second edit must match the first.\n const style = jsonStyle(existingText);\n let text: string | undefined = existingText;\n\n if (hooksChanged) {\n text = editTopLevelProperty(text, 'hooks', mergedHooks, style);\n /* v8 ignore next -- unreachable: JSON.parse accepted the same text above */\n if (text === undefined) return undefined;\n }\n if (needsVersion) {\n text = editTopLevelProperty(text, 'version', shape.version, style);\n /* v8 ignore next -- unreachable: every splice above keeps the text valid JSON */\n if (text === undefined) return undefined;\n }\n return text;\n}\n\n/* ------------------------------------------------------------------------------------\n * Planning\n * ---------------------------------------------------------------------------------- */\n\ninterface PlannedFile {\n /** Display path, relative to the project. */\n readonly name: string;\n readonly path: string;\n readonly content: string;\n readonly exists: boolean;\n readonly unchanged: boolean;\n /** chmod after writing (the cline hook must be executable). */\n readonly mode?: number;\n}\n\ninterface SkippedFile {\n readonly name: string;\n readonly why: string;\n}\n\ninterface PlannedRemoval {\n readonly name: string;\n readonly path: string;\n /** `'delete'` removes the file; `'modify'` writes `content` (ours stripped out). */\n readonly action: 'delete' | 'modify';\n readonly content?: string;\n}\n\nexport interface HooksChoices {\n harnesses: HarnessProfile[];\n /** The release writing these bytes — stamped into the snippet for `smelt doctor`. */\n writtenBy?: string;\n guard: boolean;\n statsOnStop: boolean;\n mapOnStart: boolean;\n lintOnStart: boolean;\n enforcement: EnforcementMode;\n thresholdBytes: number;\n}\n\ninterface InstallPlan {\n readonly files: readonly PlannedFile[];\n readonly skipped: readonly SkippedFile[];\n readonly notes: readonly string[];\n}\n\nfunction planFile(cwd: string, name: string, content: string, mode?: number): PlannedFile {\n const path = join(cwd, name);\n const exists = existsSync(path);\n const unchanged = exists && readFileSync(path, 'utf8') === content;\n return { name, path, content, exists, unchanged, ...(mode === undefined ? {} : { mode }) };\n}\n\nfunction readIfExists(path: string): string | undefined {\n return existsSync(path) ? readFileSync(path, 'utf8') : undefined;\n}\n\n/**\n * Every file `install` would write, computed against the current disk state — pure\n * planning, nothing written. A fold over each chosen profile's `install` list and its\n * instruction layer; all per-harness knowledge is in the profiles. Shared instruction\n * files (several harnesses read AGENTS.md) are planned once.\n *\n * @throws {CliUsageError} when an existing `smelt.config.json` is malformed — the\n * same refusal every other subcommand makes; an installer that guessed around a\n * broken config would write settings the guard then ignores.\n */\nexport function planInstall(cwd: string, choices: HooksChoices): InstallPlan {\n const files = new Map<string, PlannedFile>();\n const skipped: SkippedFile[] = [];\n const notes: string[] = [];\n\n // -- smelt.config.json: the guard's runtime settings live here, not in any harness\n // file, so every shim reads one source of truth.\n const configPath = findConfigFile(cwd) ?? join(cwd, CONFIG_FILE_NAME);\n const existingConfig =\n readIfExists(configPath) === undefined\n ? undefined\n : parseConfig(readFileSync(configPath, 'utf8'), configPath);\n const hooksBlock: SmeltConfigHooks = {\n thresholdBytes: choices.thresholdBytes,\n enforcement: choices.enforcement,\n };\n const budgetBytes = existingConfig?.defaultBudgetBytes ?? DEFAULT_SUGGESTION_BUDGET_BYTES;\n files.set(configPath, {\n name: portablePath(cwd, configPath),\n path: configPath,\n content: renderConfigWithHooks(existingConfig, hooksBlock),\n exists: existsSync(configPath),\n unchanged: readIfExists(configPath) === renderConfigWithHooks(existingConfig, hooksBlock),\n });\n\n const ctx: HarnessInstallContext = {\n cwd,\n ...(choices.writtenBy === undefined ? {} : { writtenBy: choices.writtenBy }),\n guard: choices.guard,\n statsOnStop: choices.statsOnStop,\n mapOnStart: choices.mapOnStart,\n lintOnStart: choices.lintOnStart,\n thresholdBytes: choices.thresholdBytes,\n budgetBytes,\n };\n const snippet = instructionSnippet(choices.thresholdBytes, budgetBytes, choices.writtenBy);\n\n const planJsonHooks = (\n name: string,\n events: Record<string, readonly unknown[]>,\n shape: { readonly version?: number } = {},\n ): void => {\n const path = join(cwd, name);\n // Nothing to install and nothing to strip: don't create an empty hooks file.\n if (Object.keys(events).length === 0 && !existsSync(path)) return;\n const merged = mergeJsonHooks(readIfExists(path), events, shape);\n if (merged === undefined) {\n skipped.push({\n name,\n why: 'exists but is not a JSON object — fix or remove it, then re-run',\n });\n return;\n }\n files.set(path, planFile(cwd, name, merged));\n };\n\n const planBlockFile = (\n name: string,\n block: string,\n start: string,\n end: string,\n skipWhen?: { readonly contains: string; readonly why: string },\n ): void => {\n const path = join(cwd, name);\n if (files.has(path)) return; // a shared file (AGENTS.md), already planned\n const existing = readIfExists(path);\n // A file that already carries its owner's version of what this block does is\n // theirs to edit, not ours: say so, and touch nothing.\n if (\n skipWhen !== undefined &&\n existing !== undefined &&\n !existing.includes(start) &&\n existing.includes(skipWhen.contains)\n ) {\n skipped.push({ name, why: skipWhen.why });\n return;\n }\n files.set(path, planFile(cwd, name, upsertMarkerBlock(existing, block, start, end)));\n };\n\n for (const profile of choices.harnesses) {\n for (const step of profile.install) {\n switch (step.kind) {\n case 'json-hooks':\n planJsonHooks(\n step.file,\n jsonHookEvents(step, ctx, shimCommand(profile, cwd)),\n step.shape ?? {},\n );\n break;\n case 'marker-block':\n planBlockFile(step.file, step.block(ctx), step.start, step.end, step.skipWhen);\n break;\n case 'own-file':\n if (step.guardOnly && !ctx.guard) break;\n files.set(join(cwd, step.file), planFile(cwd, step.file, step.content(ctx), step.mode));\n break;\n case 'mcp-registration': {\n // Byte-faithful beside whatever servers the user already registered —\n // sibling entries, key order and indentation all ride through.\n const existing = readIfExists(join(cwd, step.file));\n const merged = editJsonProperty(\n existing ?? '{}',\n step.path,\n step.entry(ctx),\n existing === undefined ? undefined : jsonStyle(existing),\n );\n if (merged === undefined) {\n skipped.push({\n name: step.file,\n why: 'exists but is not a JSON object — fix or remove it, then re-run',\n });\n break;\n }\n files.set(join(cwd, step.file), planFile(cwd, step.file, merged));\n break;\n }\n }\n }\n\n if (profile.instructions === 'snippet') {\n planBlockFile(profile.instructionFile, snippet, SNIPPET_START_MD, SNIPPET_END_MD);\n } else {\n files.set(\n join(cwd, profile.instructionFile),\n planFile(cwd, profile.instructionFile, profile.instructions(ctx)),\n );\n }\n\n for (const caveat of profile.caveats) notes.push(`${profile.name}: ${caveat}`);\n }\n\n return { files: [...files.values()], skipped, notes };\n}\n\n/** Where the installed config points the persistent store, relative to the config file. */\nexport const DEFAULT_STORE_DIR = SETUP_RECIPE.store.defaultDir;\n\n/**\n * Existing config re-rendered with the hooks block, other fields carried verbatim —\n * except that a config with **no** store block gains a directory store. The deny\n * reasons and the instruction snippet teach `smelt retrieve <hash>`, and retrieval\n * across processes needs a persistent store (`smelt retrieve` refuses a memory\n * store, exit 2) — an install whose own guard promises a command the installed\n * config cannot run would be the exact silent-failure shape this project refuses.\n * An *explicit* `{\"kind\":\"memory\"}` is respected; the guard then conditions its\n * retrieve promise on the store kind instead (`retrieveSentence` in guard-core).\n *\n * That store injection is this verb's **policy**, which is why it lives here; the\n * bytes are written by `renderConfig` in `config.ts`, the one writer, so a key added\n * to the schema reaches this file and `init`'s together or not at all.\n *\n * \"Carried verbatim\" is spelled as a spread rather than as a list of the fields to\n * copy, and that is load-bearing: the list version silently dropped every key nobody\n * remembered to add to it — `agents` was added to the schema and this function kept\n * writing configs without it, which is a setting the user believed was in force,\n * caught by `test/guards/config-writer.test.ts`. Only the two fields this verb\n * actually decides are named.\n */\nexport function renderConfigWithHooks(\n existing: SmeltConfig | undefined,\n hooks: SmeltConfigHooks,\n): string {\n return renderConfig({\n ...existing,\n smeltConfig: CONFIG_VERSION,\n store: existing?.store ?? { kind: 'directory', path: DEFAULT_STORE_DIR },\n hooks,\n });\n}\n\n/**\n * Everything `remove` would delete or strip, computed against the current disk state.\n * The mirror image of {@link planInstall}, over the same data: each install step's\n * kind is also how it comes back out — a JSON hook file is strip-merged, a marker\n * block is stripped, a file that is entirely ours is deleted.\n */\nexport function planRemove(\n cwd: string,\n harnesses: readonly HarnessProfile[],\n): readonly PlannedRemoval[] {\n const removals = new Map<string, PlannedRemoval>();\n\n const planJsonStrip = (name: string): void => {\n const path = join(cwd, name);\n const existing = readIfExists(path);\n if (existing === undefined) return;\n const stripped = mergeJsonHooks(existing, {});\n if (stripped === undefined || stripped === existing) return;\n const remains: unknown = JSON.parse(stripped);\n const empty =\n typeof remains === 'object' &&\n remains !== null &&\n Object.keys(remains as Record<string, unknown>).filter((key) => key !== 'version').length ===\n 0;\n removals.set(\n path,\n empty && existing.includes('hooks')\n ? { name, path, action: 'delete' }\n : { name, path, action: 'modify', content: stripped },\n );\n };\n\n const planBlockStrip = (name: string, start: string, end: string): void => {\n const path = join(cwd, name);\n const existing = readIfExists(path);\n if (existing === undefined || !existing.includes(start)) return;\n const stripped = stripMarkerBlock(existing, start, end);\n removals.set(\n path,\n stripped === undefined\n ? { name, path, action: 'delete' }\n : { name, path, action: 'modify', content: stripped },\n );\n };\n\n const planWholeFileDelete = (name: string): void => {\n const path = join(cwd, name);\n const existing = readIfExists(path);\n if (existing === undefined || !existing.includes(OURS_TOKEN)) return;\n removals.set(path, { name, path, action: 'delete' });\n };\n\n /**\n * The registration comes back out the way it went in: the server entry lifted,\n * byte-faithfully, from its container. A container this install created — empty\n * once the entry is gone — is removed with it, so a file that never carried the\n * key round-trips to byte-identical; one that carries other servers keeps them.\n */\n const planMcpStrip = (name: string, keys: readonly [string, string]): void => {\n const path = join(cwd, name);\n const existing = readIfExists(path);\n if (existing === undefined) return;\n const removed = editJsonProperty(existing, keys, undefined);\n if (removed === undefined || removed === existing) return;\n let remains: unknown;\n try {\n remains = JSON.parse(removed);\n } catch {\n return; // unreachable — the editor only returns parseable text; refuse to guess\n }\n const empty =\n typeof remains === 'object' && remains !== null && Object.keys(remains).length === 0;\n removals.set(\n path,\n empty ? { name, path, action: 'delete' } : { name, path, action: 'modify', content: removed },\n );\n };\n\n for (const profile of harnesses) {\n for (const step of profile.install) {\n switch (step.kind) {\n case 'json-hooks':\n planJsonStrip(step.file);\n break;\n case 'marker-block':\n planBlockStrip(step.file, step.start, step.end);\n break;\n case 'own-file':\n planWholeFileDelete(step.file);\n break;\n case 'mcp-registration':\n planMcpStrip(step.file, step.path);\n break;\n }\n }\n if (profile.instructions === 'snippet') {\n planBlockStrip(profile.instructionFile, SNIPPET_START_MD, SNIPPET_END_MD);\n } else {\n planWholeFileDelete(profile.instructionFile);\n }\n }\n\n return [...removals.values()];\n}\n\n/* ------------------------------------------------------------------------------------\n * The wizard\n * ---------------------------------------------------------------------------------- */\n\ntype Asker = Ask;\n\n/**\n * `smelt hooks <install|remove>`, start to finish. The same testability pattern as\n * `runInit`: a pure function over an input/output pair, exit code returned. The ask\n * adapter, the step machine and the confirms are the wizard kit's (`cli/wizard.ts`) —\n * this file holds what is hooks' own: the steps, the plan, the per-file consent.\n */\nexport async function runHooks(\n action: 'install' | 'remove',\n harnessFlag: string | undefined,\n io: HooksIo,\n): Promise<number> {\n const wizard = wizardAsk(\n io.input,\n io.output,\n `${CLI_NAME} hooks: input ended before the wizard finished. ` +\n `Files already confirmed and written stay; nothing further was written.`,\n );\n try {\n return action === 'install'\n ? await installFlow(io, wizard.ask, harnessFlag)\n : await removeFlow(io, wizard.ask, harnessFlag);\n } finally {\n await wizard.release();\n }\n}\n\nfunction resolveHarnessFlag(flag: string): HarnessProfile {\n const profile = harnessById(flag);\n if (profile === undefined) {\n throw new CliUsageError(\n `${CLI_NAME} hooks: unknown harness \"${flag}\". ` +\n `Known: ${HARNESSES.map((h) => h.id).join(', ')}.`,\n );\n }\n return profile;\n}\n\nfunction tierLabel(profile: HarnessProfile): string {\n return `${profile.id.padEnd(12)} ${profile.name.padEnd(14)} [${profile.tier}] — ${TIER_HONESTY[profile.tier]}`;\n}\n\nasync function installFlow(\n io: HooksIo,\n ask: Asker,\n harnessFlag: string | undefined,\n): Promise<number> {\n const home = io.home ?? homedir();\n const detected = detectedHarnesses(io.cwd, home);\n\n io.output(\n `${CLI_NAME} hooks install — wires the smelt guard into agent-harness hooks.\\n` +\n `Answer \\`back\\` at any step to return to the previous one. Nothing is written ` +\n `until you confirm at the end.\\n\\n`,\n );\n\n const choices: HooksChoices = {\n harnesses: harnessFlag !== undefined ? [resolveHarnessFlag(harnessFlag)] : [...detected],\n ...(io.version === undefined ? {} : { writtenBy: io.version }),\n ...presetToggles(io.cwd),\n enforcement: 'deny',\n thresholdBytes: DEFAULT_THRESHOLD_BYTES,\n };\n\n // With --harness the selection step is skipped, so the tier label — and its one\n // line of honesty about what the tier means — is printed here instead.\n if (harnessFlag !== undefined) {\n for (const profile of choices.harnesses) io.output(` ${tierLabel(profile)}\\n`);\n }\n\n const steps: readonly ((io_: HooksIo, ask_: Asker) => Promise<'ok' | 'back'>)[] = [\n async (io_, ask_) =>\n harnessFlag !== undefined ? 'ok' : stepHarnesses(io_, ask_, choices, detected),\n async (io_, ask_) =>\n stepToggle(io_, ask_, 'PreToolUse size-guard', guardCopy(), choices.guard, (on) => {\n choices.guard = on;\n }),\n async (io_, ask_) =>\n stepToggle(\n io_,\n ask_,\n 'stats on Stop',\n `\\`smelt stats\\` runs when a session ends — the honest signal (expansion rate) ` +\n `surfaced where the turn ends. Observation only; never blocks. Wired for the ` +\n `harnesses whose hooks carry session events (${harnessNames(lifecycleHarnesses())}).`,\n choices.statsOnStop,\n (on) => {\n choices.statsOnStop = on;\n },\n ),\n async (io_, ask_) =>\n stepToggle(\n io_,\n ask_,\n 'repo map on SessionStart',\n `\\`smelt map . --budget …\\` runs at session start and its output opens the ` +\n `context — the agent starts oriented. Costs one map build per session. ` +\n `Wired for the harnesses whose hooks carry session events ` +\n `(${harnessNames(lifecycleHarnesses())}).`,\n choices.mapOnStart,\n (on) => {\n choices.mapOnStart = on;\n },\n ),\n async (io_, ask_) =>\n stepToggle(\n io_,\n ask_,\n 'instruction-file lint on SessionStart',\n `\\`smelt agents lint .\\` runs at session start and reports on the AGENTS.md, ` +\n `CLAUDE.md and GEMINI.md this session is about to load on every request — ` +\n `bytes per level, and any path or link in them that no longer resolves. ` +\n `Advisory: it never blocks, and it exits 0 unless you set ` +\n `agents.budgetBytes in ${CONFIG_FILE_NAME}. Wired for verified-tier ` +\n `harnesses (Claude Code, Codex).`,\n choices.lintOnStart,\n (on) => {\n choices.lintOnStart = on;\n },\n ),\n async (io_, ask_) => stepEnforcement(io_, ask_, choices),\n async (io_, ask_) => stepThreshold(io_, ask_, choices),\n ];\n\n const machine = steps.map((step) => (a: Ask) => step(io, a));\n for (;;) {\n await walkSteps(machine, ask, io.output);\n if (choices.harnesses.length === 0) {\n io.output(`No harness selected. Nothing to do; nothing was written.\\n`);\n return 0;\n }\n const verdict = await confirmAndInstall(io, ask, choices);\n if (verdict !== 'back') return 0;\n await walkSteps(machine, ask, io.output, machine.length - 1);\n }\n}\n\nfunction guardCopy(): string {\n return (\n `Denies raw Reads (and simple \\`cat\\`s) of files over the size threshold, with a ` +\n `reason naming the exact \\`smelt\\` replacement — the model still sees everything: ` +\n `smelted first, \\`smelt retrieve\\` for the rest. Windowed reads (offset/limit) ` +\n `always pass.`\n );\n}\n\nasync function stepHarnesses(\n io: HooksIo,\n ask: Asker,\n choices: HooksChoices,\n detected: readonly HarnessProfile[],\n): Promise<'ok' | 'back'> {\n io.output(`\\nHarnesses — detected by their config directories (project or home):\\n`);\n for (const profile of HARNESSES) {\n const mark = detected.includes(profile) ? '*' : ' ';\n io.output(` ${mark} ${tierLabel(profile)}\\n`);\n }\n io.output(`(* = detected here)\\n`);\n for (;;) {\n const current = choices.harnesses.map((profile) => profile.id).join(',') || '(none)';\n const answer = await ask(\n `install for which? (comma-separated ids, Enter = ${current}, or back)\\n> `,\n );\n if (answer === 'back') return 'back';\n if (answer === '') return 'ok';\n const ids = answer\n .split(',')\n .map((id) => id.trim())\n .filter((id) => id !== '');\n const chosen: HarnessProfile[] = [];\n let bad: string | undefined;\n for (const id of ids) {\n const profile = harnessById(id);\n if (profile === undefined) bad = id;\n else if (!chosen.includes(profile)) chosen.push(profile);\n }\n if (bad !== undefined) {\n io.output(`Unknown harness \"${bad}\". Known: ${HARNESSES.map((h) => h.id).join(', ')}.\\n`);\n continue;\n }\n choices.harnesses = chosen;\n return 'ok';\n }\n}\n\nasync function stepToggle(\n io: HooksIo,\n ask: Asker,\n name: string,\n copy: string,\n current: boolean,\n set: (on: boolean) => void,\n): Promise<'ok' | 'back'> {\n io.output(`\\n${name} — ${copy}\\n`);\n for (;;) {\n const answer = await ask(`${name}? (on/off) [${current ? 'on' : 'off'}] (or back)> `);\n if (answer === 'back') return 'back';\n if (answer === '') return 'ok';\n if (answer === 'on' || answer === 'off') {\n set(answer === 'on');\n return 'ok';\n }\n io.output(`on, off, or back.\\n`);\n }\n}\n\nasync function stepEnforcement(\n io: HooksIo,\n ask: Asker,\n choices: HooksChoices,\n): Promise<'ok' | 'back'> {\n io.output(\n `\\nEnforcement — what happens when the guard catches an oversized raw read:\\n` +\n ` 1. deny — refuse with a reason naming the exact replacement command. The\\n` +\n ` transcript stays truthful; the model runs the replacement itself.\\n` +\n ` 2. rewrite — on harnesses whose hooks can modify tool input, substitute the\\n` +\n ` replacement in-flight (grep/cat piped through smelt). Never\\n` +\n ` silent: the substitution is announced in the decision reason\\n` +\n ` where the harness has one, on stderr where it does not.\\n` +\n ` Harnesses that cannot rewrite fall back to deny.\\n`,\n );\n for (;;) {\n const current = choices.enforcement === 'deny' ? '1' : '2';\n const answer = await ask(`enforcement (1/2) [${current}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? current : answer;\n if (pick === '1' || pick === '2') {\n choices.enforcement = pick === '1' ? 'deny' : 'rewrite';\n return 'ok';\n }\n io.output(`1 for deny, 2 for rewrite, or back.\\n`);\n }\n}\n\nasync function stepThreshold(\n io: HooksIo,\n ask: Asker,\n choices: HooksChoices,\n): Promise<'ok' | 'back'> {\n io.output(\n `\\nSize threshold — reads at or under this many bytes always pass. The ${String(\n DEFAULT_THRESHOLD_BYTES,\n )}-byte default comes from the measured validation in ` +\n `docs/research/2026-09-02-agent-enforcement.md § 5.\\n`,\n );\n for (;;) {\n const answer = await ask(`threshold in bytes [${String(choices.thresholdBytes)}] (or back)> `);\n if (answer === 'back') return 'back';\n if (answer === '') return 'ok';\n if (/^\\d+$/.test(answer) && Number(answer) > 0) {\n choices.thresholdBytes = Number(answer);\n return 'ok';\n }\n io.output(`A whole number of bytes greater than zero, e.g. 8192.\\n`);\n }\n}\n\n/**\n * A re-run reads the toggles back off what is actually installed — every JSON hook\n * file this installer writes, plus the guard-only shim files, both derived from the\n * registry — so it edits instead of resetting. Harnesses that only wire the guard\n * (gemini, grok, cursor, hermes, opencode, cline) persist no stats/map entries, so\n * after a re-run scoped to them those toggles read back as off; the defaults below\n * apply only when nothing of smelt's is installed at all.\n *\n * The two `SessionStart` toggles share one event, so they are told apart by **the\n * command each entry runs**, not by the key it sits under. Reading `SessionStart` as\n * one boolean would make a re-run with the map on and the lint off write both back —\n * or neither — which is a toggle the user believed they had set.\n *\n * Exported for `smelt setup`, which applies the preset's *current* state the same way\n * — read off what is installed — rather than keeping a second copy of the defaults.\n */\n/**\n * Whether a JSON hook file's text carries entries of ours — the **one** predicate for\n * this fact, shared by the readers (`smelt doctor`, `smelt setup`'s repair policy) and\n * backed by the same `isOursEntry` the writer's strip-merge uses. The guard command\n * itself carries only the shim path (no token), so a text-level `OURS_TOKEN` search\n * would miss a guard-only install — the exact drift this exists to prevent.\n */\nexport function jsonHooksContainOurs(text: string): boolean {\n let hooks: Record<string, unknown> | undefined;\n try {\n const parsed: unknown = JSON.parse(text);\n const hooksValue =\n typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)['hooks']\n : undefined;\n hooks =\n typeof hooksValue === 'object' && hooksValue !== null && !Array.isArray(hooksValue)\n ? (hooksValue as Record<string, unknown>)\n : undefined;\n } catch {\n hooks = undefined;\n }\n if (hooks === undefined) return false;\n return MANAGED_EVENTS.some((event) =>\n Array.isArray(hooks[event])\n ? (hooks[event] as unknown[]).some((entry) => isOursEntry(entry))\n : false,\n );\n}\n\nexport function presetToggles(\n cwd: string,\n): Pick<HooksChoices, 'guard' | 'statsOnStop' | 'mapOnStart' | 'lintOnStart'> {\n const defaults = { guard: true, statsOnStop: true, mapOnStart: false, lintOnStart: false };\n let anyOurs = false;\n let guard = false;\n let statsOnStop = false;\n let mapOnStart = false;\n let lintOnStart = false;\n\n for (const name of JSON_HOOK_FILES) {\n const text = readIfExists(join(cwd, name));\n if (text === undefined) continue;\n let hooks: Record<string, unknown> | undefined;\n try {\n const parsed: unknown = JSON.parse(text);\n const hooksValue =\n typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)['hooks']\n : undefined;\n hooks =\n typeof hooksValue === 'object' && hooksValue !== null && !Array.isArray(hooksValue)\n ? (hooksValue as Record<string, unknown>)\n : undefined;\n } catch {\n hooks = undefined;\n }\n if (hooks === undefined) continue;\n const installed = hooks;\n const oursUnder = (event: string): readonly unknown[] =>\n Array.isArray(installed[event])\n ? (installed[event] as unknown[]).filter((entry) => isOursEntry(entry))\n : [];\n const hasOurs = (event: string): boolean => oursUnder(event).length > 0;\n /** One of ours under `event` whose command carries `needle`. */\n const hasOursRunning = (event: string, needle: string): boolean =>\n oursUnder(event).some((entry) => (JSON.stringify(entry) ?? '').includes(needle));\n if (!MANAGED_EVENTS.some((event) => hasOurs(event))) continue;\n anyOurs = true;\n guard ||= GUARD_EVENTS.some((event) => hasOurs(event));\n statsOnStop ||= hasOurs(LIFECYCLE_EVENTS.stats);\n mapOnStart ||= hasOursRunning(LIFECYCLE_EVENTS.map, MAP_ON_START_ARGS);\n lintOnStart ||= hasOursRunning(LIFECYCLE_EVENTS.lint, AGENTS_LINT_ARGS);\n }\n\n for (const name of GUARD_ONLY_FILES) {\n const text = readIfExists(join(cwd, name));\n if (text !== undefined && text.includes(OURS_TOKEN)) {\n anyOurs = true;\n guard = true;\n }\n }\n\n return anyOurs ? { guard, statsOnStop, mapOnStart, lintOnStart } : defaults;\n}\n\nconst fileLabel = (file: PlannedFile): string => {\n if (file.unchanged) return 'unchanged — nothing to write';\n return file.exists ? 'exists — will ask before overwriting' : 'new';\n};\n\nasync function confirmAndInstall(\n io: HooksIo,\n ask: Asker,\n choices: HooksChoices,\n): Promise<'done' | 'back'> {\n const plan = planInstall(io.cwd, choices);\n\n io.output(`\\nAbout to write, into ${io.cwd}:\\n`);\n listPlannedFiles(io.output, plan.files, plan.skipped, fileLabel);\n io.output(`Nothing has been written yet.\\n`);\n\n const confirmed = await confirmLoop(\n ask,\n 'yes to write, no to leave everything untouched, back to change a setting.',\n );\n if (confirmed === 'back') return 'back';\n if (confirmed === 'no') {\n io.output(`Nothing was written.\\n`);\n return 'done';\n }\n\n for (const file of plan.files) {\n if (file.unchanged) {\n io.output(` ${file.name} — unchanged, not rewritten\\n`);\n continue;\n }\n if (file.exists) {\n // The one hard rule, same as `smelt init`: an existing file is never touched\n // without an explicit per-file yes — not `y`, not Enter, a literal `yes`.\n const answer = await ask(` ${file.name} exists — overwrite it? (yes/no)> `);\n if (answer !== 'yes') {\n io.output(` skipped ${file.name} — the existing file was not touched\\n`);\n continue;\n }\n }\n writePlannedFile(file);\n io.output(` wrote ${file.name}\\n`);\n }\n\n for (const note of plan.notes) io.output(`note: ${note}\\n`);\n io.output(\n `Done. Re-run \\`${CLI_NAME} hooks install\\` to edit toggles; ` +\n `\\`${CLI_NAME} hooks remove\\` takes it all back out.\\n`,\n );\n return 'done';\n}\n\nasync function removeFlow(\n io: HooksIo,\n ask: Asker,\n harnessFlag: string | undefined,\n): Promise<number> {\n const harnesses = harnessFlag !== undefined ? [resolveHarnessFlag(harnessFlag)] : [...HARNESSES];\n const removals = planRemove(io.cwd, harnesses);\n\n if (removals.length === 0) {\n io.output(`${CLI_NAME} hooks remove: nothing of smelt's found to remove in ${io.cwd}.\\n`);\n return 0;\n }\n\n io.output(\n `${CLI_NAME} hooks remove — takes smelt's hook wiring back out.\\n\\nPlanned:\\n` +\n removals\n .map(\n (removal) =>\n ` ${removal.name.padEnd(32)} (${\n removal.action === 'delete' ? 'delete' : 'remove smelt entries, keep the rest'\n })\\n`,\n )\n .join('') +\n `${CONFIG_FILE_NAME} is left untouched — its hooks block is your config now; ` +\n `edit or remove it there.\\nNothing has been changed yet.\\n`,\n );\n\n if ((await confirmYesNo(ask, 'yes to proceed, no to leave everything untouched.')) === 'no') {\n io.output(`Nothing was changed.\\n`);\n return 0;\n }\n\n for (const removal of removals) {\n const verb = removal.action === 'delete' ? 'delete' : 'modify';\n const answer = await ask(` ${removal.name} — ${verb} it? (yes/no)> `);\n if (answer !== 'yes') {\n io.output(` skipped ${removal.name} — not touched\\n`);\n continue;\n }\n if (removal.action === 'delete') {\n unlinkSync(removal.path);\n io.output(` deleted ${removal.name}\\n`);\n } else {\n writeFileSync(removal.path, removal.content ?? '');\n io.output(` cleaned ${removal.name}\\n`);\n }\n }\n io.output(`Done.\\n`);\n return 0;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAU/C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,mDAAmD;AACnD,eAAO,MAAM,iBAAiB,qBAAqB,CAAC;AAEpD,+CAA+C;AAC/C,eAAO,MAAM,gBAAgB,oBAAoB,CAAC;AAElD,+FAA+F;AAC/F,MAAM,WAAW,MAAM;IACrB;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;;;OAMG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AA8BD;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBzD;AAsID;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQjE;AAiVD,kFAAkF;AAClF,wBAAgB,iBAAiB,IAAI,MAAM,CA+B1C;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,IAAI,MAAM,CA6DzC","sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\n\nimport { CliUsageError } from '../errors.ts';\nimport { DEFAULT_STRATEGY, STRATEGIES } from '../plan/planners.ts';\nimport type { Strategy } from '../plan/planners.ts';\nimport { STRUCTURAL_LANGUAGES } from '../plan/structural.ts';\n\nimport { answerReader, CLI_NAME } from './shell.ts';\nimport type { AnswerStream } from './shell.ts';\nimport {\n CONFIG_FILE_NAME,\n CONFIG_VERSION,\n findConfigFile,\n parseConfig,\n renderConfig,\n} from './config.ts';\nimport type { SmeltConfig, SmeltConfigHooks, SmeltConfigStore } from './config.ts';\n\n/**\n * `smelt init` — the setup wizard.\n *\n * The same testability pattern as `run.ts`: the wizard is a pure function over an\n * input/output pair, so every flow — fresh run, back-navigation, re-run editing,\n * declined overwrites — runs in-process in tests, and `bin.ts` wires the real stdio.\n *\n * Three rules shape everything here:\n *\n * 1. **Nothing is written until the final confirm**, which lists exactly what will be\n * written or changed. A wizard that writes as it goes cannot be backed out of.\n * 2. **An existing file is never overwritten without an explicit per-file yes.** Not\n * a global \"overwrite all\", not a default — one question per existing file, and\n * anything but a literal `yes` skips it. Guarded by\n * `test/guards/init-wizard.test.ts`, with a mutation proving the guard goes red.\n * 3. **Every step accepts `back`.** A wizard you cannot reverse inside is a form. That\n * includes the directory question a fresh run asks inside a workspace: `back` from\n * the first step returns to it, keeping the answers given since.\n * 4. **Everything lands in one named directory.** A fresh run inside a workspace asks\n * which end of config discovery to write to (see `chooseDirectory`) and an edit run\n * writes beside the config it found; either way the directory is printed in the\n * \"About to write\" listing before the confirm, and nothing is ever written outside\n * it. A wizard that writes where the user cannot see is a wizard writing somewhere\n * they did not agree to.\n *\n * Law 4 note: the wizard's copy states what each choice *does*, never what it saves —\n * no percentages, no rates, no numbers smelt has not measured.\n */\n\n/** The generated measure-hook stub's file name. */\nexport const MEASURE_STUB_FILE = 'smelt.measure.ts';\n\n/** The generated reranker stub's file name. */\nexport const RERANK_STUB_FILE = 'smelt.rerank.ts';\n\n/** Where the wizard's bytes come from and go. Injected so `runInit` is testable in-process. */\nexport interface InitIo {\n /**\n * Interactive input — the real stdin in `bin.ts`, a scripted stream in tests.\n * Structural on purpose; see {@link AnswerStream}.\n */\n readonly input: AnswerStream;\n readonly output: (text: string) => void;\n /**\n * Where config discovery starts, and where a fresh run's files land — unless `cwd`\n * is inside a workspace and the user picks its root instead (`chooseDirectory`). An\n * edit run writes next to the discovered config, which may be an ancestor of `cwd`.\n * In every flow the \"About to write\" listing names the directory before anything is\n * written, and nothing is written outside it.\n */\n readonly cwd: string;\n}\n\n/** Everything the wizard decides. Pure data until the final confirm writes it. */\ninterface WizardChoices {\n budgetBytes: number | undefined;\n store: SmeltConfigStore;\n strategy: Strategy;\n /** Generate {@link MEASURE_STUB_FILE}? Never deletes an existing one. */\n measureStub: boolean;\n /** Generate {@link RERANK_STUB_FILE}? Never deletes an existing one. */\n rerankStub: boolean;\n /**\n * An existing config's `hooks` block, carried through verbatim. This wizard never\n * edits it — `smelt hooks install` owns those choices — but a re-run that silently\n * dropped it would be an edit the user never made.\n */\n hooks: SmeltConfigHooks | undefined;\n}\n\ntype StepOutcome = 'ok' | 'back';\n\ninterface PlannedWrite {\n readonly name: string;\n readonly path: string;\n readonly content: string;\n readonly exists: boolean;\n /** The file already holds exactly these bytes — nothing to write. */\n readonly unchanged: boolean;\n}\n\n/**\n * The wizard, start to finish. Returns an exit code (0 in every completed flow,\n * including \"declined to write anything\").\n *\n * @throws {CliUsageError} on a malformed existing config, or when input ends before\n * the wizard finishes — both are usage-shaped, and nothing further is written.\n */\nexport async function runInit(io: InitIo): Promise<number> {\n // `answerReader` — not `Readable.from` + readline — because the wizard must *let go*\n // of stdin when it is done. See the note on `answerReader` for the hang that shape\n // caused: files written, `Done.` printed, and the process never exiting.\n const lines = answerReader(io.input);\n const ask = async (prompt: string): Promise<string> => {\n io.output(prompt);\n const next = await lines.next();\n if (next === undefined) {\n throw new CliUsageError(\n `${CLI_NAME} init: input ended before the wizard finished. ` +\n `Files already confirmed and written stay; nothing further was written.`,\n );\n }\n return next.trim();\n };\n\n try {\n const existing = loadExisting(io.cwd);\n return existing === undefined\n ? await freshRun(io, ask)\n : await editRun(io, ask, existing.path, existing.config);\n } finally {\n await lines.release();\n }\n}\n\nfunction loadExisting(cwd: string): { path: string; config: SmeltConfig } | undefined {\n const path = findConfigFile(cwd);\n if (path === undefined) return undefined;\n // Malformed is a loud usage error, not a silent fresh start: overwriting a config\n // the user wrote, because it had a typo, would be the wizard deciding for them.\n return { path, config: parseConfig(readFileSync(path, 'utf8'), path) };\n}\n\n// ---------------------------------------------------------------------------\n// The two flows\n// ---------------------------------------------------------------------------\n\ntype Asker = (prompt: string) => Promise<string>;\n\ninterface Step {\n readonly id: 'budget' | 'store' | 'strategy' | 'measure' | 'rerank';\n run(io: InitIo, ask: Asker, choices: WizardChoices, dir: string): Promise<StepOutcome>;\n}\n\nconst STEPS: readonly Step[] = [\n { id: 'budget', run: stepBudget },\n { id: 'store', run: stepStore },\n { id: 'strategy', run: stepStrategy },\n { id: 'measure', run: stepMeasure },\n { id: 'rerank', run: stepRerank },\n];\n\nasync function freshRun(io: InitIo, ask: Asker): Promise<number> {\n const choices: WizardChoices = {\n budgetBytes: undefined,\n store: { kind: 'memory' },\n strategy: DEFAULT_STRATEGY,\n measureStub: false,\n rerankStub: false,\n hooks: undefined,\n };\n\n // Outside a workspace there is no directory question, so the first step really is the\n // first thing asked. Inside one there is, and `back` from the first step returns to it\n // — with every answer so far carried along, because reversing is not restarting.\n const root = findWorkspaceRoot(io.cwd);\n for (;;) {\n const dir = root === undefined ? io.cwd : await chooseDirectory(io, ask, root);\n io.output(\n `${CLI_NAME} init — sets up ${CONFIG_FILE_NAME} in ${dir}.\\n` +\n `A run reads the nearest ${CONFIG_FILE_NAME}, walking UP from the directory it ` +\n `is run in, so this one is found from ${dir} and everything below it.\\n` +\n `Answer \\`back\\` at any step to return to the previous one. ` +\n `Nothing is written until you confirm at the end.\\n\\n`,\n );\n if ((await runSteps(io, ask, choices, dir, root !== undefined)) === 'done') return 0;\n }\n}\n\n/**\n * The step loop for one chosen directory: every step, then the confirm, with `back`\n * reversing all the way through both. Returns `'directory'` when `back` walks off the\n * front and there is a directory question to walk back into — `freshRun` re-asks it and\n * calls this again with the same {@link WizardChoices}.\n */\nasync function runSteps(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n canReopenDirectory: boolean,\n): Promise<'done' | 'directory'> {\n let index = 0;\n for (;;) {\n while (index < STEPS.length) {\n const outcome = await STEPS[index]!.run(io, ask, choices, dir);\n if (outcome !== 'back') {\n index += 1;\n } else if (index > 0) {\n index -= 1;\n } else if (canReopenDirectory) {\n return 'directory';\n } else {\n io.output(`This is the first step — there is nothing before it.\\n`);\n }\n }\n const verdict = await confirmAndWrite(io, ask, choices, dir);\n if (verdict !== 'back') return 'done';\n index = STEPS.length - 1;\n }\n}\n\n/**\n * Which directory this fresh run writes into — and the wizard's answer to the\n * monorepo trap.\n *\n * `smelt init` in `packages/web` used to write a config in `packages/web`, silently,\n * while discovery walks **up**: every run from the repo root then found no config and\n * used none, and the user had watched themselves set a budget. So when the working\n * directory sits inside a workspace (`pnpm-workspace.yaml`, or a `package.json` with a\n * `workspaces` field, in any ancestor), the wizard states the discovery rule and asks\n * which end of it to write to, defaulting to the root that covers the whole tree.\n *\n * The answer becomes the directory every later step, the \"About to write\" listing and\n * every `writeFileSync` uses — so the wizard still writes only inside the one directory\n * the user was shown and confirmed, here as much as anywhere else. Outside a workspace\n * there is nothing to choose and nothing is asked; the discovery rule is stated either\n * way, because \"where will runs look for this?\" is not a question a user should have to\n * infer from a path.\n *\n * Rule 3 holds here too: this is genuinely the first question, so `back` at it has\n * nowhere to go — but `back` from the first *step* comes back to it (see `freshRun`),\n * carrying the answers already given. The most consequential question in the wizard is\n * not the one you cannot reverse into.\n */\nasync function chooseDirectory(io: InitIo, ask: Asker, root: string): Promise<string> {\n io.output(\n `${CLI_NAME} init — ${io.cwd} is inside a workspace rooted at ${root}.\\n` +\n `A run reads the nearest ${CONFIG_FILE_NAME}, walking UP from the directory it is ` +\n `run in. A config written here is found from here and below; a run from ${root} ` +\n `would not find it. One at the workspace root is found from every package in it.\\n` +\n ` 1. ${root} (the workspace root)\\n` +\n ` 2. ${io.cwd} (here — this package only)\\n`,\n );\n for (;;) {\n const answer = await ask(`where should ${CONFIG_FILE_NAME} go? (1/2) [1]> `);\n if (answer === 'back') {\n io.output(`This is the first question — there is nothing before it.\\n`);\n continue;\n }\n const pick = answer === '' ? '1' : answer;\n if (pick === '1') return root;\n if (pick === '2') return io.cwd;\n io.output(`1 for the workspace root, 2 for here.\\n`);\n }\n}\n\n/**\n * The nearest **ancestor** of `cwd` that declares a workspace, or `undefined`.\n *\n * Ancestors only: a `cwd` that is itself the workspace root has nothing to choose\n * between, so it is asked nothing. Two markers, because those are the two the\n * ecosystem writes — pnpm's `pnpm-workspace.yaml`, and the `workspaces` field npm,\n * yarn and bun read out of `package.json`. Detection never reads a lockfile and never\n * shells out; a `package.json` it cannot parse claims nothing, so it is not a root.\n */\nexport function findWorkspaceRoot(cwd: string): string | undefined {\n let dir = resolve(cwd);\n for (;;) {\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n if (declaresWorkspace(dir)) return dir;\n }\n}\n\nfunction declaresWorkspace(dir: string): boolean {\n if (existsSync(join(dir, 'pnpm-workspace.yaml'))) return true;\n const manifest = join(dir, 'package.json');\n if (!existsSync(manifest)) return false;\n let workspaces: unknown;\n try {\n ({ workspaces } = JSON.parse(readFileSync(manifest, 'utf8')) as { workspaces?: unknown });\n } catch {\n return false; // a package.json smelt cannot read is not a claim about anything\n }\n return Array.isArray(workspaces) || (typeof workspaces === 'object' && workspaces !== null);\n}\n\nasync function editRun(\n io: InitIo,\n ask: Asker,\n configPath: string,\n config: SmeltConfig,\n): Promise<number> {\n const dir = dirname(configPath);\n const choices: WizardChoices = {\n budgetBytes: config.defaultBudgetBytes,\n store: config.store ?? { kind: 'memory' },\n strategy: config.strategy ?? DEFAULT_STRATEGY,\n measureStub: false,\n rerankStub: false,\n hooks: config.hooks,\n };\n\n io.output(\n `${CLI_NAME} init — ${CONFIG_FILE_NAME} already exists at ${configPath}.\\n` +\n `Change one setting at a time; nothing is written until you confirm.\\n`,\n );\n\n for (;;) {\n io.output(`\\nCurrent values:\\n${summary(choices, dir)}\\n`);\n const answer = await ask(\n `Change which setting? (budget / store / strategy / measure / rerank, ` +\n `\\`done\\` to review and confirm, \\`back\\` to leave without writing)\\n> `,\n );\n if (answer === 'back') {\n io.output(`Left as it was. Nothing has been written.\\n`);\n return 0;\n }\n if (answer === 'done') {\n const verdict = await confirmAndWrite(io, ask, choices, dir);\n if (verdict !== 'back') return 0;\n continue; // back from confirm returns to this menu\n }\n const step = STEPS.find((candidate) => candidate.id === answer);\n if (step === undefined) {\n io.output(`Not a setting: \"${answer}\". One of: budget, store, strategy, measure, rerank.\\n`);\n continue;\n }\n await step.run(io, ask, choices, dir); // its own `back` returns here\n }\n}\n\nfunction summary(choices: WizardChoices, dir: string): string {\n const stubLine = (file: string, generate: boolean): string => {\n if (generate) return `generate ${file}`;\n return existsSync(join(dir, file)) ? `${file} exists (kept as is)` : 'none';\n };\n return [\n ` budget: ${choices.budgetBytes === undefined ? '(not set)' : `${String(choices.budgetBytes)} bytes`}`,\n ` store: ${choices.store.kind === 'memory' ? 'memory' : `directory (${choices.store.path})`}`,\n ` strategy: ${choices.strategy}`,\n ` measure: ${stubLine(MEASURE_STUB_FILE, choices.measureStub)}`,\n ` rerank: ${stubLine(RERANK_STUB_FILE, choices.rerankStub)}`,\n ].join('\\n');\n}\n\n// ---------------------------------------------------------------------------\n// The steps. Each one accepts `back`.\n// ---------------------------------------------------------------------------\n\nasync function stepBudget(io: InitIo, ask: Asker, choices: WizardChoices): Promise<StepOutcome> {\n io.output(\n `\\nDefault byte budget — used when a \\`${CLI_NAME}\\` run omits --budget.\\n` +\n `Budgets are UTF-8 bytes, permanently; an explicit --budget always wins.\\n` +\n `There is no suggested number: the right budget depends on your traffic, and ` +\n `smelt does not invent numbers it has not measured.\\n`,\n );\n for (;;) {\n const current = choices.budgetBytes === undefined ? '' : ` [${String(choices.budgetBytes)}]`;\n const answer = await ask(`budget in bytes${current} (or back)> `);\n if (answer === 'back') return 'back';\n if (answer === '' && choices.budgetBytes !== undefined) return 'ok';\n if (/^\\d+$/.test(answer) && Number(answer) > 0) {\n choices.budgetBytes = Number(answer);\n return 'ok';\n }\n io.output(`A whole number of bytes greater than zero, e.g. 4000.\\n`);\n }\n}\n\nasync function stepStore(io: InitIo, ask: Asker, choices: WizardChoices): Promise<StepOutcome> {\n io.output(\n `\\nWhere elided bytes live. Every elision is reversible only while a store holds ` +\n `its bytes (Law 3):\\n` +\n ` 1. memory — per-process; retrievals do not survive the process\\n` +\n ` 2. directory — persistent, content-addressed, on disk; retrieval counters ` +\n `survive restarts\\n`,\n );\n for (;;) {\n const current = choices.store.kind === 'memory' ? '1' : '2';\n const answer = await ask(`store (1/2) [${current}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? current : answer;\n if (pick === '1') {\n choices.store = { kind: 'memory' };\n return 'ok';\n }\n if (pick === '2') {\n const previous = choices.store.kind === 'directory' ? choices.store.path : '.smelt/store';\n const path = await ask(`store directory, relative to ${CONFIG_FILE_NAME} [${previous}]> `);\n if (path === 'back') continue; // back to the store choice, not out of the step\n choices.store = { kind: 'directory', path: path === '' ? previous : path };\n return 'ok';\n }\n io.output(`1 for memory, 2 for directory, or back.\\n`);\n }\n}\n\n/**\n * One sentence per strategy, for the wizard's menu — `Record<Strategy, string>`, so a\n * strategy added to the `PLANNERS` registry without a line here is a compile error\n * rather than an option the wizard silently never offers. The menu itself, its\n * numbering and the re-prompt are all rendered from {@link STRATEGIES}, which is why\n * the third choice cost one entry in this table.\n */\nconst STRATEGY_BLURB: Readonly<Record<Strategy, string>> = {\n lexical: 'line windows around your focus terms; works on any text',\n structural:\n `parses ${String(STRUCTURAL_LANGUAGES.length)} languages with bundled grammars ` +\n `(${STRUCTURAL_LANGUAGES.join(', ')}) and collapses siblings by name; refuses ` +\n `other languages rather than approximating`,\n auto:\n 'structural for those languages and lexical for everything else; the result ' +\n 'names whichever one actually ran',\n};\n\nasync function stepStrategy(io: InitIo, ask: Asker, choices: WizardChoices): Promise<StepOutcome> {\n const picks = STRATEGIES.map((_, index) => String(index + 1));\n io.output(\n `\\nDefault planner strategy — used when a run omits --strategy:\\n` +\n STRATEGIES.map(\n (name, index) => ` ${picks[index]!}. ${name.padEnd(12)}— ${STRATEGY_BLURB[name]}\\n`,\n ).join(''),\n );\n for (;;) {\n const current = picks[STRATEGIES.indexOf(choices.strategy)]!;\n const answer = await ask(`strategy (${picks.join('/')}) [${current}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? current : answer;\n const index = picks.indexOf(pick);\n if (index >= 0) {\n choices.strategy = STRATEGIES[index]!;\n return 'ok';\n }\n io.output(`${STRATEGIES.map((name, i) => `${picks[i]!} for ${name}`).join(', ')}, or back.\\n`);\n }\n}\n\nasync function stepMeasure(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n): Promise<StepOutcome> {\n const exists = existsSync(join(dir, MEASURE_STUB_FILE));\n io.output(\n `\\nMeasure hook — your own counter (a tokenizer, usually), so results carry a ` +\n `second, labelled number next to the byte counts. smelt ships none: a token ` +\n `count without its tokenizer named is not a measurement.\\n` +\n ` 1. none\\n` +\n ` 2. generate ${MEASURE_STUB_FILE}, a typed stub you fill in\\n` +\n (exists ? `(${MEASURE_STUB_FILE} already exists; it is never deleted from here.)\\n` : ``),\n );\n for (;;) {\n const answer = await ask(`measure (1/2) [${choices.measureStub ? '2' : '1'}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? (choices.measureStub ? '2' : '1') : answer;\n if (pick === '1' || pick === '2') {\n choices.measureStub = pick === '2';\n return 'ok';\n }\n io.output(`1 for none, 2 to generate the stub, or back.\\n`);\n }\n}\n\nasync function stepRerank(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n): Promise<StepOutcome> {\n const exists = existsSync(join(dir, RERANK_STUB_FILE));\n io.output(\n `\\nReranker — a RerankStage of your own. smelt never bundles one and never will: ` +\n `an outbound call must live in your code, under your key, visible in your own ` +\n `review (Law 1).\\n` +\n ` 1. none\\n` +\n ` 2. generate ${RERANK_STUB_FILE}, a typed stub with the HTTP call sketched as a TODO\\n` +\n (exists ? `(${RERANK_STUB_FILE} already exists; it is never deleted from here.)\\n` : ``),\n );\n for (;;) {\n const answer = await ask(`rerank (1/2) [${choices.rerankStub ? '2' : '1'}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? (choices.rerankStub ? '2' : '1') : answer;\n if (pick === '1' || pick === '2') {\n choices.rerankStub = pick === '2';\n return 'ok';\n }\n io.output(`1 for none, 2 to generate the stub, or back.\\n`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// The confirm step: the only place anything is written\n// ---------------------------------------------------------------------------\n\nconst writeLabel = (write: PlannedWrite): string => {\n if (write.unchanged) return 'unchanged — nothing to write';\n return write.exists ? 'exists — will ask before overwriting' : 'new';\n};\n\nasync function confirmAndWrite(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n): Promise<'done' | 'back'> {\n if (choices.budgetBytes === undefined) {\n // Unreachable in the fresh flow (the budget step requires a number) but reachable\n // from an edit of a config that never had one — `defaultBudgetBytes` is optional,\n // so such a config parses fine. The wizard still insists: a confirm without a\n // budget would write a file this wizard just called incomplete. `back` here is a\n // real `back` — it returns to the caller, never falls forward into the confirm.\n io.output(`No budget is set yet — set one before confirming.\\n`);\n if ((await stepBudget(io, ask, choices)) === 'back') return 'back';\n }\n\n const writes = plannedWrites(choices, dir);\n io.output(\n `\\nAbout to write, into ${dir}:\\n` +\n writes.map((write) => ` ${write.name.padEnd(20)} (${writeLabel(write)})\\n`).join('') +\n `Nothing has been written yet.\\n`,\n );\n\n for (;;) {\n const answer = await ask(`confirm (yes / no / back)> `);\n if (answer === 'back') return 'back';\n if (answer === 'no') {\n io.output(`Nothing was written.\\n`);\n return 'done';\n }\n if (answer === 'yes') break;\n io.output(`yes to write, no to leave everything untouched, back to change a setting.\\n`);\n }\n\n for (const write of writes) {\n if (write.unchanged) {\n io.output(` ${write.name} — unchanged, not rewritten\\n`);\n continue;\n }\n if (write.exists) {\n // The per-file consent rule: one explicit question per existing file, and only\n // a literal `yes` overwrites. This is the line the mutation\n // `init-overwrite-without-consent` breaks to prove the guard can go red.\n const answer = await ask(` ${write.name} exists — overwrite it? (yes/no)> `);\n if (answer !== 'yes') {\n io.output(` skipped ${write.name} — the existing file was not touched\\n`);\n continue;\n }\n }\n writeFileSync(write.path, write.content);\n io.output(` wrote ${write.name}\\n`);\n }\n io.output(`Done.\\n`);\n io.output(\n `Also: \\`${CLI_NAME} hooks install\\` wires the smelt guard into agent-harness ` +\n `hooks (Claude Code, Codex, and more) — it detects installed harnesses and asks ` +\n `before writing anything.\\n`,\n );\n return 'done';\n}\n\nfunction plannedWrites(choices: WizardChoices, dir: string): readonly PlannedWrite[] {\n const plan = (name: string, content: string): PlannedWrite => {\n const path = join(dir, name);\n const exists = existsSync(path);\n const unchanged = exists && readFileSync(path, 'utf8') === content;\n return { name, path, content, exists, unchanged };\n };\n const writes = [plan(CONFIG_FILE_NAME, renderConfig(chosenConfig(choices)))];\n if (choices.measureStub) writes.push(plan(MEASURE_STUB_FILE, measureStubSource()));\n if (choices.rerankStub) writes.push(plan(RERANK_STUB_FILE, rerankStubSource()));\n return writes;\n}\n\n/**\n * The config the wizard's choices mean — the wizard's *policy*, handed to the one\n * writer in `config.ts`. `strategy` and `store` are always written: the wizard asked\n * about both, and a chosen value left out of the file would be a setting the user\n * watched themselves pick and never got.\n */\nfunction chosenConfig(choices: {\n readonly budgetBytes: number | undefined;\n readonly store: SmeltConfigStore;\n readonly strategy: Strategy;\n /** Carried through from an existing config; this wizard never edits it. */\n readonly hooks?: SmeltConfigHooks | undefined;\n}): SmeltConfig {\n return {\n smeltConfig: CONFIG_VERSION,\n ...(choices.budgetBytes === undefined ? {} : { defaultBudgetBytes: choices.budgetBytes }),\n strategy: choices.strategy,\n store: choices.store,\n ...(choices.hooks === undefined ? {} : { hooks: choices.hooks }),\n };\n}\n\n// ---------------------------------------------------------------------------\n// The generated stubs. String literals on purpose: smelt's own import graph must not\n// gain an HTTP client, and the zero-network guard's string-stripper ignores string\n// bodies — the sketched fetch below lives in the CONSUMER'S file, never in smelt's.\n//\n// The comment token between `from` and the package name in each stub's import line is\n// deliberate: the guard's import scanner reads raw source, so spelling that import\n// plainly inside this template would register as an edge in smelt's own graph — which\n// it is not. The comment keeps the generated file valid TypeScript while keeping this\n// data out of the guard's walk.\n// ---------------------------------------------------------------------------\n\n/** The `smelt.measure.ts` the wizard writes. Exported so tests can compile it. */\nexport function measureStubSource(): string {\n return `/**\n * Measure hook — generated by \\`smelt init\\`.\n *\n * smelt's budgets are UTF-8 bytes, permanently; this hook adds a second, labelled\n * number to every result in YOUR unit, counted by YOUR tokenizer. Both \\`id\\` and\n * \\`unit\\` are required: a count without the counter named is not a measurement.\n *\n * Wire it in, in your own code:\n *\n * const smelter = createSmelter({ defaultBudgetBytes: 8_000, measure });\n *\n * with createSmelter imported from your @smeltjs/core install and measure from here.\n */\nimport type { Measure } from /* your install */ '@smeltjs/core';\n\nexport const measure: Measure = {\n // TODO: name the counter that produces these numbers, e.g. 'tiktoken/o200k_base'.\n id: 'TODO/your-tokenizer',\n unit: 'tokens',\n count(text: string): number {\n // TODO: replace with your real tokenizer. It must be local and synchronous —\n // a count() that calls an API would make your process call an API on every smelt.\n // e.g. return encode(text).length;\n throw new Error(\n 'smelt.measure.ts: count() is not implemented yet — ' +\n 'fill it in with your tokenizer (length of input: ' + String(text.length) + ')',\n );\n },\n};\n`;\n}\n\n/** The `smelt.rerank.ts` the wizard writes. Exported so tests can compile it. */\nexport function rerankStubSource(): string {\n return `/**\n * Reranker stub — generated by \\`smelt init\\`.\n *\n * smelt itself makes zero network calls and never bundles a reranker (Law 1): the\n * moment one ships as a default, every consumer's source code leaves the machine and\n * they find out from a changelog, or never. So the outbound call lives HERE, in your\n * file, reading your env var, visible in your own review.\n *\n * Wire it into your own pipeline; smelt never calls this for you.\n */\nimport type { RerankCandidate, RerankedCandidate, RerankStage } from /* your install */ '@smeltjs/core';\n\n/** The env var YOUR code reads. Rename it to match your vendor. */\nconst API_KEY_ENV = 'RERANKER_API_KEY';\n\nexport const rerank: RerankStage = {\n id: 'my-reranker/v1',\n async rerank(\n candidates: readonly RerankCandidate[],\n query: string,\n ): Promise<readonly RerankedCandidate[]> {\n const apiKey = process.env[API_KEY_ENV];\n if (apiKey === undefined || apiKey === '') {\n throw new Error(\n 'smelt.rerank.ts: ' + API_KEY_ENV + ' is not set. This stage makes an outbound ' +\n 'HTTP call from YOUR code with YOUR key; without a key it refuses to pretend.',\n );\n }\n\n // TODO: the outbound call. Voyage AI is one example vendor; any reranker with an\n // HTTP API fits this shape:\n //\n // const response = await fetch('https://api.voyageai.com/v1/rerank', {\n // method: 'POST',\n // headers: {\n // 'content-type': 'application/json',\n // authorization: \\`Bearer \\${apiKey}\\`,\n // },\n // body: JSON.stringify({\n // query,\n // documents: candidates.map((candidate) => candidate.text),\n // }),\n // });\n // if (!response.ok) throw new Error('rerank failed: ' + String(response.status));\n // const body = (await response.json()) as {\n // data: { index: number; relevance_score: number }[];\n // };\n // return body.data.map(({ index, relevance_score }) => ({\n // ...candidates[index]!,\n // score: relevance_score,\n // }));\n //\n void candidates;\n void query;\n throw new Error(\n 'smelt.rerank.ts: implement the outbound call sketched above, then delete this throw.',\n );\n },\n};\n`;\n}\n"]}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAU/C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,mDAAmD;AACnD,eAAO,MAAM,iBAAiB,qBAAqB,CAAC;AAEpD,+CAA+C;AAC/C,eAAO,MAAM,gBAAgB,oBAAoB,CAAC;AAElD,+FAA+F;AAC/F,MAAM,WAAW,MAAM;IACrB;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;;;OAMG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AA8BD;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBzD;AAsID;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQjE;AAkVD,kFAAkF;AAClF,wBAAgB,iBAAiB,IAAI,MAAM,CA+B1C;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,IAAI,MAAM,CA6DzC","sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\n\nimport { DEFAULT_STRATEGY, STRATEGIES } from '../plan/planners.ts';\nimport type { Strategy } from '../plan/planners.ts';\nimport { STRUCTURAL_LANGUAGES } from '../plan/structural.ts';\nimport { SETUP_RECIPE } from '../setup/recipe.ts';\n\nimport { CLI_NAME } from './shell.ts';\nimport { wizardAsk } from './wizard.ts';\nimport type { AnswerStream } from './shell.ts';\nimport {\n CONFIG_FILE_NAME,\n CONFIG_VERSION,\n findConfigFile,\n parseConfig,\n renderConfig,\n} from './config.ts';\nimport type { SmeltConfig, SmeltConfigHooks, SmeltConfigStore } from './config.ts';\n\n/**\n * `smelt init` — the setup wizard.\n *\n * The same testability pattern as `run.ts`: the wizard is a pure function over an\n * input/output pair, so every flow — fresh run, back-navigation, re-run editing,\n * declined overwrites — runs in-process in tests, and `bin.ts` wires the real stdio.\n *\n * Three rules shape everything here:\n *\n * 1. **Nothing is written until the final confirm**, which lists exactly what will be\n * written or changed. A wizard that writes as it goes cannot be backed out of.\n * 2. **An existing file is never overwritten without an explicit per-file yes.** Not\n * a global \"overwrite all\", not a default — one question per existing file, and\n * anything but a literal `yes` skips it. Guarded by\n * `test/guards/init-wizard.test.ts`, with a mutation proving the guard goes red.\n * 3. **Every step accepts `back`.** A wizard you cannot reverse inside is a form. That\n * includes the directory question a fresh run asks inside a workspace: `back` from\n * the first step returns to it, keeping the answers given since.\n * 4. **Everything lands in one named directory.** A fresh run inside a workspace asks\n * which end of config discovery to write to (see `chooseDirectory`) and an edit run\n * writes beside the config it found; either way the directory is printed in the\n * \"About to write\" listing before the confirm, and nothing is ever written outside\n * it. A wizard that writes where the user cannot see is a wizard writing somewhere\n * they did not agree to.\n *\n * Law 4 note: the wizard's copy states what each choice *does*, never what it saves —\n * no percentages, no rates, no numbers smelt has not measured.\n */\n\n/** The generated measure-hook stub's file name. */\nexport const MEASURE_STUB_FILE = 'smelt.measure.ts';\n\n/** The generated reranker stub's file name. */\nexport const RERANK_STUB_FILE = 'smelt.rerank.ts';\n\n/** Where the wizard's bytes come from and go. Injected so `runInit` is testable in-process. */\nexport interface InitIo {\n /**\n * Interactive input — the real stdin in `bin.ts`, a scripted stream in tests.\n * Structural on purpose; see {@link AnswerStream}.\n */\n readonly input: AnswerStream;\n readonly output: (text: string) => void;\n /**\n * Where config discovery starts, and where a fresh run's files land — unless `cwd`\n * is inside a workspace and the user picks its root instead (`chooseDirectory`). An\n * edit run writes next to the discovered config, which may be an ancestor of `cwd`.\n * In every flow the \"About to write\" listing names the directory before anything is\n * written, and nothing is written outside it.\n */\n readonly cwd: string;\n}\n\n/** Everything the wizard decides. Pure data until the final confirm writes it. */\ninterface WizardChoices {\n budgetBytes: number | undefined;\n store: SmeltConfigStore;\n strategy: Strategy;\n /** Generate {@link MEASURE_STUB_FILE}? Never deletes an existing one. */\n measureStub: boolean;\n /** Generate {@link RERANK_STUB_FILE}? Never deletes an existing one. */\n rerankStub: boolean;\n /**\n * An existing config's `hooks` block, carried through verbatim. This wizard never\n * edits it — `smelt hooks install` owns those choices — but a re-run that silently\n * dropped it would be an edit the user never made.\n */\n hooks: SmeltConfigHooks | undefined;\n}\n\ntype StepOutcome = 'ok' | 'back';\n\ninterface PlannedWrite {\n readonly name: string;\n readonly path: string;\n readonly content: string;\n readonly exists: boolean;\n /** The file already holds exactly these bytes — nothing to write. */\n readonly unchanged: boolean;\n}\n\n/**\n * The wizard, start to finish. Returns an exit code (0 in every completed flow,\n * including \"declined to write anything\").\n *\n * @throws {CliUsageError} on a malformed existing config, or when input ends before\n * the wizard finishes — both are usage-shaped, and nothing further is written.\n */\nexport async function runInit(io: InitIo): Promise<number> {\n // `answerReader` — not `Readable.from` + readline — because the wizard must *let go*\n // of stdin when it is done. See the note on `answerReader` for the hang that shape\n // caused: files written, `Done.` printed, and the process never exiting.\n const wizard = wizardAsk(\n io.input,\n io.output,\n `${CLI_NAME} init: input ended before the wizard finished. ` +\n `Files already confirmed and written stay; nothing further was written.`,\n );\n const ask = wizard.ask;\n\n try {\n const existing = loadExisting(io.cwd);\n return existing === undefined\n ? await freshRun(io, ask)\n : await editRun(io, ask, existing.path, existing.config);\n } finally {\n await wizard.release();\n }\n}\n\nfunction loadExisting(cwd: string): { path: string; config: SmeltConfig } | undefined {\n const path = findConfigFile(cwd);\n if (path === undefined) return undefined;\n // Malformed is a loud usage error, not a silent fresh start: overwriting a config\n // the user wrote, because it had a typo, would be the wizard deciding for them.\n return { path, config: parseConfig(readFileSync(path, 'utf8'), path) };\n}\n\n// ---------------------------------------------------------------------------\n// The two flows\n// ---------------------------------------------------------------------------\n\ntype Asker = (prompt: string) => Promise<string>;\n\ninterface Step {\n readonly id: 'budget' | 'store' | 'strategy' | 'measure' | 'rerank';\n run(io: InitIo, ask: Asker, choices: WizardChoices, dir: string): Promise<StepOutcome>;\n}\n\nconst STEPS: readonly Step[] = [\n { id: 'budget', run: stepBudget },\n { id: 'store', run: stepStore },\n { id: 'strategy', run: stepStrategy },\n { id: 'measure', run: stepMeasure },\n { id: 'rerank', run: stepRerank },\n];\n\nasync function freshRun(io: InitIo, ask: Asker): Promise<number> {\n const choices: WizardChoices = {\n budgetBytes: undefined,\n store: { kind: 'memory' },\n strategy: DEFAULT_STRATEGY,\n measureStub: false,\n rerankStub: false,\n hooks: undefined,\n };\n\n // Outside a workspace there is no directory question, so the first step really is the\n // first thing asked. Inside one there is, and `back` from the first step returns to it\n // — with every answer so far carried along, because reversing is not restarting.\n const root = findWorkspaceRoot(io.cwd);\n for (;;) {\n const dir = root === undefined ? io.cwd : await chooseDirectory(io, ask, root);\n io.output(\n `${CLI_NAME} init — sets up ${CONFIG_FILE_NAME} in ${dir}.\\n` +\n `A run reads the nearest ${CONFIG_FILE_NAME}, walking UP from the directory it ` +\n `is run in, so this one is found from ${dir} and everything below it.\\n` +\n `Answer \\`back\\` at any step to return to the previous one. ` +\n `Nothing is written until you confirm at the end.\\n\\n`,\n );\n if ((await runSteps(io, ask, choices, dir, root !== undefined)) === 'done') return 0;\n }\n}\n\n/**\n * The step loop for one chosen directory: every step, then the confirm, with `back`\n * reversing all the way through both. Returns `'directory'` when `back` walks off the\n * front and there is a directory question to walk back into — `freshRun` re-asks it and\n * calls this again with the same {@link WizardChoices}.\n */\nasync function runSteps(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n canReopenDirectory: boolean,\n): Promise<'done' | 'directory'> {\n let index = 0;\n for (;;) {\n while (index < STEPS.length) {\n const outcome = await STEPS[index]!.run(io, ask, choices, dir);\n if (outcome !== 'back') {\n index += 1;\n } else if (index > 0) {\n index -= 1;\n } else if (canReopenDirectory) {\n return 'directory';\n } else {\n io.output(`This is the first step — there is nothing before it.\\n`);\n }\n }\n const verdict = await confirmAndWrite(io, ask, choices, dir);\n if (verdict !== 'back') return 'done';\n index = STEPS.length - 1;\n }\n}\n\n/**\n * Which directory this fresh run writes into — and the wizard's answer to the\n * monorepo trap.\n *\n * `smelt init` in `packages/web` used to write a config in `packages/web`, silently,\n * while discovery walks **up**: every run from the repo root then found no config and\n * used none, and the user had watched themselves set a budget. So when the working\n * directory sits inside a workspace (`pnpm-workspace.yaml`, or a `package.json` with a\n * `workspaces` field, in any ancestor), the wizard states the discovery rule and asks\n * which end of it to write to, defaulting to the root that covers the whole tree.\n *\n * The answer becomes the directory every later step, the \"About to write\" listing and\n * every `writeFileSync` uses — so the wizard still writes only inside the one directory\n * the user was shown and confirmed, here as much as anywhere else. Outside a workspace\n * there is nothing to choose and nothing is asked; the discovery rule is stated either\n * way, because \"where will runs look for this?\" is not a question a user should have to\n * infer from a path.\n *\n * Rule 3 holds here too: this is genuinely the first question, so `back` at it has\n * nowhere to go — but `back` from the first *step* comes back to it (see `freshRun`),\n * carrying the answers already given. The most consequential question in the wizard is\n * not the one you cannot reverse into.\n */\nasync function chooseDirectory(io: InitIo, ask: Asker, root: string): Promise<string> {\n io.output(\n `${CLI_NAME} init — ${io.cwd} is inside a workspace rooted at ${root}.\\n` +\n `A run reads the nearest ${CONFIG_FILE_NAME}, walking UP from the directory it is ` +\n `run in. A config written here is found from here and below; a run from ${root} ` +\n `would not find it. One at the workspace root is found from every package in it.\\n` +\n ` 1. ${root} (the workspace root)\\n` +\n ` 2. ${io.cwd} (here — this package only)\\n`,\n );\n for (;;) {\n const answer = await ask(`where should ${CONFIG_FILE_NAME} go? (1/2) [1]> `);\n if (answer === 'back') {\n io.output(`This is the first question — there is nothing before it.\\n`);\n continue;\n }\n const pick = answer === '' ? '1' : answer;\n if (pick === '1') return root;\n if (pick === '2') return io.cwd;\n io.output(`1 for the workspace root, 2 for here.\\n`);\n }\n}\n\n/**\n * The nearest **ancestor** of `cwd` that declares a workspace, or `undefined`.\n *\n * Ancestors only: a `cwd` that is itself the workspace root has nothing to choose\n * between, so it is asked nothing. Two markers, because those are the two the\n * ecosystem writes — pnpm's `pnpm-workspace.yaml`, and the `workspaces` field npm,\n * yarn and bun read out of `package.json`. Detection never reads a lockfile and never\n * shells out; a `package.json` it cannot parse claims nothing, so it is not a root.\n */\nexport function findWorkspaceRoot(cwd: string): string | undefined {\n let dir = resolve(cwd);\n for (;;) {\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n if (declaresWorkspace(dir)) return dir;\n }\n}\n\nfunction declaresWorkspace(dir: string): boolean {\n if (existsSync(join(dir, 'pnpm-workspace.yaml'))) return true;\n const manifest = join(dir, 'package.json');\n if (!existsSync(manifest)) return false;\n let workspaces: unknown;\n try {\n ({ workspaces } = JSON.parse(readFileSync(manifest, 'utf8')) as { workspaces?: unknown });\n } catch {\n return false; // a package.json smelt cannot read is not a claim about anything\n }\n return Array.isArray(workspaces) || (typeof workspaces === 'object' && workspaces !== null);\n}\n\nasync function editRun(\n io: InitIo,\n ask: Asker,\n configPath: string,\n config: SmeltConfig,\n): Promise<number> {\n const dir = dirname(configPath);\n const choices: WizardChoices = {\n budgetBytes: config.defaultBudgetBytes,\n store: config.store ?? { kind: 'memory' },\n strategy: config.strategy ?? DEFAULT_STRATEGY,\n measureStub: false,\n rerankStub: false,\n hooks: config.hooks,\n };\n\n io.output(\n `${CLI_NAME} init — ${CONFIG_FILE_NAME} already exists at ${configPath}.\\n` +\n `Change one setting at a time; nothing is written until you confirm.\\n`,\n );\n\n for (;;) {\n io.output(`\\nCurrent values:\\n${summary(choices, dir)}\\n`);\n const answer = await ask(\n `Change which setting? (budget / store / strategy / measure / rerank, ` +\n `\\`done\\` to review and confirm, \\`back\\` to leave without writing)\\n> `,\n );\n if (answer === 'back') {\n io.output(`Left as it was. Nothing has been written.\\n`);\n return 0;\n }\n if (answer === 'done') {\n const verdict = await confirmAndWrite(io, ask, choices, dir);\n if (verdict !== 'back') return 0;\n continue; // back from confirm returns to this menu\n }\n const step = STEPS.find((candidate) => candidate.id === answer);\n if (step === undefined) {\n io.output(`Not a setting: \"${answer}\". One of: budget, store, strategy, measure, rerank.\\n`);\n continue;\n }\n await step.run(io, ask, choices, dir); // its own `back` returns here\n }\n}\n\nfunction summary(choices: WizardChoices, dir: string): string {\n const stubLine = (file: string, generate: boolean): string => {\n if (generate) return `generate ${file}`;\n return existsSync(join(dir, file)) ? `${file} exists (kept as is)` : 'none';\n };\n return [\n ` budget: ${choices.budgetBytes === undefined ? '(not set)' : `${String(choices.budgetBytes)} bytes`}`,\n ` store: ${choices.store.kind === 'memory' ? 'memory' : `directory (${choices.store.path})`}`,\n ` strategy: ${choices.strategy}`,\n ` measure: ${stubLine(MEASURE_STUB_FILE, choices.measureStub)}`,\n ` rerank: ${stubLine(RERANK_STUB_FILE, choices.rerankStub)}`,\n ].join('\\n');\n}\n\n// ---------------------------------------------------------------------------\n// The steps. Each one accepts `back`.\n// ---------------------------------------------------------------------------\n\nasync function stepBudget(io: InitIo, ask: Asker, choices: WizardChoices): Promise<StepOutcome> {\n io.output(\n `\\nDefault byte budget — used when a \\`${CLI_NAME}\\` run omits --budget.\\n` +\n `Budgets are UTF-8 bytes, permanently; an explicit --budget always wins.\\n` +\n `There is no suggested number: the right budget depends on your traffic, and ` +\n `smelt does not invent numbers it has not measured.\\n`,\n );\n for (;;) {\n const current = choices.budgetBytes === undefined ? '' : ` [${String(choices.budgetBytes)}]`;\n const answer = await ask(`budget in bytes${current} (or back)> `);\n if (answer === 'back') return 'back';\n if (answer === '' && choices.budgetBytes !== undefined) return 'ok';\n if (/^\\d+$/.test(answer) && Number(answer) > 0) {\n choices.budgetBytes = Number(answer);\n return 'ok';\n }\n io.output(`A whole number of bytes greater than zero, e.g. 4000.\\n`);\n }\n}\n\nasync function stepStore(io: InitIo, ask: Asker, choices: WizardChoices): Promise<StepOutcome> {\n io.output(\n `\\nWhere elided bytes live. Every elision is reversible only while a store holds ` +\n `its bytes (Law 3):\\n` +\n ` 1. memory — per-process; retrievals do not survive the process\\n` +\n ` 2. directory — persistent, content-addressed, on disk; retrieval counters ` +\n `survive restarts\\n`,\n );\n for (;;) {\n const current = choices.store.kind === 'memory' ? '1' : '2';\n const answer = await ask(`store (1/2) [${current}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? current : answer;\n if (pick === '1') {\n choices.store = { kind: 'memory' };\n return 'ok';\n }\n if (pick === '2') {\n const previous =\n choices.store.kind === 'directory' ? choices.store.path : SETUP_RECIPE.store.defaultDir;\n const path = await ask(`store directory, relative to ${CONFIG_FILE_NAME} [${previous}]> `);\n if (path === 'back') continue; // back to the store choice, not out of the step\n choices.store = { kind: 'directory', path: path === '' ? previous : path };\n return 'ok';\n }\n io.output(`1 for memory, 2 for directory, or back.\\n`);\n }\n}\n\n/**\n * One sentence per strategy, for the wizard's menu — `Record<Strategy, string>`, so a\n * strategy added to the `PLANNERS` registry without a line here is a compile error\n * rather than an option the wizard silently never offers. The menu itself, its\n * numbering and the re-prompt are all rendered from {@link STRATEGIES}, which is why\n * the third choice cost one entry in this table.\n */\nconst STRATEGY_BLURB: Readonly<Record<Strategy, string>> = {\n lexical: 'line windows around your focus terms; works on any text',\n structural:\n `parses ${String(STRUCTURAL_LANGUAGES.length)} languages with bundled grammars ` +\n `(${STRUCTURAL_LANGUAGES.join(', ')}) and collapses siblings by name; refuses ` +\n `other languages rather than approximating`,\n auto:\n 'structural for those languages and lexical for everything else; the result ' +\n 'names whichever one actually ran',\n};\n\nasync function stepStrategy(io: InitIo, ask: Asker, choices: WizardChoices): Promise<StepOutcome> {\n const picks = STRATEGIES.map((_, index) => String(index + 1));\n io.output(\n `\\nDefault planner strategy — used when a run omits --strategy:\\n` +\n STRATEGIES.map(\n (name, index) => ` ${picks[index]!}. ${name.padEnd(12)}— ${STRATEGY_BLURB[name]}\\n`,\n ).join(''),\n );\n for (;;) {\n const current = picks[STRATEGIES.indexOf(choices.strategy)]!;\n const answer = await ask(`strategy (${picks.join('/')}) [${current}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? current : answer;\n const index = picks.indexOf(pick);\n if (index >= 0) {\n choices.strategy = STRATEGIES[index]!;\n return 'ok';\n }\n io.output(`${STRATEGIES.map((name, i) => `${picks[i]!} for ${name}`).join(', ')}, or back.\\n`);\n }\n}\n\nasync function stepMeasure(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n): Promise<StepOutcome> {\n const exists = existsSync(join(dir, MEASURE_STUB_FILE));\n io.output(\n `\\nMeasure hook — your own counter (a tokenizer, usually), so results carry a ` +\n `second, labelled number next to the byte counts. smelt ships none: a token ` +\n `count without its tokenizer named is not a measurement.\\n` +\n ` 1. none\\n` +\n ` 2. generate ${MEASURE_STUB_FILE}, a typed stub you fill in\\n` +\n (exists ? `(${MEASURE_STUB_FILE} already exists; it is never deleted from here.)\\n` : ``),\n );\n for (;;) {\n const answer = await ask(`measure (1/2) [${choices.measureStub ? '2' : '1'}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? (choices.measureStub ? '2' : '1') : answer;\n if (pick === '1' || pick === '2') {\n choices.measureStub = pick === '2';\n return 'ok';\n }\n io.output(`1 for none, 2 to generate the stub, or back.\\n`);\n }\n}\n\nasync function stepRerank(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n): Promise<StepOutcome> {\n const exists = existsSync(join(dir, RERANK_STUB_FILE));\n io.output(\n `\\nReranker — a RerankStage of your own. smelt never bundles one and never will: ` +\n `an outbound call must live in your code, under your key, visible in your own ` +\n `review (Law 1).\\n` +\n ` 1. none\\n` +\n ` 2. generate ${RERANK_STUB_FILE}, a typed stub with the HTTP call sketched as a TODO\\n` +\n (exists ? `(${RERANK_STUB_FILE} already exists; it is never deleted from here.)\\n` : ``),\n );\n for (;;) {\n const answer = await ask(`rerank (1/2) [${choices.rerankStub ? '2' : '1'}] (or back)> `);\n if (answer === 'back') return 'back';\n const pick = answer === '' ? (choices.rerankStub ? '2' : '1') : answer;\n if (pick === '1' || pick === '2') {\n choices.rerankStub = pick === '2';\n return 'ok';\n }\n io.output(`1 for none, 2 to generate the stub, or back.\\n`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// The confirm step: the only place anything is written\n// ---------------------------------------------------------------------------\n\nconst writeLabel = (write: PlannedWrite): string => {\n if (write.unchanged) return 'unchanged — nothing to write';\n return write.exists ? 'exists — will ask before overwriting' : 'new';\n};\n\nasync function confirmAndWrite(\n io: InitIo,\n ask: Asker,\n choices: WizardChoices,\n dir: string,\n): Promise<'done' | 'back'> {\n if (choices.budgetBytes === undefined) {\n // Unreachable in the fresh flow (the budget step requires a number) but reachable\n // from an edit of a config that never had one — `defaultBudgetBytes` is optional,\n // so such a config parses fine. The wizard still insists: a confirm without a\n // budget would write a file this wizard just called incomplete. `back` here is a\n // real `back` — it returns to the caller, never falls forward into the confirm.\n io.output(`No budget is set yet — set one before confirming.\\n`);\n if ((await stepBudget(io, ask, choices)) === 'back') return 'back';\n }\n\n const writes = plannedWrites(choices, dir);\n io.output(\n `\\nAbout to write, into ${dir}:\\n` +\n writes.map((write) => ` ${write.name.padEnd(20)} (${writeLabel(write)})\\n`).join('') +\n `Nothing has been written yet.\\n`,\n );\n\n for (;;) {\n const answer = await ask(`confirm (yes / no / back)> `);\n if (answer === 'back') return 'back';\n if (answer === 'no') {\n io.output(`Nothing was written.\\n`);\n return 'done';\n }\n if (answer === 'yes') break;\n io.output(`yes to write, no to leave everything untouched, back to change a setting.\\n`);\n }\n\n for (const write of writes) {\n if (write.unchanged) {\n io.output(` ${write.name} — unchanged, not rewritten\\n`);\n continue;\n }\n if (write.exists) {\n // The per-file consent rule: one explicit question per existing file, and only\n // a literal `yes` overwrites. This is the line the mutation\n // `init-overwrite-without-consent` breaks to prove the guard can go red.\n const answer = await ask(` ${write.name} exists — overwrite it? (yes/no)> `);\n if (answer !== 'yes') {\n io.output(` skipped ${write.name} — the existing file was not touched\\n`);\n continue;\n }\n }\n writeFileSync(write.path, write.content);\n io.output(` wrote ${write.name}\\n`);\n }\n io.output(`Done.\\n`);\n io.output(\n `Also: \\`${CLI_NAME} hooks install\\` wires the smelt guard into agent-harness ` +\n `hooks (Claude Code, Codex, and more) — it detects installed harnesses and asks ` +\n `before writing anything.\\n`,\n );\n return 'done';\n}\n\nfunction plannedWrites(choices: WizardChoices, dir: string): readonly PlannedWrite[] {\n const plan = (name: string, content: string): PlannedWrite => {\n const path = join(dir, name);\n const exists = existsSync(path);\n const unchanged = exists && readFileSync(path, 'utf8') === content;\n return { name, path, content, exists, unchanged };\n };\n const writes = [plan(CONFIG_FILE_NAME, renderConfig(chosenConfig(choices)))];\n if (choices.measureStub) writes.push(plan(MEASURE_STUB_FILE, measureStubSource()));\n if (choices.rerankStub) writes.push(plan(RERANK_STUB_FILE, rerankStubSource()));\n return writes;\n}\n\n/**\n * The config the wizard's choices mean — the wizard's *policy*, handed to the one\n * writer in `config.ts`. `strategy` and `store` are always written: the wizard asked\n * about both, and a chosen value left out of the file would be a setting the user\n * watched themselves pick and never got.\n */\nfunction chosenConfig(choices: {\n readonly budgetBytes: number | undefined;\n readonly store: SmeltConfigStore;\n readonly strategy: Strategy;\n /** Carried through from an existing config; this wizard never edits it. */\n readonly hooks?: SmeltConfigHooks | undefined;\n}): SmeltConfig {\n return {\n smeltConfig: CONFIG_VERSION,\n ...(choices.budgetBytes === undefined ? {} : { defaultBudgetBytes: choices.budgetBytes }),\n strategy: choices.strategy,\n store: choices.store,\n ...(choices.hooks === undefined ? {} : { hooks: choices.hooks }),\n };\n}\n\n// ---------------------------------------------------------------------------\n// The generated stubs. String literals on purpose: smelt's own import graph must not\n// gain an HTTP client, and the zero-network guard's string-stripper ignores string\n// bodies — the sketched fetch below lives in the CONSUMER'S file, never in smelt's.\n//\n// The comment token between `from` and the package name in each stub's import line is\n// deliberate: the guard's import scanner reads raw source, so spelling that import\n// plainly inside this template would register as an edge in smelt's own graph — which\n// it is not. The comment keeps the generated file valid TypeScript while keeping this\n// data out of the guard's walk.\n// ---------------------------------------------------------------------------\n\n/** The `smelt.measure.ts` the wizard writes. Exported so tests can compile it. */\nexport function measureStubSource(): string {\n return `/**\n * Measure hook — generated by \\`smelt init\\`.\n *\n * smelt's budgets are UTF-8 bytes, permanently; this hook adds a second, labelled\n * number to every result in YOUR unit, counted by YOUR tokenizer. Both \\`id\\` and\n * \\`unit\\` are required: a count without the counter named is not a measurement.\n *\n * Wire it in, in your own code:\n *\n * const smelter = createSmelter({ defaultBudgetBytes: 8_000, measure });\n *\n * with createSmelter imported from your @smeltjs/core install and measure from here.\n */\nimport type { Measure } from /* your install */ '@smeltjs/core';\n\nexport const measure: Measure = {\n // TODO: name the counter that produces these numbers, e.g. 'tiktoken/o200k_base'.\n id: 'TODO/your-tokenizer',\n unit: 'tokens',\n count(text: string): number {\n // TODO: replace with your real tokenizer. It must be local and synchronous —\n // a count() that calls an API would make your process call an API on every smelt.\n // e.g. return encode(text).length;\n throw new Error(\n 'smelt.measure.ts: count() is not implemented yet — ' +\n 'fill it in with your tokenizer (length of input: ' + String(text.length) + ')',\n );\n },\n};\n`;\n}\n\n/** The `smelt.rerank.ts` the wizard writes. Exported so tests can compile it. */\nexport function rerankStubSource(): string {\n return `/**\n * Reranker stub — generated by \\`smelt init\\`.\n *\n * smelt itself makes zero network calls and never bundles a reranker (Law 1): the\n * moment one ships as a default, every consumer's source code leaves the machine and\n * they find out from a changelog, or never. So the outbound call lives HERE, in your\n * file, reading your env var, visible in your own review.\n *\n * Wire it into your own pipeline; smelt never calls this for you.\n */\nimport type { RerankCandidate, RerankedCandidate, RerankStage } from /* your install */ '@smeltjs/core';\n\n/** The env var YOUR code reads. Rename it to match your vendor. */\nconst API_KEY_ENV = 'RERANKER_API_KEY';\n\nexport const rerank: RerankStage = {\n id: 'my-reranker/v1',\n async rerank(\n candidates: readonly RerankCandidate[],\n query: string,\n ): Promise<readonly RerankedCandidate[]> {\n const apiKey = process.env[API_KEY_ENV];\n if (apiKey === undefined || apiKey === '') {\n throw new Error(\n 'smelt.rerank.ts: ' + API_KEY_ENV + ' is not set. This stage makes an outbound ' +\n 'HTTP call from YOUR code with YOUR key; without a key it refuses to pretend.',\n );\n }\n\n // TODO: the outbound call. Voyage AI is one example vendor; any reranker with an\n // HTTP API fits this shape:\n //\n // const response = await fetch('https://api.voyageai.com/v1/rerank', {\n // method: 'POST',\n // headers: {\n // 'content-type': 'application/json',\n // authorization: \\`Bearer \\${apiKey}\\`,\n // },\n // body: JSON.stringify({\n // query,\n // documents: candidates.map((candidate) => candidate.text),\n // }),\n // });\n // if (!response.ok) throw new Error('rerank failed: ' + String(response.status));\n // const body = (await response.json()) as {\n // data: { index: number; relevance_score: number }[];\n // };\n // return body.data.map(({ index, relevance_score }) => ({\n // ...candidates[index]!,\n // score: relevance_score,\n // }));\n //\n void candidates;\n void query;\n throw new Error(\n 'smelt.rerank.ts: implement the outbound call sketched above, then delete this throw.',\n );\n },\n};\n`;\n}\n"]}
package/dist/cli/init.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, join, resolve } from 'node:path';
3
- import { CliUsageError } from '../errors.js';
4
3
  import { DEFAULT_STRATEGY, STRATEGIES } from '../plan/planners.js';
5
4
  import { STRUCTURAL_LANGUAGES } from '../plan/structural.js';
6
- import { answerReader, CLI_NAME } from './shell.js';
5
+ import { SETUP_RECIPE } from '../setup/recipe.js';
6
+ import { CLI_NAME } from './shell.js';
7
+ import { wizardAsk } from './wizard.js';
7
8
  import { CONFIG_FILE_NAME, CONFIG_VERSION, findConfigFile, parseConfig, renderConfig, } from './config.js';
8
9
  /**
9
10
  * `smelt init` — the setup wizard.
@@ -48,16 +49,9 @@ export async function runInit(io) {
48
49
  // `answerReader` — not `Readable.from` + readline — because the wizard must *let go*
49
50
  // of stdin when it is done. See the note on `answerReader` for the hang that shape
50
51
  // caused: files written, `Done.` printed, and the process never exiting.
51
- const lines = answerReader(io.input);
52
- const ask = async (prompt) => {
53
- io.output(prompt);
54
- const next = await lines.next();
55
- if (next === undefined) {
56
- throw new CliUsageError(`${CLI_NAME} init: input ended before the wizard finished. ` +
57
- `Files already confirmed and written stay; nothing further was written.`);
58
- }
59
- return next.trim();
60
- };
52
+ const wizard = wizardAsk(io.input, io.output, `${CLI_NAME} init: input ended before the wizard finished. ` +
53
+ `Files already confirmed and written stay; nothing further was written.`);
54
+ const ask = wizard.ask;
61
55
  try {
62
56
  const existing = loadExisting(io.cwd);
63
57
  return existing === undefined
@@ -65,7 +59,7 @@ export async function runInit(io) {
65
59
  : await editRun(io, ask, existing.path, existing.config);
66
60
  }
67
61
  finally {
68
- await lines.release();
62
+ await wizard.release();
69
63
  }
70
64
  }
71
65
  function loadExisting(cwd) {
@@ -303,7 +297,7 @@ async function stepStore(io, ask, choices) {
303
297
  return 'ok';
304
298
  }
305
299
  if (pick === '2') {
306
- const previous = choices.store.kind === 'directory' ? choices.store.path : '.smelt/store';
300
+ const previous = choices.store.kind === 'directory' ? choices.store.path : SETUP_RECIPE.store.defaultDir;
307
301
  const path = await ask(`store directory, relative to ${CONFIG_FILE_NAME} [${previous}]> `);
308
302
  if (path === 'back')
309
303
  continue; // back to the store choice, not out of the step