@smeltjs/core 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apply.d.ts.map +1 -1
- package/dist/apply.js +8 -2
- package/dist/apply.js.map +1 -1
- package/dist/cli/bin.d.ts.map +1 -1
- package/dist/cli/bin.js +9 -4
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/doctor.d.ts +66 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +137 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/hooks.d.ts +36 -2
- package/dist/cli/hooks.d.ts.map +1 -1
- package/dist/cli/hooks.js +108 -64
- package/dist/cli/hooks.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +12 -16
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/installed.d.ts +56 -0
- package/dist/cli/installed.d.ts.map +1 -0
- package/dist/cli/installed.js +105 -0
- package/dist/cli/installed.js.map +1 -0
- package/dist/cli/lava.d.ts +31 -0
- package/dist/cli/lava.d.ts.map +1 -0
- package/dist/cli/lava.js +82 -0
- package/dist/cli/lava.js.map +1 -0
- package/dist/cli/report.d.ts +10 -1
- package/dist/cli/report.d.ts.map +1 -1
- package/dist/cli/report.js +16 -1
- package/dist/cli/report.js.map +1 -1
- package/dist/cli/setup.d.ts +87 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +513 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli/shell.d.ts +11 -4
- package/dist/cli/shell.d.ts.map +1 -1
- package/dist/cli/shell.js.map +1 -1
- package/dist/cli/subcommands/agents.d.ts.map +1 -1
- package/dist/cli/subcommands/agents.js +2 -1
- package/dist/cli/subcommands/agents.js.map +1 -1
- package/dist/cli/subcommands/doctor.d.ts +15 -0
- package/dist/cli/subcommands/doctor.d.ts.map +1 -0
- package/dist/cli/subcommands/doctor.js +43 -0
- package/dist/cli/subcommands/doctor.js.map +1 -0
- package/dist/cli/subcommands/flags.d.ts +16 -0
- package/dist/cli/subcommands/flags.d.ts.map +1 -1
- package/dist/cli/subcommands/flags.js +43 -4
- package/dist/cli/subcommands/flags.js.map +1 -1
- package/dist/cli/subcommands/hooks.d.ts.map +1 -1
- package/dist/cli/subcommands/hooks.js +10 -2
- package/dist/cli/subcommands/hooks.js.map +1 -1
- package/dist/cli/subcommands/init.d.ts.map +1 -1
- package/dist/cli/subcommands/init.js +2 -1
- package/dist/cli/subcommands/init.js.map +1 -1
- package/dist/cli/subcommands/registry.d.ts +3 -1
- package/dist/cli/subcommands/registry.d.ts.map +1 -1
- package/dist/cli/subcommands/registry.js +6 -0
- package/dist/cli/subcommands/registry.js.map +1 -1
- package/dist/cli/subcommands/setup.d.ts +23 -0
- package/dist/cli/subcommands/setup.d.ts.map +1 -0
- package/dist/cli/subcommands/setup.js +87 -0
- package/dist/cli/subcommands/setup.js.map +1 -0
- package/dist/cli/subcommands/smelt.d.ts +3 -0
- package/dist/cli/subcommands/smelt.d.ts.map +1 -1
- package/dist/cli/subcommands/smelt.js +5 -1
- package/dist/cli/subcommands/smelt.js.map +1 -1
- package/dist/cli/subcommands/stats.d.ts +8 -3
- package/dist/cli/subcommands/stats.d.ts.map +1 -1
- package/dist/cli/subcommands/stats.js +15 -5
- package/dist/cli/subcommands/stats.js.map +1 -1
- package/dist/cli/subcommands/subcommand.d.ts +1 -1
- package/dist/cli/subcommands/subcommand.d.ts.map +1 -1
- package/dist/cli/subcommands/subcommand.js.map +1 -1
- package/dist/cli/wizard.d.ts +72 -0
- package/dist/cli/wizard.d.ts.map +1 -0
- package/dist/cli/wizard.js +86 -0
- package/dist/cli/wizard.js.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +10 -0
- package/dist/errors.js.map +1 -1
- package/dist/harness/claude-code.d.ts.map +1 -1
- package/dist/harness/claude-code.js +10 -0
- package/dist/harness/claude-code.js.map +1 -1
- package/dist/harness/codex.d.ts.map +1 -1
- package/dist/harness/codex.js +1 -0
- package/dist/harness/codex.js.map +1 -1
- package/dist/harness/grok.d.ts.map +1 -1
- package/dist/harness/grok.js +4 -1
- package/dist/harness/grok.js.map +1 -1
- package/dist/harness/kilocode.d.ts.map +1 -1
- package/dist/harness/kilocode.js +1 -1
- package/dist/harness/kilocode.js.map +1 -1
- package/dist/harness/opencode.d.ts.map +1 -1
- package/dist/harness/opencode.js +10 -0
- package/dist/harness/opencode.js.map +1 -1
- package/dist/harness/profile.d.ts +23 -2
- package/dist/harness/profile.d.ts.map +1 -1
- package/dist/harness/profile.js.map +1 -1
- package/dist/harness/snippet.d.ts +14 -1
- package/dist/harness/snippet.d.ts.map +1 -1
- package/dist/harness/snippet.js +18 -2
- package/dist/harness/snippet.js.map +1 -1
- package/dist/hooks/focus-terms.d.ts +57 -0
- package/dist/hooks/focus-terms.d.ts.map +1 -0
- package/dist/hooks/focus-terms.js +230 -0
- package/dist/hooks/focus-terms.js.map +1 -0
- package/dist/hooks/guard-core.d.ts +8 -16
- package/dist/hooks/guard-core.d.ts.map +1 -1
- package/dist/hooks/guard-core.js +22 -120
- package/dist/hooks/guard-core.js.map +1 -1
- package/dist/index.d.ts +16 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/ops/index.d.ts +3 -3
- package/dist/ops/index.d.ts.map +1 -1
- package/dist/ops/index.js +2 -2
- package/dist/ops/index.js.map +1 -1
- package/dist/ops/verbs.d.ts +57 -1
- package/dist/ops/verbs.d.ts.map +1 -1
- package/dist/ops/verbs.js +49 -1
- package/dist/ops/verbs.js.map +1 -1
- package/dist/plan/auto.d.ts +9 -2
- package/dist/plan/auto.d.ts.map +1 -1
- package/dist/plan/auto.js +14 -2
- package/dist/plan/auto.js.map +1 -1
- package/dist/plan/diff.d.ts +45 -0
- package/dist/plan/diff.d.ts.map +1 -0
- package/dist/plan/diff.js +284 -0
- package/dist/plan/diff.js.map +1 -0
- package/dist/plan/json.d.ts +37 -0
- package/dist/plan/json.d.ts.map +1 -0
- package/dist/plan/json.js +181 -0
- package/dist/plan/json.js.map +1 -0
- package/dist/plan/kind.d.ts +28 -0
- package/dist/plan/kind.d.ts.map +1 -0
- package/dist/plan/kind.js +57 -0
- package/dist/plan/kind.js.map +1 -0
- package/dist/plan/offsets.d.ts +8 -0
- package/dist/plan/offsets.d.ts.map +1 -0
- package/dist/plan/offsets.js +19 -0
- package/dist/plan/offsets.js.map +1 -0
- package/dist/plan/planners.d.ts +15 -5
- package/dist/plan/planners.d.ts.map +1 -1
- package/dist/plan/planners.js +13 -5
- package/dist/plan/planners.js.map +1 -1
- package/dist/plan/structural.d.ts.map +1 -1
- package/dist/plan/structural.js +106 -14
- package/dist/plan/structural.js.map +1 -1
- package/dist/retrieve.d.ts +22 -1
- package/dist/retrieve.d.ts.map +1 -1
- package/dist/retrieve.js +59 -0
- package/dist/retrieve.js.map +1 -1
- package/dist/setup/recipe.d.ts +73 -0
- package/dist/setup/recipe.d.ts.map +1 -0
- package/dist/setup/recipe.js +76 -0
- package/dist/setup/recipe.js.map +1 -0
- package/dist/smelter.d.ts +4 -0
- package/dist/smelter.d.ts.map +1 -1
- package/dist/smelter.js +4 -0
- package/dist/smelter.js.map +1 -1
- package/dist/stats.d.ts +16 -1
- package/dist/stats.d.ts.map +1 -1
- package/dist/stats.js +29 -0
- package/dist/stats.js.map +1 -1
- package/dist/store-dir.d.ts +9 -2
- package/dist/store-dir.d.ts.map +1 -1
- package/dist/store-dir.js +48 -7
- package/dist/store-dir.js.map +1 -1
- package/dist/store.d.ts +4 -2
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +12 -2
- package/dist/store.js.map +1 -1
- package/dist/text/json-edit.d.ts +15 -1
- package/dist/text/json-edit.d.ts.map +1 -1
- package/dist/text/json-edit.js +76 -5
- package/dist/text/json-edit.js.map +1 -1
- package/dist/types.d.ts +116 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-edit.js","sourceRoot":"","sources":["../../src/text/json-edit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Byte-faithful edits to somebody else's text file.\n *\n * Two editors, one contract: **change the one thing you were asked to change and\n * leave every other byte alone.** An installer that reformats a settings file has\n * edited what it was never asked to — key order, indentation, string escapes, number\n * spellings and unknown keys are all somebody's choices, and `JSON.parse` →\n * `JSON.stringify` erases every one of them.\n *\n * - {@link editTopLevelProperty}: replace, insert or remove **one top-level property**\n * of a JSON object, in its source text. The value is rendered fresh; nothing\n * outside its bytes moves. Callers have already `JSON.parse`d the text and decided\n * what the new value is; this module holds only the tokenising.\n * - {@link upsertMarkerBlock} / {@link stripMarkerBlock}: the same idea over a\n * delimited block in a plain-text file (an instruction file, a Markdown snippet\n * between two marker lines).\n *\n * Neither knows what a harness or a hook is. `cli/hooks.ts` is the consumer today; the\n * module lives under `src/text/` rather than `cli/` because its whole interface is\n * strings in, strings out — it reads no argv, prints nothing and imports nothing from\n * the CLI, and the next byte-faithful editor (an instruction-file rewrite) wants a\n * sibling here, not a CLI import.\n */\n\n/** How a JSON file is laid out — what a rendered value must match to blend in. */\nexport interface JsonStyle {\n /** The indentation unit — the whitespace before a top-level key. */\n readonly indent: string;\n /** `'\\r\\n'` when the file uses it anywhere, `'\\n'` otherwise. */\n readonly newline: string;\n}\n\n/**\n * The layout an existing file uses: its first indented key's leading whitespace (two\n * spaces when nothing is indented) and its newline convention. Detected once, before\n * any edit, so a sequence of edits renders consistently even after an earlier one has\n * changed the first indented line.\n */\nexport function jsonStyle(text: string): JsonStyle {\n return {\n newline: text.includes('\\r\\n') ? '\\r\\n' : '\\n',\n indent: /\\n([ \\t]+)\"/.exec(text)?.[1] ?? ' ',\n };\n}\n\n/**\n * Replace, insert or remove one top-level property of the JSON object in `text`,\n * leaving every other byte verbatim.\n *\n * - `value` defined: the property's value is replaced in place when the key exists,\n * otherwise the property is appended after the last one (or into an empty object).\n * - `value === undefined`: the property is removed, with its separating comma and\n * whitespace; a key that is not there is a no-op and the text comes back unchanged.\n *\n * The rendered value is `JSON.stringify(value, null, indent)`, re-indented to sit at\n * top level. `style` defaults to {@link jsonStyle} of `text`; pass it explicitly when\n * making several edits to one file.\n *\n * Returns `undefined` when `text` is not a JSON object the scanner can walk — an\n * array, a scalar, or something that is not JSON at all. Callers should have\n * `JSON.parse`d first and refused; this is belt and braces, not a validator.\n */\nexport function editTopLevelProperty(\n text: string,\n key: string,\n value: unknown,\n style: JsonStyle = jsonStyle(text),\n): string | undefined {\n const scan = scanJsonTopLevel(text);\n if (scan === undefined) return undefined;\n const property = scan.properties.find((candidate) => candidate.key === key);\n if (value === undefined) {\n return property === undefined ? text : removeJsonProperty(text, scan, property);\n }\n const rendered = renderJsonValue(value, style.indent, style.newline);\n return property !== undefined\n ? `${text.slice(0, property.valueStart)}${rendered}${text.slice(property.valueEnd)}`\n : insertJsonProperty(text, scan, key, rendered, style.indent, style.newline);\n}\n\n/** One top-level property of a JSON object, located by offsets in its source text. */\ninterface JsonTopLevelProperty {\n readonly key: string;\n /** Offset of the key's opening quote. */\n readonly keyStart: number;\n /** Offset of the value's first byte. */\n readonly valueStart: number;\n /** Offset one past the value's last byte. */\n readonly valueEnd: number;\n}\n\ninterface JsonTopLevelScan {\n /** Offset of the root object's `{`. */\n readonly open: number;\n /** Offset of the root object's `}`. */\n readonly close: number;\n readonly properties: readonly JsonTopLevelProperty[];\n}\n\n/**\n * Locate the top-level properties of a JSON object *in its source text*, so one\n * property can be replaced, inserted or removed while every other byte of the file\n * rides through verbatim. `undefined` when the text is not an object.\n */\nfunction scanJsonTopLevel(text: string): JsonTopLevelScan | undefined {\n let i = skipJsonWhitespace(text, 0);\n if (text[i] !== '{') return undefined;\n const open = i;\n i = skipJsonWhitespace(text, i + 1);\n const properties: JsonTopLevelProperty[] = [];\n if (text[i] === '}') return { open, close: i, properties };\n for (;;) {\n if (text[i] !== '\"') return undefined;\n const keyStart = i;\n const keyEnd = skipJsonString(text, i);\n if (keyEnd === undefined) return undefined;\n const key = JSON.parse(text.slice(keyStart, keyEnd)) as string;\n i = skipJsonWhitespace(text, keyEnd);\n if (text[i] !== ':') return undefined;\n const valueStart = skipJsonWhitespace(text, i + 1);\n const valueEnd = skipJsonValue(text, valueStart);\n if (valueEnd === undefined) return undefined;\n properties.push({ key, keyStart, valueStart, valueEnd });\n i = skipJsonWhitespace(text, valueEnd);\n if (text[i] === ',') {\n i = skipJsonWhitespace(text, i + 1);\n continue;\n }\n if (text[i] === '}') return { open, close: i, properties };\n return undefined;\n }\n}\n\nfunction skipJsonWhitespace(text: string, from: number): number {\n let i = from;\n while (i < text.length && ' \\t\\r\\n'.includes(text[i]!)) i += 1;\n return i;\n}\n\n/** `from` points at `\"`; returns the offset one past the closing quote. */\nfunction skipJsonString(text: string, from: number): number | undefined {\n let i = from + 1;\n while (i < text.length) {\n if (text[i] === '\\\\') i += 2;\n else if (text[i] === '\"') return i + 1;\n else i += 1;\n }\n return undefined;\n}\n\nfunction skipJsonValue(text: string, from: number): number | undefined {\n const first = text[from];\n if (first === '\"') return skipJsonString(text, from);\n if (first === '{' || first === '[') {\n let depth = 0;\n let i = from;\n while (i < text.length) {\n const ch = text[i]!;\n if (ch === '\"') {\n const end = skipJsonString(text, i);\n if (end === undefined) return undefined;\n i = end;\n continue;\n }\n if (ch === '{' || ch === '[') depth += 1;\n else if (ch === '}' || ch === ']') {\n depth -= 1;\n if (depth === 0) return i + 1;\n }\n i += 1;\n }\n return undefined;\n }\n // number / true / false / null\n let i = from;\n while (i < text.length && !',}] \\t\\r\\n'.includes(text[i]!)) i += 1;\n return i > from ? i : undefined;\n}\n\n/** A JSON value indented for embedding at a top-level property position. */\nfunction renderJsonValue(value: unknown, indent: string, newline: string): string {\n return JSON.stringify(value, null, indent).split('\\n').join(`${newline}${indent}`);\n}\n\nfunction removeJsonProperty(\n text: string,\n scan: JsonTopLevelScan,\n property: JsonTopLevelProperty,\n): string {\n const index = scan.properties.indexOf(property);\n const next = scan.properties[index + 1];\n if (next !== undefined) {\n // Delete through the separating comma and whitespace, up to the next key.\n return text.slice(0, property.keyStart) + text.slice(next.keyStart);\n }\n const previous = scan.properties[index - 1];\n // Last (or only) property: delete the preceding comma (if any) with it.\n const from = previous !== undefined ? previous.valueEnd : scan.open + 1;\n return text.slice(0, from) + text.slice(property.valueEnd);\n}\n\nfunction insertJsonProperty(\n text: string,\n scan: JsonTopLevelScan,\n key: string,\n renderedValue: string,\n indent: string,\n newline: string,\n): string {\n const entry = `${JSON.stringify(key)}: ${renderedValue}`;\n if (scan.properties.length === 0) {\n return `${text.slice(0, scan.open + 1)}${newline}${indent}${entry}${newline}${text.slice(scan.close)}`;\n }\n const last = scan.properties[scan.properties.length - 1]!;\n return `${text.slice(0, last.valueEnd)},${newline}${indent}${entry}${text.slice(last.valueEnd)}`;\n}\n\n/* ------------------------------------------------------------------------------------\n * Marker blocks\n * ---------------------------------------------------------------------------------- */\n\n/**\n * Replace the block delimited by `start` … `end` in `existingText`, or append it.\n *\n * An absent or blank file becomes exactly `block`. A file that carries the block has\n * it replaced in place (one newline after `end` is absorbed, so a block that ends in\n * its own newline does not grow a blank line per re-run). A file without it gets the\n * block appended after exactly one blank line, whatever trailing newlines it had.\n */\nexport function upsertMarkerBlock(\n existingText: string | undefined,\n block: string,\n start: string,\n end: string,\n): string {\n if (existingText === undefined || existingText.trim() === '') return block;\n const startIndex = existingText.indexOf(start);\n const endIndex = existingText.indexOf(end);\n if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {\n const before = existingText.slice(0, startIndex);\n const after = existingText.slice(endIndex + end.length).replace(/^\\n/, '');\n return `${before}${block}${after}`;\n }\n return `${existingText.replace(/\\n*$/, '\\n\\n')}${block}`;\n}\n\n/**\n * Remove the block delimited by `start` … `end`. The text comes back unchanged when\n * the block is not there; `undefined` when nothing (or only whitespace) remains — the\n * file was entirely the block, and the caller decides whether to delete it.\n */\nexport function stripMarkerBlock(\n existingText: string,\n start: string,\n end: string,\n): string | undefined {\n const startIndex = existingText.indexOf(start);\n const endIndex = existingText.indexOf(end);\n if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) return existingText;\n const stripped =\n existingText.slice(0, startIndex).replace(/\\n+$/, '\\n') +\n existingText.slice(endIndex + end.length).replace(/^\\n+/, '');\n return stripped.trim() === '' ? undefined : stripped;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"json-edit.js","sourceRoot":"","sources":["../../src/text/json-edit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAUH;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI;QAC9C,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI;KAC9C,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAY,EACZ,GAAW,EACX,KAAc,EACd,KAAK,GAAc,SAAS,CAAC,IAAI,CAAC,EAClC,WAAW,GAAW,KAAK,CAAC,MAAM;IAElC,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;IAC5E,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAClF,CAAC;IACD,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAClF,OAAO,QAAQ,KAAK,SAAS;QAC3B,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;QACpF,CAAC,CAAC,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;AACjF,CAAC;AAqBD;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,IAAY;IACpC,IAAI,CAAC,GAAG,kBAAkB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACpC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,OAAO,SAAS,CAAC;IACtC,MAAM,IAAI,GAAG,CAAC,CAAC;IACf,CAAC,GAAG,kBAAkB,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACpC,MAAM,UAAU,GAA2B,EAAE,CAAC;IAC9C,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3D,SAAS,CAAC;QACR,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,OAAO,SAAS,CAAC;QACtC,MAAM,QAAQ,GAAG,CAAC,CAAC;QACnB,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACvC,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAW,CAAC;QAC/D,CAAC,GAAG,kBAAkB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACrC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,OAAO,SAAS,CAAC;QACtC,MAAM,UAAU,GAAG,kBAAkB,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACjD,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC7C,UAAU,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;QACzD,CAAC,GAAG,kBAAkB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACvC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACpB,CAAC,GAAG,kBAAkB,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YACpC,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC;QAC3D,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAY,EAAE,IAAY;IACpD,IAAI,CAAC,GAAG,IAAI,CAAC;IACb,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC;QAAE,CAAC,IAAI,CAAC,CAAC;IAC/D,OAAO,CAAC,CAAC;AACX,CAAC;AAED,2EAA2E;AAC3E,SAAS,cAAc,CAAC,IAAY,EAAE,IAAY;IAChD,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;IACjB,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI;YAAE,CAAC,IAAI,CAAC,CAAC;aACxB,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,OAAO,CAAC,GAAG,CAAC,CAAC;;YAClC,CAAC,IAAI,CAAC,CAAC;IACd,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,aAAa,CAAC,IAAY,EAAE,IAAY;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IACzB,IAAI,KAAK,KAAK,GAAG;QAAE,OAAO,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACrD,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;QACnC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,CAAC,GAAG,IAAI,CAAC;QACb,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACvB,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;YACpB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;gBACpC,IAAI,GAAG,KAAK,SAAS;oBAAE,OAAO,SAAS,CAAC;gBACxC,CAAC,GAAG,GAAG,CAAC;gBACR,SAAS;YACX,CAAC;YACD,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG;gBAAE,KAAK,IAAI,CAAC,CAAC;iBACpC,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBAClC,KAAK,IAAI,CAAC,CAAC;gBACX,IAAI,KAAK,KAAK,CAAC;oBAAE,OAAO,CAAC,GAAG,CAAC,CAAC;YAChC,CAAC;YACD,CAAC,IAAI,CAAC,CAAC;QACT,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,+BAA+B;IAC/B,IAAI,CAAC,GAAG,IAAI,CAAC;IACb,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC;QAAE,CAAC,IAAI,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAClC,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,eAAe,CACtB,KAAc,EACd,eAAuB,EACvB,UAAkB,EAClB,OAAe;IAEf,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,GAAG,UAAU,EAAE,CAAC,CAAC;AAClG,CAAC;AAED,SAAS,kBAAkB,CACzB,IAAY,EACZ,IAAsB,EACtB,QAA8B;IAE9B,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACxC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,0EAA0E;QAC1E,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACtE,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IAC5C,wEAAwE;IACxE,MAAM,IAAI,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;IACxE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,kBAAkB,CACzB,IAAY,EACZ,IAAsB,EACtB,GAAW,EACX,aAAqB,EACrB,MAAc,EACd,OAAe;IAEf,MAAM,KAAK,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,aAAa,EAAE,CAAC;IACzD,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;IACzG,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;IAC1D,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;AACnG,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,IAA+C,EAC/C,KAAc,EACd,KAAK,GAAc,SAAS,CAAC,IAAI,CAAC;IAElC,OAAO,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;AACpE,CAAC;AAED,wEAAwE;AACxE,SAAS,kBAAkB,CACzB,IAAY,EACZ,IAAuB,EACvB,KAAc,EACd,KAAgB,EAChB,UAAkB;IAElB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;IACtB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;IACzF,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC;IAC7E,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,CAAC,8CAA8C;QACpF,2EAA2E;QAC3E,sEAAsE;QACtE,MAAM,SAAS,GAA4B,EAAE,CAAC;QAC9C,IAAI,MAAM,GAAG,SAAS,CAAC;QACvB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpC,MAAM,IAAI,GAA4B,EAAE,CAAC;YACzC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;YACnB,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,GAAG,KAAK,CAAC;QACvC,OAAO,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAC5D,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACjE,+EAA+E;IAC/E,kFAAkF;IAClF,gFAAgF;IAChF,+DAA+D;IAC/D,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IACnF,MAAM,MAAM,GAAG,kBAAkB,CAC/B,KAAK,EACL,IAAI,EACJ,KAAK,EACL,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,EAClC,UAAU,CACX,CAAC;IACF,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC,CAAC,yCAAyC;IACrF,IAAI,MAAM,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAClC,IAAI,KAAK,KAAK,SAAS,IAAI,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC;QACxD,+EAA+E;QAC/E,gFAAgF;QAChF,OAAO,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC;IACpE,CAAC;IACD,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC1F,CAAC;AAED,oEAAoE;AACpE,SAAS,oBAAoB,CAAC,IAAY;IACxC,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC;AAC5D,CAAC;AAED;;wFAEwF;AAExF;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAC/B,YAAgC,EAChC,KAAa,EACb,KAAa,EACb,GAAW;IAEX,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAC3E,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,UAAU,KAAK,CAAC,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,IAAI,QAAQ,GAAG,UAAU,EAAE,CAAC;QAClE,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;QACjD,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC3E,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,CAAC;IACrC,CAAC;IACD,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AAC3D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAC9B,YAAoB,EACpB,KAAa,EACb,GAAW;IAEX,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,UAAU,KAAK,CAAC,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,IAAI,QAAQ,IAAI,UAAU;QAAE,OAAO,YAAY,CAAC;IACxF,MAAM,QAAQ,GACZ,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC;QACvD,YAAY,CAAC,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChE,OAAO,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;AACvD,CAAC","sourcesContent":["/**\n * Byte-faithful edits to somebody else's text file.\n *\n * Two editors, one contract: **change the one thing you were asked to change and\n * leave every other byte alone.** An installer that reformats a settings file has\n * edited what it was never asked to — key order, indentation, string escapes, number\n * spellings and unknown keys are all somebody's choices, and `JSON.parse` →\n * `JSON.stringify` erases every one of them.\n *\n * - {@link editTopLevelProperty}: replace, insert or remove **one top-level property**\n * of a JSON object, in its source text. The value is rendered fresh; nothing\n * outside its bytes moves. Callers have already `JSON.parse`d the text and decided\n * what the new value is; this module holds only the tokenising.\n * - {@link upsertMarkerBlock} / {@link stripMarkerBlock}: the same idea over a\n * delimited block in a plain-text file (an instruction file, a Markdown snippet\n * between two marker lines).\n *\n * Neither knows what a harness or a hook is. `cli/hooks.ts` is the consumer today; the\n * module lives under `src/text/` rather than `cli/` because its whole interface is\n * strings in, strings out — it reads no argv, prints nothing and imports nothing from\n * the CLI, and the next byte-faithful editor (an instruction-file rewrite) wants a\n * sibling here, not a CLI import.\n */\n\n/** How a JSON file is laid out — what a rendered value must match to blend in. */\nexport interface JsonStyle {\n /** The indentation unit — the whitespace before a top-level key. */\n readonly indent: string;\n /** `'\\r\\n'` when the file uses it anywhere, `'\\n'` otherwise. */\n readonly newline: string;\n}\n\n/**\n * The layout an existing file uses: its first indented key's leading whitespace (two\n * spaces when nothing is indented) and its newline convention. Detected once, before\n * any edit, so a sequence of edits renders consistently even after an earlier one has\n * changed the first indented line.\n */\nexport function jsonStyle(text: string): JsonStyle {\n return {\n newline: text.includes('\\r\\n') ? '\\r\\n' : '\\n',\n indent: /\\n([ \\t]+)\"/.exec(text)?.[1] ?? ' ',\n };\n}\n\n/**\n * Replace, insert or remove one top-level property of the JSON object in `text`,\n * leaving every other byte verbatim.\n *\n * - `value` defined: the property's value is replaced in place when the key exists,\n * otherwise the property is appended after the last one (or into an empty object).\n * - `value === undefined`: the property is removed, with its separating comma and\n * whitespace; a key that is not there is a no-op and the text comes back unchanged.\n *\n * The rendered value is `JSON.stringify(value, null, indent)`, re-indented to sit at\n * top level. `style` defaults to {@link jsonStyle} of `text`; pass it explicitly when\n * making several edits to one file.\n *\n * Returns `undefined` when `text` is not a JSON object the scanner can walk — an\n * array, a scalar, or something that is not JSON at all. Callers should have\n * `JSON.parse`d first and refused; this is belt and braces, not a validator.\n */\nexport function editTopLevelProperty(\n text: string,\n key: string,\n value: unknown,\n style: JsonStyle = jsonStyle(text),\n valueIndent: string = style.indent,\n): string | undefined {\n const scan = scanJsonTopLevel(text);\n if (scan === undefined) return undefined;\n const property = scan.properties.find((candidate) => candidate.key === key);\n if (value === undefined) {\n return property === undefined ? text : removeJsonProperty(text, scan, property);\n }\n const rendered = renderJsonValue(value, valueIndent, style.indent, style.newline);\n return property !== undefined\n ? `${text.slice(0, property.valueStart)}${rendered}${text.slice(property.valueEnd)}`\n : insertJsonProperty(text, scan, key, rendered, style.indent, style.newline);\n}\n\n/** One top-level property of a JSON object, located by offsets in its source text. */\ninterface JsonTopLevelProperty {\n readonly key: string;\n /** Offset of the key's opening quote. */\n readonly keyStart: number;\n /** Offset of the value's first byte. */\n readonly valueStart: number;\n /** Offset one past the value's last byte. */\n readonly valueEnd: number;\n}\n\ninterface JsonTopLevelScan {\n /** Offset of the root object's `{`. */\n readonly open: number;\n /** Offset of the root object's `}`. */\n readonly close: number;\n readonly properties: readonly JsonTopLevelProperty[];\n}\n\n/**\n * Locate the top-level properties of a JSON object *in its source text*, so one\n * property can be replaced, inserted or removed while every other byte of the file\n * rides through verbatim. `undefined` when the text is not an object.\n */\nfunction scanJsonTopLevel(text: string): JsonTopLevelScan | undefined {\n let i = skipJsonWhitespace(text, 0);\n if (text[i] !== '{') return undefined;\n const open = i;\n i = skipJsonWhitespace(text, i + 1);\n const properties: JsonTopLevelProperty[] = [];\n if (text[i] === '}') return { open, close: i, properties };\n for (;;) {\n if (text[i] !== '\"') return undefined;\n const keyStart = i;\n const keyEnd = skipJsonString(text, i);\n if (keyEnd === undefined) return undefined;\n const key = JSON.parse(text.slice(keyStart, keyEnd)) as string;\n i = skipJsonWhitespace(text, keyEnd);\n if (text[i] !== ':') return undefined;\n const valueStart = skipJsonWhitespace(text, i + 1);\n const valueEnd = skipJsonValue(text, valueStart);\n if (valueEnd === undefined) return undefined;\n properties.push({ key, keyStart, valueStart, valueEnd });\n i = skipJsonWhitespace(text, valueEnd);\n if (text[i] === ',') {\n i = skipJsonWhitespace(text, i + 1);\n continue;\n }\n if (text[i] === '}') return { open, close: i, properties };\n return undefined;\n }\n}\n\nfunction skipJsonWhitespace(text: string, from: number): number {\n let i = from;\n while (i < text.length && ' \\t\\r\\n'.includes(text[i]!)) i += 1;\n return i;\n}\n\n/** `from` points at `\"`; returns the offset one past the closing quote. */\nfunction skipJsonString(text: string, from: number): number | undefined {\n let i = from + 1;\n while (i < text.length) {\n if (text[i] === '\\\\') i += 2;\n else if (text[i] === '\"') return i + 1;\n else i += 1;\n }\n return undefined;\n}\n\nfunction skipJsonValue(text: string, from: number): number | undefined {\n const first = text[from];\n if (first === '\"') return skipJsonString(text, from);\n if (first === '{' || first === '[') {\n let depth = 0;\n let i = from;\n while (i < text.length) {\n const ch = text[i]!;\n if (ch === '\"') {\n const end = skipJsonString(text, i);\n if (end === undefined) return undefined;\n i = end;\n continue;\n }\n if (ch === '{' || ch === '[') depth += 1;\n else if (ch === '}' || ch === ']') {\n depth -= 1;\n if (depth === 0) return i + 1;\n }\n i += 1;\n }\n return undefined;\n }\n // number / true / false / null\n let i = from;\n while (i < text.length && !',}] \\t\\r\\n'.includes(text[i]!)) i += 1;\n return i > from ? i : undefined;\n}\n\n/**\n * A JSON value rendered for embedding at a property position. `stringifyIndent` is\n * the unit its own members nest by; `joinIndent` is where the value sits — for a\n * top-level property they are the same, and for a member of a nested container the\n * value sits at the member's indent while its members keep nesting by the file's\n * unit, which is the difference between `command` landing one level under `smelt`\n * or two under it.\n */\nfunction renderJsonValue(\n value: unknown,\n stringifyIndent: string,\n joinIndent: string,\n newline: string,\n): string {\n return JSON.stringify(value, null, stringifyIndent).split('\\n').join(`${newline}${joinIndent}`);\n}\n\nfunction removeJsonProperty(\n text: string,\n scan: JsonTopLevelScan,\n property: JsonTopLevelProperty,\n): string {\n const index = scan.properties.indexOf(property);\n const next = scan.properties[index + 1];\n if (next !== undefined) {\n // Delete through the separating comma and whitespace, up to the next key.\n return text.slice(0, property.keyStart) + text.slice(next.keyStart);\n }\n const previous = scan.properties[index - 1];\n // Last (or only) property: delete the preceding comma (if any) with it.\n const from = previous !== undefined ? previous.valueEnd : scan.open + 1;\n return text.slice(0, from) + text.slice(property.valueEnd);\n}\n\nfunction insertJsonProperty(\n text: string,\n scan: JsonTopLevelScan,\n key: string,\n renderedValue: string,\n indent: string,\n newline: string,\n): string {\n const entry = `${JSON.stringify(key)}: ${renderedValue}`;\n if (scan.properties.length === 0) {\n return `${text.slice(0, scan.open + 1)}${newline}${indent}${entry}${newline}${text.slice(scan.close)}`;\n }\n const last = scan.properties[scan.properties.length - 1]!;\n return `${text.slice(0, last.valueEnd)},${newline}${indent}${entry}${text.slice(last.valueEnd)}`;\n}\n\n/**\n * `editJsonProperty`: the same contract, one level deeper. Replace, insert or remove\n * the property at `path` — e.g. `['mcpServers', 'smelt']` — where the *container* is a\n * top-level property whose value is itself a JSON object. Everything outside the\n * edited bytes rides through verbatim, including sibling entries inside the container.\n *\n * When the container key is absent and a value is given, the container is created\n * fresh around the entry. When a removal empties the container, the container is\n * lifted out too — a file that never carried the key comes back byte-identical after\n * an apply → remove round trip, and one that carried other entries keeps them\n * untouched. Returns `undefined` when the container's value is not a JSON object the\n * scanner can walk (the caller refuses or skips, as with {@link editTopLevelProperty}).\n */\nexport function editJsonProperty(\n text: string,\n path: readonly [string, ...(readonly string[])],\n value: unknown,\n style: JsonStyle = jsonStyle(text),\n): string | undefined {\n return editJsonPropertyAt(text, path, value, style, style.indent);\n}\n\n/** The walker: same body, `path` as a plain (runtime-checked) array. */\nfunction editJsonPropertyAt(\n text: string,\n path: readonly string[],\n value: unknown,\n style: JsonStyle,\n baseIndent: string,\n): string | undefined {\n const head = path[0]!;\n const rest = path.slice(1);\n if (rest.length === 0) return editTopLevelProperty(text, head, value, style, baseIndent);\n const scan = scanJsonTopLevel(text);\n if (scan === undefined) return undefined;\n const property = scan.properties.find((candidate) => candidate.key === head);\n if (property === undefined) {\n if (value === undefined) return text; // nothing to remove under an absent container\n // Build the fresh container from the tail of the path, then hand it to the\n // top-level editor as a plain value — one renderer, one indent story.\n const container: Record<string, unknown> = {};\n let cursor = container;\n for (const key of rest.slice(0, -1)) {\n const next: Record<string, unknown> = {};\n cursor[key] = next;\n cursor = next;\n }\n cursor[rest[rest.length - 1]!] = value;\n return editTopLevelProperty(text, head, container, style);\n }\n const inner = text.slice(property.valueStart, property.valueEnd);\n // The container's members sit one unit deeper than the file's top level — read\n // that unit off the container's own first key, so a fresh member lands beside its\n // siblings and the member's *value* keeps nesting by the file's unit (passed as\n // valueIndent), which is what one level deeper actually means.\n const memberIndent = /\\n([ \\t]+)\"/.exec(inner)?.[1] ?? style.indent + style.indent;\n const edited = editJsonPropertyAt(\n inner,\n rest,\n value,\n { ...style, indent: memberIndent },\n baseIndent,\n );\n if (edited === undefined) return undefined; // the container's value is not an object\n if (edited === inner) return text;\n if (value === undefined && removesToEmptyObject(edited)) {\n // The container is now `{}` and it only got that way because of this removal —\n // lift it out, so a file that never carried the key round-trips byte-identical.\n return editTopLevelProperty(text, head, undefined, style) ?? text;\n }\n return `${text.slice(0, property.valueStart)}${edited}${text.slice(property.valueEnd)}`;\n}\n\n/** True when `text` is exactly a JSON object with no properties. */\nfunction removesToEmptyObject(text: string): boolean {\n const scan = scanJsonTopLevel(text);\n return scan !== undefined && scan.properties.length === 0;\n}\n\n/* ------------------------------------------------------------------------------------\n * Marker blocks\n * ---------------------------------------------------------------------------------- */\n\n/**\n * Replace the block delimited by `start` … `end` in `existingText`, or append it.\n *\n * An absent or blank file becomes exactly `block`. A file that carries the block has\n * it replaced in place (one newline after `end` is absorbed, so a block that ends in\n * its own newline does not grow a blank line per re-run). A file without it gets the\n * block appended after exactly one blank line, whatever trailing newlines it had.\n */\nexport function upsertMarkerBlock(\n existingText: string | undefined,\n block: string,\n start: string,\n end: string,\n): string {\n if (existingText === undefined || existingText.trim() === '') return block;\n const startIndex = existingText.indexOf(start);\n const endIndex = existingText.indexOf(end);\n if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {\n const before = existingText.slice(0, startIndex);\n const after = existingText.slice(endIndex + end.length).replace(/^\\n/, '');\n return `${before}${block}${after}`;\n }\n return `${existingText.replace(/\\n*$/, '\\n\\n')}${block}`;\n}\n\n/**\n * Remove the block delimited by `start` … `end`. The text comes back unchanged when\n * the block is not there; `undefined` when nothing (or only whitespace) remains — the\n * file was entirely the block, and the caller decides whether to delete it.\n */\nexport function stripMarkerBlock(\n existingText: string,\n start: string,\n end: string,\n): string | undefined {\n const startIndex = existingText.indexOf(start);\n const endIndex = existingText.indexOf(end);\n if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) return existingText;\n const stripped =\n existingText.slice(0, startIndex).replace(/\\n+$/, '\\n') +\n existingText.slice(endIndex + end.length).replace(/^\\n+/, '');\n return stripped.trim() === '' ? undefined : stripped;\n}\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -22,6 +22,20 @@ export interface ElisionReason {
|
|
|
22
22
|
export interface PlannedElision {
|
|
23
23
|
readonly range: ByteRange;
|
|
24
24
|
readonly reason: ElisionReason;
|
|
25
|
+
/**
|
|
26
|
+
* The **outline**: the names of the declarations this elision collapses, in source
|
|
27
|
+
* order — `['parseConfig', 'normalisePath']` — when the planner can read them off a
|
|
28
|
+
* parse tree. Absent, never empty, when it cannot (a lexical planner sees lines, not
|
|
29
|
+
* declarations; a run of comments has nothing to name).
|
|
30
|
+
*
|
|
31
|
+
* Out of band by design. The names ride on the plan, the applied elision and the
|
|
32
|
+
* report — never in the marker, whose bytes and priced cost do not move by one byte
|
|
33
|
+
* (`test/guards/marker-format.test.ts` pins that). It exists because the planner
|
|
34
|
+
* held the cheapest possible index of what it hid and threw it away at explanation
|
|
35
|
+
* time, leaving a model on a whole-file task to retrieve hash by hash just to learn
|
|
36
|
+
* what was behind each marker.
|
|
37
|
+
*/
|
|
38
|
+
readonly names?: readonly string[];
|
|
25
39
|
}
|
|
26
40
|
/**
|
|
27
41
|
* The complete output of a planner: the whole decision, before anything is mutated.
|
|
@@ -77,6 +91,18 @@ export interface PlanInput {
|
|
|
77
91
|
* `markerPricing()` in `apply.ts` from the exact builder `applyPlan` will use.
|
|
78
92
|
*/
|
|
79
93
|
readonly pricing: MarkerPricing;
|
|
94
|
+
/**
|
|
95
|
+
* The store's per-rule ledger — how many cuts each rule has made in this store and
|
|
96
|
+
* how many of them were asked for back — when the store can supply one. Filled
|
|
97
|
+
* centrally by `createSmelter`, like {@link MarkerPricing}; never guessed.
|
|
98
|
+
*
|
|
99
|
+
* **Opt-in data, not a lever.** The shipped planners do not read it: smelt measures
|
|
100
|
+
* the expansion rate and never thresholds it (`docs/ARCHITECTURE.md` § Decision 4),
|
|
101
|
+
* so a rule that "does not pay" is a fact a *caller's* planner may weigh, and never
|
|
102
|
+
* a warning smelt authors. This is the deterministic form of "revert a cut that got
|
|
103
|
+
* asked back": the loop is closed as data a planner can read, in one place.
|
|
104
|
+
*/
|
|
105
|
+
readonly ruleHistory?: readonly RuleLedgerEntry[];
|
|
80
106
|
}
|
|
81
107
|
/**
|
|
82
108
|
* A planner decides *what* to remove. It never removes anything itself; `applyPlan`
|
|
@@ -105,6 +131,8 @@ export interface AppliedElision {
|
|
|
105
131
|
readonly reason: ElisionReason;
|
|
106
132
|
/** The exact marker text substituted into the output. */
|
|
107
133
|
readonly marker: string;
|
|
134
|
+
/** The planner's outline, carried verbatim from {@link PlannedElision.names}. */
|
|
135
|
+
readonly names?: readonly string[];
|
|
108
136
|
}
|
|
109
137
|
/**
|
|
110
138
|
* A consumer-supplied counter, so a caller who bills in tokens can *see* tokens.
|
|
@@ -205,13 +233,37 @@ export interface RetrieveStats {
|
|
|
205
233
|
*/
|
|
206
234
|
readonly allElisionsRetrieved: boolean;
|
|
207
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* One row of a store's **ledger**: a rule, the distinct hashes it put, and how many
|
|
238
|
+
* of those were retrieved at least once. Rows are sorted by rule, so two reads of one
|
|
239
|
+
* store — or of one directory from two processes — render identically.
|
|
240
|
+
*
|
|
241
|
+
* `retrieved === stored` for a rule is the per-rule form of `allElisionsRetrieved`:
|
|
242
|
+
* every cut that rule made was asked for back, an arithmetic fact and never a
|
|
243
|
+
* threshold. What to do about it is the caller's call.
|
|
244
|
+
*/
|
|
245
|
+
export interface RuleLedgerEntry {
|
|
246
|
+
/** The {@link ElisionReason.rule} id, e.g. `'sibling-collapse'`. */
|
|
247
|
+
readonly rule: string;
|
|
248
|
+
/** Distinct hashes put under this rule. */
|
|
249
|
+
readonly stored: number;
|
|
250
|
+
/** Of those, distinct hashes retrieved at least once. */
|
|
251
|
+
readonly retrieved: number;
|
|
252
|
+
}
|
|
208
253
|
/**
|
|
209
254
|
* Local, content-addressed storage for elided bytes. No network, no eviction in v1 —
|
|
210
255
|
* evicting is how "reversible" quietly becomes "reversible for a while".
|
|
211
256
|
*/
|
|
212
257
|
export interface ElisionStore {
|
|
213
|
-
/**
|
|
214
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Store content, returning its hash. Idempotent for identical content.
|
|
260
|
+
*
|
|
261
|
+
* `reason` is the rule the content was cut by, when the caller is the applier — it
|
|
262
|
+
* feeds the store's {@link ledger}. Optional, so a store written before ledgers and
|
|
263
|
+
* a caller storing bytes for its own reasons both keep working; a put with no
|
|
264
|
+
* reason is stored and never attributed.
|
|
265
|
+
*/
|
|
266
|
+
put(content: string, reason?: ElisionReason): string;
|
|
215
267
|
/** The stored content, or `undefined` if this store never held that hash. */
|
|
216
268
|
peek(hash: string): string | undefined;
|
|
217
269
|
/**
|
|
@@ -241,6 +293,13 @@ export interface ElisionStore {
|
|
|
241
293
|
has(hash: string): boolean;
|
|
242
294
|
/** A snapshot of the counters. See {@link RetrieveStats}. */
|
|
243
295
|
stats(): RetrieveStats;
|
|
296
|
+
/**
|
|
297
|
+
* The per-rule ledger, when this store keeps one — both shipped stores do. Optional
|
|
298
|
+
* so a custom store need not; a consumer that wants the feedback loop implements it
|
|
299
|
+
* with the shared `ruleLedger()` derivation from `stats.ts`. Uncounted, like
|
|
300
|
+
* `stats()`: reading the ledger never moves it.
|
|
301
|
+
*/
|
|
302
|
+
ledger?(): readonly RuleLedgerEntry[];
|
|
244
303
|
}
|
|
245
304
|
/**
|
|
246
305
|
* The retrieval tool a consumer exposes to its model. Deliberately not an MCP or
|
|
@@ -280,6 +339,61 @@ export interface RetrieveTool {
|
|
|
280
339
|
readonly hash: string;
|
|
281
340
|
}): string;
|
|
282
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* One answer inside a batched retrieval: the exact bytes for a hash, or the store's
|
|
344
|
+
* own refusal for it. A batch never fails as a whole — a model that asked for
|
|
345
|
+
* eighteen blobs and typo'd one must still get the seventeen, and the one refusal
|
|
346
|
+
* must still be the store's distinct error (`UnknownHashError` vs
|
|
347
|
+
* `StoreCorruptionError`), never an empty string standing in for either.
|
|
348
|
+
*/
|
|
349
|
+
export type RetrievedBlock = {
|
|
350
|
+
readonly hash: string;
|
|
351
|
+
readonly text: string;
|
|
352
|
+
} | {
|
|
353
|
+
readonly hash: string;
|
|
354
|
+
readonly error: Error;
|
|
355
|
+
};
|
|
356
|
+
/**
|
|
357
|
+
* The batched sibling of {@link RetrieveTool}: N hashes in, one {@link RetrievedBlock}
|
|
358
|
+
* per hash out, in the order asked. Additive — `smelt_retrieve` is the frozen wire
|
|
359
|
+
* surface and stays byte-identical beside this.
|
|
360
|
+
*
|
|
361
|
+
* Why it exists is a measured fact, not a convenience: every tool call is a new
|
|
362
|
+
* request, and input tokens are billed per request, so a model expanding eighteen
|
|
363
|
+
* markers one call at a time re-bills its whole transcript eighteen times. One
|
|
364
|
+
* request for eighteen blocks changes what that costs without changing what the
|
|
365
|
+
* expansion rate *means* — each hit inside the batch is journalled exactly as a
|
|
366
|
+
* single call would journal it.
|
|
367
|
+
*/
|
|
368
|
+
export interface RetrieveBatchTool {
|
|
369
|
+
/** `'smelt_retrieve_batch'`. Stable — consumers hard-code it in prompts. */
|
|
370
|
+
readonly name: string;
|
|
371
|
+
/** Prose the consumer can put straight into a tool description. */
|
|
372
|
+
readonly description: string;
|
|
373
|
+
/** Strict-mode shaped, like {@link RetrieveTool.inputSchema}. */
|
|
374
|
+
readonly inputSchema: {
|
|
375
|
+
readonly type: 'object';
|
|
376
|
+
readonly properties: {
|
|
377
|
+
readonly hashes: {
|
|
378
|
+
readonly type: 'array';
|
|
379
|
+
readonly items: {
|
|
380
|
+
readonly type: 'string';
|
|
381
|
+
};
|
|
382
|
+
readonly description: string;
|
|
383
|
+
};
|
|
384
|
+
};
|
|
385
|
+
readonly required: readonly ['hashes'];
|
|
386
|
+
readonly additionalProperties: false;
|
|
387
|
+
};
|
|
388
|
+
/**
|
|
389
|
+
* One block per hash, in order. Never throws for a hash the store refuses — that
|
|
390
|
+
* refusal rides inside its block — but anything that is not the store's own
|
|
391
|
+
* refusal (an I/O failure, a bug) still propagates.
|
|
392
|
+
*/
|
|
393
|
+
invoke(input: {
|
|
394
|
+
readonly hashes: readonly string[];
|
|
395
|
+
}): readonly RetrievedBlock[];
|
|
396
|
+
}
|
|
283
397
|
/** A candidate handed to a {@link RerankStage}: an opaque id plus the text to judge. */
|
|
284
398
|
export interface RerankCandidate {
|
|
285
399
|
readonly id: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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","sourcesContent":["/** Languages smelt can parse structurally. Everything else falls back to the lexical planner. */\nexport type LanguageId =\n | 'typescript'\n | 'tsx'\n | 'javascript'\n | 'rust'\n | 'python'\n | 'go'\n | 'java'\n | 'c'\n | 'cpp'\n | 'c_sharp'\n | 'ruby'\n | 'php'\n | 'kotlin'\n | 'swift'\n | 'bash';\n\n/** `'unknown'` is a first-class outcome, not a failure: it selects the lexical planner. */\nexport type DetectedLanguage = LanguageId | 'unknown';\n\n/** A half-open byte range `[start, end)` into the UTF-8 bytes of the input. */\nexport interface ByteRange {\n readonly start: number;\n readonly end: number;\n}\n\n/**\n * Why a range was elided, in two registers: a stable id for counters, and a sentence\n * a human can read in a diff. Law 2 — every elision is explainable — lives here:\n * if you cannot write the sentence, the rule does not ship.\n */\nexport interface ElisionReason {\n /** Stable machine id, e.g. `'sibling-collapse'`, `'line-window'`. */\n readonly rule: string;\n /** e.g. `'collapsed 3 sibling functions'`. Present tense, no trailing period. */\n readonly explanation: string;\n}\n\n/** One range a planner proposes to remove. Plans are pure data — inspectable and testable. */\nexport interface PlannedElision {\n readonly range: ByteRange;\n readonly reason: ElisionReason;\n}\n\n/**\n * The complete output of a planner: the whole decision, before anything is mutated.\n * A plan can be logged, diffed, snapshot-tested, and rejected without touching the text.\n */\nexport interface ElisionPlan {\n readonly planner: string;\n readonly language: DetectedLanguage;\n readonly elisions: readonly PlannedElision[];\n}\n\n/**\n * The seam through which a planner asks what a marker will cost, in UTF-8 bytes.\n *\n * Marker cost is `apply.ts`'s fact — the applier renders the marker, so only the\n * applier knows its price. Planners need that price for two decisions (profitability:\n * a marker that costs more than it removes grows the output; and budget prediction:\n * which ladder rung actually fits), and before this seam each planner rebuilt the\n * marker machinery privately to measure it — correct, but an inversion. Now\n * `markerPricing()` in `apply.ts` builds the one adapter from the exact builder\n * `applyPlan` will use, and planners ask it. They never guess, and they never render\n * a marker of their own.\n *\n * The price is exact, not an estimate: the cost of the marker this elision would\n * earn, comment leader and all, with a stand-in hash of the real hash's length —\n * marker cost depends on the hash's *length*, never its value.\n */\nexport interface MarkerPricing {\n /** The exact UTF-8 byte cost of the marker an elision with this reason and size would earn. */\n costBytes(reason: ElisionReason, elidedBytes: number): number;\n}\n\n/**\n * What the caller hands a planner.\n *\n * Constructed centrally: `createSmelter` (and through it, the CLI) builds the one\n * `PlanInput` per call, including its {@link MarkerPricing} — a caller invoking\n * `planLexical`/`planStructural` directly builds `pricing` with `markerPricing()`\n * from `apply.ts`. A JS caller who omits it gets {@link MissingMarkerPricingError}\n * at plan time, not a guessed cost.\n */\nexport interface PlanInput {\n readonly text: string;\n readonly language: DetectedLanguage;\n /** Soft ceiling for the emitted output, in UTF-8 bytes. Planners aim under it. */\n readonly budgetBytes: number;\n /**\n * What the caller was actually looking for — grep pattern, symbol name, error string.\n * Planners keep matching regions and collapse around them.\n */\n readonly focus?: readonly string[];\n /**\n * What a marker costs. Required: a planner that guesses marker cost can plan an\n * elision that grows the output. See {@link MarkerPricing}; built by\n * `markerPricing()` in `apply.ts` from the exact builder `applyPlan` will use.\n */\n readonly pricing: MarkerPricing;\n}\n\n/**\n * A planner decides *what* to remove. It never removes anything itself; `applyPlan`\n * does that. Keeping the decision and the mutation apart is what makes the decision\n * testable in isolation.\n */\nexport interface Planner {\n readonly id: string;\n plan(input: PlanInput): Promise<ElisionPlan>;\n}\n\n/** One elision that actually happened, with the receipt needed to undo it. */\nexport interface AppliedElision {\n /** Content hash of the removed bytes — the key `retrieve()` takes. */\n readonly hash: string;\n /** Where the removed bytes were in the *input*. */\n readonly range: ByteRange;\n /**\n * Where the marker sits in the *output*. Law 3 — every elision is reversible — needs\n * this: {@link Reconstructor} splices stored bytes back over these ranges. Without it,\n * \"reversible\" would mean parsing markers back out of the text, which is a guess.\n * This is a fact recorded at the moment of the cut.\n */\n readonly outputRange: ByteRange;\n /** Size of the removed content, in UTF-8 bytes. */\n readonly bytes: number;\n readonly reason: ElisionReason;\n /** The exact marker text substituted into the output. */\n readonly marker: string;\n}\n\n/**\n * A consumer-supplied counter, so a caller who bills in tokens can *see* tokens.\n *\n * Budgets in smelt's core are UTF-8 bytes, permanently — bytes are the only unit that\n * is computable locally for every model, and they mean the same thing in five years.\n * See `docs/ARCHITECTURE.md` § \"Decision 1\". This hook does not change that: it adds a\n * second, labelled number to the result. The plan is still made in bytes.\n *\n * Both `id` and `unit` are required, and that is a Law 4 decision rather than\n * bookkeeping: a token count is meaningless without naming the tokenizer that produced\n * it. Anthropic's own docs record that Claude 4.7 and later use a newer tokenizer where\n * the same text yields roughly 30% more tokens than on earlier models — so `1,204\n * tokens` is not a fact, and `1,204 tokens (claude-4.7/count_tokens)` is.\n *\n * **This hook does not relax Law 1.** smelt imports no transport and the guard proves\n * that about smelt's own modules; it cannot prove it about a function you hand in. A\n * `count()` that calls an API makes *your* process call an API, from a line in *your*\n * source — exactly the arrangement {@link RerankStage} already describes. `count` is\n * synchronous on purpose: local tokenizers are synchronous, and network clients are not.\n */\nexport interface Measure {\n /** Names the counter, e.g. `'tiktoken/o200k_base'` or `'claude-4.7/count_tokens'`. */\n readonly id: string;\n /** The unit `count()` returns, e.g. `'tokens'`. Printed next to the number. */\n readonly unit: string;\n /** Local, synchronous count over the whole string. */\n count(text: string): number;\n}\n\n/** A second size for a result, in someone else's unit, with the counter named. */\nexport interface MeasuredSize {\n /** {@link Measure.id} of the counter that produced these numbers. */\n readonly measure: string;\n /** {@link Measure.unit}. */\n readonly unit: string;\n readonly input: number;\n readonly output: number;\n}\n\n/** The result of smelting one blob of text. */\nexport interface SmeltResult {\n readonly text: string;\n readonly inputBytes: number;\n readonly outputBytes: number;\n readonly planner: string;\n readonly language: DetectedLanguage;\n readonly elisions: readonly AppliedElision[];\n /** Present only when the caller supplied a {@link Measure}. Never invented. */\n readonly measured?: MeasuredSize;\n}\n\n/**\n * Reversibility, as a callable. Takes a {@link SmeltResult} and the store that holds its\n * elided bytes, and returns the original text — byte for byte.\n */\nexport type Reconstructor = (result: SmeltResult, store: ElisionStore) => string;\n\n// ---------------------------------------------------------------------------\n// The store, and the counters that make over-pruning visible\n// ---------------------------------------------------------------------------\n\n/**\n * The numbers that keep smelt honest about itself.\n *\n * Law 3 says elisions are reversible. That is cheap to satisfy and easy to abuse: a\n * compressor that cuts everything is \"reversible\" and useless. The *retrieve rate* is\n * the tell. If the model keeps calling `smelt_retrieve`, smelt cut material the task\n * needed, and the round trip cost more tokens than the elision saved.\n *\n * So: `expansionRate` is not telemetry. It is the metric a caller tunes budgets\n * against, and the only number smelt is willing to have an opinion about — because it\n * measures it locally, per session, on the caller's own traffic.\n */\nexport interface RetrieveStats {\n /** Distinct blobs put into the store. */\n readonly elisionsStored: number;\n /** Total bytes held by the store. */\n readonly bytesStored: number;\n /** Every `retrieve()` call, including repeats and misses. */\n readonly retrieveCalls: number;\n /** Distinct hashes successfully retrieved at least once. */\n readonly uniqueRetrieved: number;\n /** Calls for a hash the store does not hold. Non-zero means a bug, not over-pruning. */\n readonly misses: number;\n /**\n * `uniqueRetrieved / elisionsStored`, or `0` when nothing has been stored.\n *\n * Read it as: *what fraction of what smelt hid did the model have to ask for back?*\n * There is no universally right value, and smelt does not ship a threshold it has\n * not measured. Rising across a workload is the signal.\n */\n readonly expansionRate: number;\n /**\n * The one degenerate outcome smelt is willing to name: **every distinct blob it hid\n * was asked for again.**\n *\n * smelt ships no expansion-rate threshold, because a threshold is a policy claim it\n * has no basis for and the right rate depends on how aggressive a budget the caller\n * chose — and a library printing warnings into someone else's process is bad manners.\n * This is not a threshold. At `uniqueRetrieved === elisionsStored` the elision\n * achieved nothing and cost a round trip: an arithmetic fact, not a preference. What\n * to do about it is the caller's call.\n *\n * `false` for an empty store — nothing was hidden, so nothing was defeated.\n */\n readonly allElisionsRetrieved: boolean;\n}\n\n/**\n * Local, content-addressed storage for elided bytes. No network, no eviction in v1 —\n * evicting is how \"reversible\" quietly becomes \"reversible for a while\".\n */\nexport interface ElisionStore {\n /** Store content, returning its hash. Idempotent for identical content. */\n put(content: string): string;\n /** The stored content, or `undefined` if this store never held that hash. */\n peek(hash: string): string | undefined;\n /**\n * The stored content, *counted* as a retrieval. This is what the model's tool calls.\n *\n * @throws {UnknownHashError} when the hash is unknown.\n * @throws {StoreCorruptionError} — {@link DirectoryElisionStore} only — when the\n * bytes on disk no longer hash to their own name. Distinct from\n * `UnknownHashError` on purpose: \"we hold damaged bytes\" and \"it never existed\"\n * are different answers, and a caller that conflates them will report the wrong\n * one to its user.\n *\n * A {@link DirectoryElisionStore} whose journal cannot be written (a read-only\n * store directory, a full disk) still returns the bytes — verified bytes are never\n * withheld over a bookkeeping failure — and surfaces the lost count as a\n * `process.emitWarning` named `SmeltCounterWriteFailure` instead of throwing. Its\n * stats go quiet from that point; the retrieval itself succeeded.\n */\n retrieve(hash: string): string;\n /**\n * Whether this hash can be retrieved — **not** merely whether a slot bearing it\n * exists. `true` must mean the very next `retrieve(hash)` returns bytes, so a store\n * that verifies content on read verifies here too and raises its corruption error\n * rather than answering `true` for bytes it would then refuse. Uncounted: a check is\n * not the model asking for material back.\n */\n has(hash: string): boolean;\n /** A snapshot of the counters. See {@link RetrieveStats}. */\n stats(): RetrieveStats;\n}\n\n/**\n * The retrieval tool a consumer exposes to its model. Deliberately not an MCP or\n * provider-specific shape — smelt does not know which SDK you use. The consumer adapts\n * this into its own tool schema; the contract is `hash in, exact bytes out`.\n */\nexport interface RetrieveTool {\n /** `'smelt_retrieve'`. Stable — consumers hard-code it in prompts. */\n readonly name: string;\n /** Prose the consumer can put straight into a tool description. */\n readonly description: string;\n /**\n * JSON-Schema-shaped parameter description, for consumers that want one.\n *\n * Strict-mode shaped: `additionalProperties: false` and a `required` naming every\n * property, so a consumer registering this under OpenAI's structured-outputs strict\n * mode is not refused at registration. See {@link createRetrieveTool}.\n */\n readonly inputSchema: {\n readonly type: 'object';\n readonly properties: {\n readonly hash: { readonly type: 'string'; readonly description: string };\n };\n readonly required: readonly ['hash'];\n readonly additionalProperties: false;\n };\n /**\n * @throws {UnknownHashError} when the hash is unknown.\n * @throws {StoreCorruptionError} when the backing store holds damaged bytes for\n * that hash — see {@link ElisionStore.retrieve}, whose contract this forwards\n * verbatim. Surface either to the model as a tool error, never as empty text.\n */\n invoke(input: { readonly hash: string }): string;\n}\n\n// ---------------------------------------------------------------------------\n// Pluggable stages — interfaces in v1, nothing more\n// ---------------------------------------------------------------------------\n\n/** A candidate handed to a {@link RerankStage}: an opaque id plus the text to judge. */\nexport interface RerankCandidate {\n readonly id: string;\n readonly text: string;\n}\n\n/** A reranked candidate, most relevant first. `score` is the stage's own scale. */\nexport interface RerankedCandidate extends RerankCandidate {\n readonly score: number;\n}\n\n/**\n * Relevance reranking — a *seam*, not a feature.\n *\n * Hosted rerankers are good and smelt will never bundle one, because bundling would\n * break Law 1: the moment smelt ships a default reranker, `smelt()` can make a network\n * call that the caller did not ask for and cannot see. A consumer that wants one\n * implements this interface, wires its own key, and owns the fact that its context now\n * leaves the machine. That decision must be legible in the consumer's own source.\n */\nexport interface RerankStage {\n readonly id: string;\n /** May make network calls — that is the consumer's choice, made in the consumer's code. */\n rerank(\n candidates: readonly RerankCandidate[],\n query: string,\n ): Promise<readonly RerankedCandidate[]>;\n}\n\n/**\n * Learned distillation — rewriting content with a model instead of cutting it.\n *\n * Out of v1 for a reason beyond the network: a distilled paragraph cannot satisfy\n * Law 2. \"The model summarised this\" is not an explanation of what was removed, and\n * the removed material is no longer recoverable from the output. If this ever ships,\n * it ships as a stage that stores the original and explains itself in the same terms\n * every other rule does.\n */\nexport interface DistillStage {\n readonly id: string;\n distill(text: string, budgetBytes: number): Promise<string>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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","sourcesContent":["/** Languages smelt can parse structurally. Everything else falls back to the lexical planner. */\nexport type LanguageId =\n | 'typescript'\n | 'tsx'\n | 'javascript'\n | 'rust'\n | 'python'\n | 'go'\n | 'java'\n | 'c'\n | 'cpp'\n | 'c_sharp'\n | 'ruby'\n | 'php'\n | 'kotlin'\n | 'swift'\n | 'bash';\n\n/** `'unknown'` is a first-class outcome, not a failure: it selects the lexical planner. */\nexport type DetectedLanguage = LanguageId | 'unknown';\n\n/** A half-open byte range `[start, end)` into the UTF-8 bytes of the input. */\nexport interface ByteRange {\n readonly start: number;\n readonly end: number;\n}\n\n/**\n * Why a range was elided, in two registers: a stable id for counters, and a sentence\n * a human can read in a diff. Law 2 — every elision is explainable — lives here:\n * if you cannot write the sentence, the rule does not ship.\n */\nexport interface ElisionReason {\n /** Stable machine id, e.g. `'sibling-collapse'`, `'line-window'`. */\n readonly rule: string;\n /** e.g. `'collapsed 3 sibling functions'`. Present tense, no trailing period. */\n readonly explanation: string;\n}\n\n/** One range a planner proposes to remove. Plans are pure data — inspectable and testable. */\nexport interface PlannedElision {\n readonly range: ByteRange;\n readonly reason: ElisionReason;\n /**\n * The **outline**: the names of the declarations this elision collapses, in source\n * order — `['parseConfig', 'normalisePath']` — when the planner can read them off a\n * parse tree. Absent, never empty, when it cannot (a lexical planner sees lines, not\n * declarations; a run of comments has nothing to name).\n *\n * Out of band by design. The names ride on the plan, the applied elision and the\n * report — never in the marker, whose bytes and priced cost do not move by one byte\n * (`test/guards/marker-format.test.ts` pins that). It exists because the planner\n * held the cheapest possible index of what it hid and threw it away at explanation\n * time, leaving a model on a whole-file task to retrieve hash by hash just to learn\n * what was behind each marker.\n */\n readonly names?: readonly string[];\n}\n\n/**\n * The complete output of a planner: the whole decision, before anything is mutated.\n * A plan can be logged, diffed, snapshot-tested, and rejected without touching the text.\n */\nexport interface ElisionPlan {\n readonly planner: string;\n readonly language: DetectedLanguage;\n readonly elisions: readonly PlannedElision[];\n}\n\n/**\n * The seam through which a planner asks what a marker will cost, in UTF-8 bytes.\n *\n * Marker cost is `apply.ts`'s fact — the applier renders the marker, so only the\n * applier knows its price. Planners need that price for two decisions (profitability:\n * a marker that costs more than it removes grows the output; and budget prediction:\n * which ladder rung actually fits), and before this seam each planner rebuilt the\n * marker machinery privately to measure it — correct, but an inversion. Now\n * `markerPricing()` in `apply.ts` builds the one adapter from the exact builder\n * `applyPlan` will use, and planners ask it. They never guess, and they never render\n * a marker of their own.\n *\n * The price is exact, not an estimate: the cost of the marker this elision would\n * earn, comment leader and all, with a stand-in hash of the real hash's length —\n * marker cost depends on the hash's *length*, never its value.\n */\nexport interface MarkerPricing {\n /** The exact UTF-8 byte cost of the marker an elision with this reason and size would earn. */\n costBytes(reason: ElisionReason, elidedBytes: number): number;\n}\n\n/**\n * What the caller hands a planner.\n *\n * Constructed centrally: `createSmelter` (and through it, the CLI) builds the one\n * `PlanInput` per call, including its {@link MarkerPricing} — a caller invoking\n * `planLexical`/`planStructural` directly builds `pricing` with `markerPricing()`\n * from `apply.ts`. A JS caller who omits it gets {@link MissingMarkerPricingError}\n * at plan time, not a guessed cost.\n */\nexport interface PlanInput {\n readonly text: string;\n readonly language: DetectedLanguage;\n /** Soft ceiling for the emitted output, in UTF-8 bytes. Planners aim under it. */\n readonly budgetBytes: number;\n /**\n * What the caller was actually looking for — grep pattern, symbol name, error string.\n * Planners keep matching regions and collapse around them.\n */\n readonly focus?: readonly string[];\n /**\n * What a marker costs. Required: a planner that guesses marker cost can plan an\n * elision that grows the output. See {@link MarkerPricing}; built by\n * `markerPricing()` in `apply.ts` from the exact builder `applyPlan` will use.\n */\n readonly pricing: MarkerPricing;\n /**\n * The store's per-rule ledger — how many cuts each rule has made in this store and\n * how many of them were asked for back — when the store can supply one. Filled\n * centrally by `createSmelter`, like {@link MarkerPricing}; never guessed.\n *\n * **Opt-in data, not a lever.** The shipped planners do not read it: smelt measures\n * the expansion rate and never thresholds it (`docs/ARCHITECTURE.md` § Decision 4),\n * so a rule that \"does not pay\" is a fact a *caller's* planner may weigh, and never\n * a warning smelt authors. This is the deterministic form of \"revert a cut that got\n * asked back\": the loop is closed as data a planner can read, in one place.\n */\n readonly ruleHistory?: readonly RuleLedgerEntry[];\n}\n\n/**\n * A planner decides *what* to remove. It never removes anything itself; `applyPlan`\n * does that. Keeping the decision and the mutation apart is what makes the decision\n * testable in isolation.\n */\nexport interface Planner {\n readonly id: string;\n plan(input: PlanInput): Promise<ElisionPlan>;\n}\n\n/** One elision that actually happened, with the receipt needed to undo it. */\nexport interface AppliedElision {\n /** Content hash of the removed bytes — the key `retrieve()` takes. */\n readonly hash: string;\n /** Where the removed bytes were in the *input*. */\n readonly range: ByteRange;\n /**\n * Where the marker sits in the *output*. Law 3 — every elision is reversible — needs\n * this: {@link Reconstructor} splices stored bytes back over these ranges. Without it,\n * \"reversible\" would mean parsing markers back out of the text, which is a guess.\n * This is a fact recorded at the moment of the cut.\n */\n readonly outputRange: ByteRange;\n /** Size of the removed content, in UTF-8 bytes. */\n readonly bytes: number;\n readonly reason: ElisionReason;\n /** The exact marker text substituted into the output. */\n readonly marker: string;\n /** The planner's outline, carried verbatim from {@link PlannedElision.names}. */\n readonly names?: readonly string[];\n}\n\n/**\n * A consumer-supplied counter, so a caller who bills in tokens can *see* tokens.\n *\n * Budgets in smelt's core are UTF-8 bytes, permanently — bytes are the only unit that\n * is computable locally for every model, and they mean the same thing in five years.\n * See `docs/ARCHITECTURE.md` § \"Decision 1\". This hook does not change that: it adds a\n * second, labelled number to the result. The plan is still made in bytes.\n *\n * Both `id` and `unit` are required, and that is a Law 4 decision rather than\n * bookkeeping: a token count is meaningless without naming the tokenizer that produced\n * it. Anthropic's own docs record that Claude 4.7 and later use a newer tokenizer where\n * the same text yields roughly 30% more tokens than on earlier models — so `1,204\n * tokens` is not a fact, and `1,204 tokens (claude-4.7/count_tokens)` is.\n *\n * **This hook does not relax Law 1.** smelt imports no transport and the guard proves\n * that about smelt's own modules; it cannot prove it about a function you hand in. A\n * `count()` that calls an API makes *your* process call an API, from a line in *your*\n * source — exactly the arrangement {@link RerankStage} already describes. `count` is\n * synchronous on purpose: local tokenizers are synchronous, and network clients are not.\n */\nexport interface Measure {\n /** Names the counter, e.g. `'tiktoken/o200k_base'` or `'claude-4.7/count_tokens'`. */\n readonly id: string;\n /** The unit `count()` returns, e.g. `'tokens'`. Printed next to the number. */\n readonly unit: string;\n /** Local, synchronous count over the whole string. */\n count(text: string): number;\n}\n\n/** A second size for a result, in someone else's unit, with the counter named. */\nexport interface MeasuredSize {\n /** {@link Measure.id} of the counter that produced these numbers. */\n readonly measure: string;\n /** {@link Measure.unit}. */\n readonly unit: string;\n readonly input: number;\n readonly output: number;\n}\n\n/** The result of smelting one blob of text. */\nexport interface SmeltResult {\n readonly text: string;\n readonly inputBytes: number;\n readonly outputBytes: number;\n readonly planner: string;\n readonly language: DetectedLanguage;\n readonly elisions: readonly AppliedElision[];\n /** Present only when the caller supplied a {@link Measure}. Never invented. */\n readonly measured?: MeasuredSize;\n}\n\n/**\n * Reversibility, as a callable. Takes a {@link SmeltResult} and the store that holds its\n * elided bytes, and returns the original text — byte for byte.\n */\nexport type Reconstructor = (result: SmeltResult, store: ElisionStore) => string;\n\n// ---------------------------------------------------------------------------\n// The store, and the counters that make over-pruning visible\n// ---------------------------------------------------------------------------\n\n/**\n * The numbers that keep smelt honest about itself.\n *\n * Law 3 says elisions are reversible. That is cheap to satisfy and easy to abuse: a\n * compressor that cuts everything is \"reversible\" and useless. The *retrieve rate* is\n * the tell. If the model keeps calling `smelt_retrieve`, smelt cut material the task\n * needed, and the round trip cost more tokens than the elision saved.\n *\n * So: `expansionRate` is not telemetry. It is the metric a caller tunes budgets\n * against, and the only number smelt is willing to have an opinion about — because it\n * measures it locally, per session, on the caller's own traffic.\n */\nexport interface RetrieveStats {\n /** Distinct blobs put into the store. */\n readonly elisionsStored: number;\n /** Total bytes held by the store. */\n readonly bytesStored: number;\n /** Every `retrieve()` call, including repeats and misses. */\n readonly retrieveCalls: number;\n /** Distinct hashes successfully retrieved at least once. */\n readonly uniqueRetrieved: number;\n /** Calls for a hash the store does not hold. Non-zero means a bug, not over-pruning. */\n readonly misses: number;\n /**\n * `uniqueRetrieved / elisionsStored`, or `0` when nothing has been stored.\n *\n * Read it as: *what fraction of what smelt hid did the model have to ask for back?*\n * There is no universally right value, and smelt does not ship a threshold it has\n * not measured. Rising across a workload is the signal.\n */\n readonly expansionRate: number;\n /**\n * The one degenerate outcome smelt is willing to name: **every distinct blob it hid\n * was asked for again.**\n *\n * smelt ships no expansion-rate threshold, because a threshold is a policy claim it\n * has no basis for and the right rate depends on how aggressive a budget the caller\n * chose — and a library printing warnings into someone else's process is bad manners.\n * This is not a threshold. At `uniqueRetrieved === elisionsStored` the elision\n * achieved nothing and cost a round trip: an arithmetic fact, not a preference. What\n * to do about it is the caller's call.\n *\n * `false` for an empty store — nothing was hidden, so nothing was defeated.\n */\n readonly allElisionsRetrieved: boolean;\n}\n\n/**\n * One row of a store's **ledger**: a rule, the distinct hashes it put, and how many\n * of those were retrieved at least once. Rows are sorted by rule, so two reads of one\n * store — or of one directory from two processes — render identically.\n *\n * `retrieved === stored` for a rule is the per-rule form of `allElisionsRetrieved`:\n * every cut that rule made was asked for back, an arithmetic fact and never a\n * threshold. What to do about it is the caller's call.\n */\nexport interface RuleLedgerEntry {\n /** The {@link ElisionReason.rule} id, e.g. `'sibling-collapse'`. */\n readonly rule: string;\n /** Distinct hashes put under this rule. */\n readonly stored: number;\n /** Of those, distinct hashes retrieved at least once. */\n readonly retrieved: number;\n}\n\n/**\n * Local, content-addressed storage for elided bytes. No network, no eviction in v1 —\n * evicting is how \"reversible\" quietly becomes \"reversible for a while\".\n */\nexport interface ElisionStore {\n /**\n * Store content, returning its hash. Idempotent for identical content.\n *\n * `reason` is the rule the content was cut by, when the caller is the applier — it\n * feeds the store's {@link ledger}. Optional, so a store written before ledgers and\n * a caller storing bytes for its own reasons both keep working; a put with no\n * reason is stored and never attributed.\n */\n put(content: string, reason?: ElisionReason): string;\n /** The stored content, or `undefined` if this store never held that hash. */\n peek(hash: string): string | undefined;\n /**\n * The stored content, *counted* as a retrieval. This is what the model's tool calls.\n *\n * @throws {UnknownHashError} when the hash is unknown.\n * @throws {StoreCorruptionError} — {@link DirectoryElisionStore} only — when the\n * bytes on disk no longer hash to their own name. Distinct from\n * `UnknownHashError` on purpose: \"we hold damaged bytes\" and \"it never existed\"\n * are different answers, and a caller that conflates them will report the wrong\n * one to its user.\n *\n * A {@link DirectoryElisionStore} whose journal cannot be written (a read-only\n * store directory, a full disk) still returns the bytes — verified bytes are never\n * withheld over a bookkeeping failure — and surfaces the lost count as a\n * `process.emitWarning` named `SmeltCounterWriteFailure` instead of throwing. Its\n * stats go quiet from that point; the retrieval itself succeeded.\n */\n retrieve(hash: string): string;\n /**\n * Whether this hash can be retrieved — **not** merely whether a slot bearing it\n * exists. `true` must mean the very next `retrieve(hash)` returns bytes, so a store\n * that verifies content on read verifies here too and raises its corruption error\n * rather than answering `true` for bytes it would then refuse. Uncounted: a check is\n * not the model asking for material back.\n */\n has(hash: string): boolean;\n /** A snapshot of the counters. See {@link RetrieveStats}. */\n stats(): RetrieveStats;\n /**\n * The per-rule ledger, when this store keeps one — both shipped stores do. Optional\n * so a custom store need not; a consumer that wants the feedback loop implements it\n * with the shared `ruleLedger()` derivation from `stats.ts`. Uncounted, like\n * `stats()`: reading the ledger never moves it.\n */\n ledger?(): readonly RuleLedgerEntry[];\n}\n\n/**\n * The retrieval tool a consumer exposes to its model. Deliberately not an MCP or\n * provider-specific shape — smelt does not know which SDK you use. The consumer adapts\n * this into its own tool schema; the contract is `hash in, exact bytes out`.\n */\nexport interface RetrieveTool {\n /** `'smelt_retrieve'`. Stable — consumers hard-code it in prompts. */\n readonly name: string;\n /** Prose the consumer can put straight into a tool description. */\n readonly description: string;\n /**\n * JSON-Schema-shaped parameter description, for consumers that want one.\n *\n * Strict-mode shaped: `additionalProperties: false` and a `required` naming every\n * property, so a consumer registering this under OpenAI's structured-outputs strict\n * mode is not refused at registration. See {@link createRetrieveTool}.\n */\n readonly inputSchema: {\n readonly type: 'object';\n readonly properties: {\n readonly hash: { readonly type: 'string'; readonly description: string };\n };\n readonly required: readonly ['hash'];\n readonly additionalProperties: false;\n };\n /**\n * @throws {UnknownHashError} when the hash is unknown.\n * @throws {StoreCorruptionError} when the backing store holds damaged bytes for\n * that hash — see {@link ElisionStore.retrieve}, whose contract this forwards\n * verbatim. Surface either to the model as a tool error, never as empty text.\n */\n invoke(input: { readonly hash: string }): string;\n}\n\n/**\n * One answer inside a batched retrieval: the exact bytes for a hash, or the store's\n * own refusal for it. A batch never fails as a whole — a model that asked for\n * eighteen blobs and typo'd one must still get the seventeen, and the one refusal\n * must still be the store's distinct error (`UnknownHashError` vs\n * `StoreCorruptionError`), never an empty string standing in for either.\n */\nexport type RetrievedBlock =\n | { readonly hash: string; readonly text: string }\n | { readonly hash: string; readonly error: Error };\n\n/**\n * The batched sibling of {@link RetrieveTool}: N hashes in, one {@link RetrievedBlock}\n * per hash out, in the order asked. Additive — `smelt_retrieve` is the frozen wire\n * surface and stays byte-identical beside this.\n *\n * Why it exists is a measured fact, not a convenience: every tool call is a new\n * request, and input tokens are billed per request, so a model expanding eighteen\n * markers one call at a time re-bills its whole transcript eighteen times. One\n * request for eighteen blocks changes what that costs without changing what the\n * expansion rate *means* — each hit inside the batch is journalled exactly as a\n * single call would journal it.\n */\nexport interface RetrieveBatchTool {\n /** `'smelt_retrieve_batch'`. Stable — consumers hard-code it in prompts. */\n readonly name: string;\n /** Prose the consumer can put straight into a tool description. */\n readonly description: string;\n /** Strict-mode shaped, like {@link RetrieveTool.inputSchema}. */\n readonly inputSchema: {\n readonly type: 'object';\n readonly properties: {\n readonly hashes: {\n readonly type: 'array';\n readonly items: { readonly type: 'string' };\n readonly description: string;\n };\n };\n readonly required: readonly ['hashes'];\n readonly additionalProperties: false;\n };\n /**\n * One block per hash, in order. Never throws for a hash the store refuses — that\n * refusal rides inside its block — but anything that is not the store's own\n * refusal (an I/O failure, a bug) still propagates.\n */\n invoke(input: { readonly hashes: readonly string[] }): readonly RetrievedBlock[];\n}\n\n// ---------------------------------------------------------------------------\n// Pluggable stages — interfaces in v1, nothing more\n// ---------------------------------------------------------------------------\n\n/** A candidate handed to a {@link RerankStage}: an opaque id plus the text to judge. */\nexport interface RerankCandidate {\n readonly id: string;\n readonly text: string;\n}\n\n/** A reranked candidate, most relevant first. `score` is the stage's own scale. */\nexport interface RerankedCandidate extends RerankCandidate {\n readonly score: number;\n}\n\n/**\n * Relevance reranking — a *seam*, not a feature.\n *\n * Hosted rerankers are good and smelt will never bundle one, because bundling would\n * break Law 1: the moment smelt ships a default reranker, `smelt()` can make a network\n * call that the caller did not ask for and cannot see. A consumer that wants one\n * implements this interface, wires its own key, and owns the fact that its context now\n * leaves the machine. That decision must be legible in the consumer's own source.\n */\nexport interface RerankStage {\n readonly id: string;\n /** May make network calls — that is the consumer's choice, made in the consumer's code. */\n rerank(\n candidates: readonly RerankCandidate[],\n query: string,\n ): Promise<readonly RerankedCandidate[]>;\n}\n\n/**\n * Learned distillation — rewriting content with a model instead of cutting it.\n *\n * Out of v1 for a reason beyond the network: a distilled paragraph cannot satisfy\n * Law 2. \"The model summarised this\" is not an explanation of what was removed, and\n * the removed material is no longer recoverable from the output. If this ever ships,\n * it ships as a stage that stores the original and explains itself in the same terms\n * every other rule does.\n */\nexport interface DistillStage {\n readonly id: string;\n distill(text: string, budgetBytes: number): Promise<string>;\n}\n"]}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["/** Languages smelt can parse structurally. Everything else falls back to the lexical planner. */\nexport type LanguageId =\n | 'typescript'\n | 'tsx'\n | 'javascript'\n | 'rust'\n | 'python'\n | 'go'\n | 'java'\n | 'c'\n | 'cpp'\n | 'c_sharp'\n | 'ruby'\n | 'php'\n | 'kotlin'\n | 'swift'\n | 'bash';\n\n/** `'unknown'` is a first-class outcome, not a failure: it selects the lexical planner. */\nexport type DetectedLanguage = LanguageId | 'unknown';\n\n/** A half-open byte range `[start, end)` into the UTF-8 bytes of the input. */\nexport interface ByteRange {\n readonly start: number;\n readonly end: number;\n}\n\n/**\n * Why a range was elided, in two registers: a stable id for counters, and a sentence\n * a human can read in a diff. Law 2 — every elision is explainable — lives here:\n * if you cannot write the sentence, the rule does not ship.\n */\nexport interface ElisionReason {\n /** Stable machine id, e.g. `'sibling-collapse'`, `'line-window'`. */\n readonly rule: string;\n /** e.g. `'collapsed 3 sibling functions'`. Present tense, no trailing period. */\n readonly explanation: string;\n}\n\n/** One range a planner proposes to remove. Plans are pure data — inspectable and testable. */\nexport interface PlannedElision {\n readonly range: ByteRange;\n readonly reason: ElisionReason;\n}\n\n/**\n * The complete output of a planner: the whole decision, before anything is mutated.\n * A plan can be logged, diffed, snapshot-tested, and rejected without touching the text.\n */\nexport interface ElisionPlan {\n readonly planner: string;\n readonly language: DetectedLanguage;\n readonly elisions: readonly PlannedElision[];\n}\n\n/**\n * The seam through which a planner asks what a marker will cost, in UTF-8 bytes.\n *\n * Marker cost is `apply.ts`'s fact — the applier renders the marker, so only the\n * applier knows its price. Planners need that price for two decisions (profitability:\n * a marker that costs more than it removes grows the output; and budget prediction:\n * which ladder rung actually fits), and before this seam each planner rebuilt the\n * marker machinery privately to measure it — correct, but an inversion. Now\n * `markerPricing()` in `apply.ts` builds the one adapter from the exact builder\n * `applyPlan` will use, and planners ask it. They never guess, and they never render\n * a marker of their own.\n *\n * The price is exact, not an estimate: the cost of the marker this elision would\n * earn, comment leader and all, with a stand-in hash of the real hash's length —\n * marker cost depends on the hash's *length*, never its value.\n */\nexport interface MarkerPricing {\n /** The exact UTF-8 byte cost of the marker an elision with this reason and size would earn. */\n costBytes(reason: ElisionReason, elidedBytes: number): number;\n}\n\n/**\n * What the caller hands a planner.\n *\n * Constructed centrally: `createSmelter` (and through it, the CLI) builds the one\n * `PlanInput` per call, including its {@link MarkerPricing} — a caller invoking\n * `planLexical`/`planStructural` directly builds `pricing` with `markerPricing()`\n * from `apply.ts`. A JS caller who omits it gets {@link MissingMarkerPricingError}\n * at plan time, not a guessed cost.\n */\nexport interface PlanInput {\n readonly text: string;\n readonly language: DetectedLanguage;\n /** Soft ceiling for the emitted output, in UTF-8 bytes. Planners aim under it. */\n readonly budgetBytes: number;\n /**\n * What the caller was actually looking for — grep pattern, symbol name, error string.\n * Planners keep matching regions and collapse around them.\n */\n readonly focus?: readonly string[];\n /**\n * What a marker costs. Required: a planner that guesses marker cost can plan an\n * elision that grows the output. See {@link MarkerPricing}; built by\n * `markerPricing()` in `apply.ts` from the exact builder `applyPlan` will use.\n */\n readonly pricing: MarkerPricing;\n}\n\n/**\n * A planner decides *what* to remove. It never removes anything itself; `applyPlan`\n * does that. Keeping the decision and the mutation apart is what makes the decision\n * testable in isolation.\n */\nexport interface Planner {\n readonly id: string;\n plan(input: PlanInput): Promise<ElisionPlan>;\n}\n\n/** One elision that actually happened, with the receipt needed to undo it. */\nexport interface AppliedElision {\n /** Content hash of the removed bytes — the key `retrieve()` takes. */\n readonly hash: string;\n /** Where the removed bytes were in the *input*. */\n readonly range: ByteRange;\n /**\n * Where the marker sits in the *output*. Law 3 — every elision is reversible — needs\n * this: {@link Reconstructor} splices stored bytes back over these ranges. Without it,\n * \"reversible\" would mean parsing markers back out of the text, which is a guess.\n * This is a fact recorded at the moment of the cut.\n */\n readonly outputRange: ByteRange;\n /** Size of the removed content, in UTF-8 bytes. */\n readonly bytes: number;\n readonly reason: ElisionReason;\n /** The exact marker text substituted into the output. */\n readonly marker: string;\n}\n\n/**\n * A consumer-supplied counter, so a caller who bills in tokens can *see* tokens.\n *\n * Budgets in smelt's core are UTF-8 bytes, permanently — bytes are the only unit that\n * is computable locally for every model, and they mean the same thing in five years.\n * See `docs/ARCHITECTURE.md` § \"Decision 1\". This hook does not change that: it adds a\n * second, labelled number to the result. The plan is still made in bytes.\n *\n * Both `id` and `unit` are required, and that is a Law 4 decision rather than\n * bookkeeping: a token count is meaningless without naming the tokenizer that produced\n * it. Anthropic's own docs record that Claude 4.7 and later use a newer tokenizer where\n * the same text yields roughly 30% more tokens than on earlier models — so `1,204\n * tokens` is not a fact, and `1,204 tokens (claude-4.7/count_tokens)` is.\n *\n * **This hook does not relax Law 1.** smelt imports no transport and the guard proves\n * that about smelt's own modules; it cannot prove it about a function you hand in. A\n * `count()` that calls an API makes *your* process call an API, from a line in *your*\n * source — exactly the arrangement {@link RerankStage} already describes. `count` is\n * synchronous on purpose: local tokenizers are synchronous, and network clients are not.\n */\nexport interface Measure {\n /** Names the counter, e.g. `'tiktoken/o200k_base'` or `'claude-4.7/count_tokens'`. */\n readonly id: string;\n /** The unit `count()` returns, e.g. `'tokens'`. Printed next to the number. */\n readonly unit: string;\n /** Local, synchronous count over the whole string. */\n count(text: string): number;\n}\n\n/** A second size for a result, in someone else's unit, with the counter named. */\nexport interface MeasuredSize {\n /** {@link Measure.id} of the counter that produced these numbers. */\n readonly measure: string;\n /** {@link Measure.unit}. */\n readonly unit: string;\n readonly input: number;\n readonly output: number;\n}\n\n/** The result of smelting one blob of text. */\nexport interface SmeltResult {\n readonly text: string;\n readonly inputBytes: number;\n readonly outputBytes: number;\n readonly planner: string;\n readonly language: DetectedLanguage;\n readonly elisions: readonly AppliedElision[];\n /** Present only when the caller supplied a {@link Measure}. Never invented. */\n readonly measured?: MeasuredSize;\n}\n\n/**\n * Reversibility, as a callable. Takes a {@link SmeltResult} and the store that holds its\n * elided bytes, and returns the original text — byte for byte.\n */\nexport type Reconstructor = (result: SmeltResult, store: ElisionStore) => string;\n\n// ---------------------------------------------------------------------------\n// The store, and the counters that make over-pruning visible\n// ---------------------------------------------------------------------------\n\n/**\n * The numbers that keep smelt honest about itself.\n *\n * Law 3 says elisions are reversible. That is cheap to satisfy and easy to abuse: a\n * compressor that cuts everything is \"reversible\" and useless. The *retrieve rate* is\n * the tell. If the model keeps calling `smelt_retrieve`, smelt cut material the task\n * needed, and the round trip cost more tokens than the elision saved.\n *\n * So: `expansionRate` is not telemetry. It is the metric a caller tunes budgets\n * against, and the only number smelt is willing to have an opinion about — because it\n * measures it locally, per session, on the caller's own traffic.\n */\nexport interface RetrieveStats {\n /** Distinct blobs put into the store. */\n readonly elisionsStored: number;\n /** Total bytes held by the store. */\n readonly bytesStored: number;\n /** Every `retrieve()` call, including repeats and misses. */\n readonly retrieveCalls: number;\n /** Distinct hashes successfully retrieved at least once. */\n readonly uniqueRetrieved: number;\n /** Calls for a hash the store does not hold. Non-zero means a bug, not over-pruning. */\n readonly misses: number;\n /**\n * `uniqueRetrieved / elisionsStored`, or `0` when nothing has been stored.\n *\n * Read it as: *what fraction of what smelt hid did the model have to ask for back?*\n * There is no universally right value, and smelt does not ship a threshold it has\n * not measured. Rising across a workload is the signal.\n */\n readonly expansionRate: number;\n /**\n * The one degenerate outcome smelt is willing to name: **every distinct blob it hid\n * was asked for again.**\n *\n * smelt ships no expansion-rate threshold, because a threshold is a policy claim it\n * has no basis for and the right rate depends on how aggressive a budget the caller\n * chose — and a library printing warnings into someone else's process is bad manners.\n * This is not a threshold. At `uniqueRetrieved === elisionsStored` the elision\n * achieved nothing and cost a round trip: an arithmetic fact, not a preference. What\n * to do about it is the caller's call.\n *\n * `false` for an empty store — nothing was hidden, so nothing was defeated.\n */\n readonly allElisionsRetrieved: boolean;\n}\n\n/**\n * Local, content-addressed storage for elided bytes. No network, no eviction in v1 —\n * evicting is how \"reversible\" quietly becomes \"reversible for a while\".\n */\nexport interface ElisionStore {\n /** Store content, returning its hash. Idempotent for identical content. */\n put(content: string): string;\n /** The stored content, or `undefined` if this store never held that hash. */\n peek(hash: string): string | undefined;\n /**\n * The stored content, *counted* as a retrieval. This is what the model's tool calls.\n *\n * @throws {UnknownHashError} when the hash is unknown.\n * @throws {StoreCorruptionError} — {@link DirectoryElisionStore} only — when the\n * bytes on disk no longer hash to their own name. Distinct from\n * `UnknownHashError` on purpose: \"we hold damaged bytes\" and \"it never existed\"\n * are different answers, and a caller that conflates them will report the wrong\n * one to its user.\n *\n * A {@link DirectoryElisionStore} whose journal cannot be written (a read-only\n * store directory, a full disk) still returns the bytes — verified bytes are never\n * withheld over a bookkeeping failure — and surfaces the lost count as a\n * `process.emitWarning` named `SmeltCounterWriteFailure` instead of throwing. Its\n * stats go quiet from that point; the retrieval itself succeeded.\n */\n retrieve(hash: string): string;\n /**\n * Whether this hash can be retrieved — **not** merely whether a slot bearing it\n * exists. `true` must mean the very next `retrieve(hash)` returns bytes, so a store\n * that verifies content on read verifies here too and raises its corruption error\n * rather than answering `true` for bytes it would then refuse. Uncounted: a check is\n * not the model asking for material back.\n */\n has(hash: string): boolean;\n /** A snapshot of the counters. See {@link RetrieveStats}. */\n stats(): RetrieveStats;\n}\n\n/**\n * The retrieval tool a consumer exposes to its model. Deliberately not an MCP or\n * provider-specific shape — smelt does not know which SDK you use. The consumer adapts\n * this into its own tool schema; the contract is `hash in, exact bytes out`.\n */\nexport interface RetrieveTool {\n /** `'smelt_retrieve'`. Stable — consumers hard-code it in prompts. */\n readonly name: string;\n /** Prose the consumer can put straight into a tool description. */\n readonly description: string;\n /**\n * JSON-Schema-shaped parameter description, for consumers that want one.\n *\n * Strict-mode shaped: `additionalProperties: false` and a `required` naming every\n * property, so a consumer registering this under OpenAI's structured-outputs strict\n * mode is not refused at registration. See {@link createRetrieveTool}.\n */\n readonly inputSchema: {\n readonly type: 'object';\n readonly properties: {\n readonly hash: { readonly type: 'string'; readonly description: string };\n };\n readonly required: readonly ['hash'];\n readonly additionalProperties: false;\n };\n /**\n * @throws {UnknownHashError} when the hash is unknown.\n * @throws {StoreCorruptionError} when the backing store holds damaged bytes for\n * that hash — see {@link ElisionStore.retrieve}, whose contract this forwards\n * verbatim. Surface either to the model as a tool error, never as empty text.\n */\n invoke(input: { readonly hash: string }): string;\n}\n\n// ---------------------------------------------------------------------------\n// Pluggable stages — interfaces in v1, nothing more\n// ---------------------------------------------------------------------------\n\n/** A candidate handed to a {@link RerankStage}: an opaque id plus the text to judge. */\nexport interface RerankCandidate {\n readonly id: string;\n readonly text: string;\n}\n\n/** A reranked candidate, most relevant first. `score` is the stage's own scale. */\nexport interface RerankedCandidate extends RerankCandidate {\n readonly score: number;\n}\n\n/**\n * Relevance reranking — a *seam*, not a feature.\n *\n * Hosted rerankers are good and smelt will never bundle one, because bundling would\n * break Law 1: the moment smelt ships a default reranker, `smelt()` can make a network\n * call that the caller did not ask for and cannot see. A consumer that wants one\n * implements this interface, wires its own key, and owns the fact that its context now\n * leaves the machine. That decision must be legible in the consumer's own source.\n */\nexport interface RerankStage {\n readonly id: string;\n /** May make network calls — that is the consumer's choice, made in the consumer's code. */\n rerank(\n candidates: readonly RerankCandidate[],\n query: string,\n ): Promise<readonly RerankedCandidate[]>;\n}\n\n/**\n * Learned distillation — rewriting content with a model instead of cutting it.\n *\n * Out of v1 for a reason beyond the network: a distilled paragraph cannot satisfy\n * Law 2. \"The model summarised this\" is not an explanation of what was removed, and\n * the removed material is no longer recoverable from the output. If this ever ships,\n * it ships as a stage that stores the original and explains itself in the same terms\n * every other rule does.\n */\nexport interface DistillStage {\n readonly id: string;\n distill(text: string, budgetBytes: number): Promise<string>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["/** Languages smelt can parse structurally. Everything else falls back to the lexical planner. */\nexport type LanguageId =\n | 'typescript'\n | 'tsx'\n | 'javascript'\n | 'rust'\n | 'python'\n | 'go'\n | 'java'\n | 'c'\n | 'cpp'\n | 'c_sharp'\n | 'ruby'\n | 'php'\n | 'kotlin'\n | 'swift'\n | 'bash';\n\n/** `'unknown'` is a first-class outcome, not a failure: it selects the lexical planner. */\nexport type DetectedLanguage = LanguageId | 'unknown';\n\n/** A half-open byte range `[start, end)` into the UTF-8 bytes of the input. */\nexport interface ByteRange {\n readonly start: number;\n readonly end: number;\n}\n\n/**\n * Why a range was elided, in two registers: a stable id for counters, and a sentence\n * a human can read in a diff. Law 2 — every elision is explainable — lives here:\n * if you cannot write the sentence, the rule does not ship.\n */\nexport interface ElisionReason {\n /** Stable machine id, e.g. `'sibling-collapse'`, `'line-window'`. */\n readonly rule: string;\n /** e.g. `'collapsed 3 sibling functions'`. Present tense, no trailing period. */\n readonly explanation: string;\n}\n\n/** One range a planner proposes to remove. Plans are pure data — inspectable and testable. */\nexport interface PlannedElision {\n readonly range: ByteRange;\n readonly reason: ElisionReason;\n /**\n * The **outline**: the names of the declarations this elision collapses, in source\n * order — `['parseConfig', 'normalisePath']` — when the planner can read them off a\n * parse tree. Absent, never empty, when it cannot (a lexical planner sees lines, not\n * declarations; a run of comments has nothing to name).\n *\n * Out of band by design. The names ride on the plan, the applied elision and the\n * report — never in the marker, whose bytes and priced cost do not move by one byte\n * (`test/guards/marker-format.test.ts` pins that). It exists because the planner\n * held the cheapest possible index of what it hid and threw it away at explanation\n * time, leaving a model on a whole-file task to retrieve hash by hash just to learn\n * what was behind each marker.\n */\n readonly names?: readonly string[];\n}\n\n/**\n * The complete output of a planner: the whole decision, before anything is mutated.\n * A plan can be logged, diffed, snapshot-tested, and rejected without touching the text.\n */\nexport interface ElisionPlan {\n readonly planner: string;\n readonly language: DetectedLanguage;\n readonly elisions: readonly PlannedElision[];\n}\n\n/**\n * The seam through which a planner asks what a marker will cost, in UTF-8 bytes.\n *\n * Marker cost is `apply.ts`'s fact — the applier renders the marker, so only the\n * applier knows its price. Planners need that price for two decisions (profitability:\n * a marker that costs more than it removes grows the output; and budget prediction:\n * which ladder rung actually fits), and before this seam each planner rebuilt the\n * marker machinery privately to measure it — correct, but an inversion. Now\n * `markerPricing()` in `apply.ts` builds the one adapter from the exact builder\n * `applyPlan` will use, and planners ask it. They never guess, and they never render\n * a marker of their own.\n *\n * The price is exact, not an estimate: the cost of the marker this elision would\n * earn, comment leader and all, with a stand-in hash of the real hash's length —\n * marker cost depends on the hash's *length*, never its value.\n */\nexport interface MarkerPricing {\n /** The exact UTF-8 byte cost of the marker an elision with this reason and size would earn. */\n costBytes(reason: ElisionReason, elidedBytes: number): number;\n}\n\n/**\n * What the caller hands a planner.\n *\n * Constructed centrally: `createSmelter` (and through it, the CLI) builds the one\n * `PlanInput` per call, including its {@link MarkerPricing} — a caller invoking\n * `planLexical`/`planStructural` directly builds `pricing` with `markerPricing()`\n * from `apply.ts`. A JS caller who omits it gets {@link MissingMarkerPricingError}\n * at plan time, not a guessed cost.\n */\nexport interface PlanInput {\n readonly text: string;\n readonly language: DetectedLanguage;\n /** Soft ceiling for the emitted output, in UTF-8 bytes. Planners aim under it. */\n readonly budgetBytes: number;\n /**\n * What the caller was actually looking for — grep pattern, symbol name, error string.\n * Planners keep matching regions and collapse around them.\n */\n readonly focus?: readonly string[];\n /**\n * What a marker costs. Required: a planner that guesses marker cost can plan an\n * elision that grows the output. See {@link MarkerPricing}; built by\n * `markerPricing()` in `apply.ts` from the exact builder `applyPlan` will use.\n */\n readonly pricing: MarkerPricing;\n /**\n * The store's per-rule ledger — how many cuts each rule has made in this store and\n * how many of them were asked for back — when the store can supply one. Filled\n * centrally by `createSmelter`, like {@link MarkerPricing}; never guessed.\n *\n * **Opt-in data, not a lever.** The shipped planners do not read it: smelt measures\n * the expansion rate and never thresholds it (`docs/ARCHITECTURE.md` § Decision 4),\n * so a rule that \"does not pay\" is a fact a *caller's* planner may weigh, and never\n * a warning smelt authors. This is the deterministic form of \"revert a cut that got\n * asked back\": the loop is closed as data a planner can read, in one place.\n */\n readonly ruleHistory?: readonly RuleLedgerEntry[];\n}\n\n/**\n * A planner decides *what* to remove. It never removes anything itself; `applyPlan`\n * does that. Keeping the decision and the mutation apart is what makes the decision\n * testable in isolation.\n */\nexport interface Planner {\n readonly id: string;\n plan(input: PlanInput): Promise<ElisionPlan>;\n}\n\n/** One elision that actually happened, with the receipt needed to undo it. */\nexport interface AppliedElision {\n /** Content hash of the removed bytes — the key `retrieve()` takes. */\n readonly hash: string;\n /** Where the removed bytes were in the *input*. */\n readonly range: ByteRange;\n /**\n * Where the marker sits in the *output*. Law 3 — every elision is reversible — needs\n * this: {@link Reconstructor} splices stored bytes back over these ranges. Without it,\n * \"reversible\" would mean parsing markers back out of the text, which is a guess.\n * This is a fact recorded at the moment of the cut.\n */\n readonly outputRange: ByteRange;\n /** Size of the removed content, in UTF-8 bytes. */\n readonly bytes: number;\n readonly reason: ElisionReason;\n /** The exact marker text substituted into the output. */\n readonly marker: string;\n /** The planner's outline, carried verbatim from {@link PlannedElision.names}. */\n readonly names?: readonly string[];\n}\n\n/**\n * A consumer-supplied counter, so a caller who bills in tokens can *see* tokens.\n *\n * Budgets in smelt's core are UTF-8 bytes, permanently — bytes are the only unit that\n * is computable locally for every model, and they mean the same thing in five years.\n * See `docs/ARCHITECTURE.md` § \"Decision 1\". This hook does not change that: it adds a\n * second, labelled number to the result. The plan is still made in bytes.\n *\n * Both `id` and `unit` are required, and that is a Law 4 decision rather than\n * bookkeeping: a token count is meaningless without naming the tokenizer that produced\n * it. Anthropic's own docs record that Claude 4.7 and later use a newer tokenizer where\n * the same text yields roughly 30% more tokens than on earlier models — so `1,204\n * tokens` is not a fact, and `1,204 tokens (claude-4.7/count_tokens)` is.\n *\n * **This hook does not relax Law 1.** smelt imports no transport and the guard proves\n * that about smelt's own modules; it cannot prove it about a function you hand in. A\n * `count()` that calls an API makes *your* process call an API, from a line in *your*\n * source — exactly the arrangement {@link RerankStage} already describes. `count` is\n * synchronous on purpose: local tokenizers are synchronous, and network clients are not.\n */\nexport interface Measure {\n /** Names the counter, e.g. `'tiktoken/o200k_base'` or `'claude-4.7/count_tokens'`. */\n readonly id: string;\n /** The unit `count()` returns, e.g. `'tokens'`. Printed next to the number. */\n readonly unit: string;\n /** Local, synchronous count over the whole string. */\n count(text: string): number;\n}\n\n/** A second size for a result, in someone else's unit, with the counter named. */\nexport interface MeasuredSize {\n /** {@link Measure.id} of the counter that produced these numbers. */\n readonly measure: string;\n /** {@link Measure.unit}. */\n readonly unit: string;\n readonly input: number;\n readonly output: number;\n}\n\n/** The result of smelting one blob of text. */\nexport interface SmeltResult {\n readonly text: string;\n readonly inputBytes: number;\n readonly outputBytes: number;\n readonly planner: string;\n readonly language: DetectedLanguage;\n readonly elisions: readonly AppliedElision[];\n /** Present only when the caller supplied a {@link Measure}. Never invented. */\n readonly measured?: MeasuredSize;\n}\n\n/**\n * Reversibility, as a callable. Takes a {@link SmeltResult} and the store that holds its\n * elided bytes, and returns the original text — byte for byte.\n */\nexport type Reconstructor = (result: SmeltResult, store: ElisionStore) => string;\n\n// ---------------------------------------------------------------------------\n// The store, and the counters that make over-pruning visible\n// ---------------------------------------------------------------------------\n\n/**\n * The numbers that keep smelt honest about itself.\n *\n * Law 3 says elisions are reversible. That is cheap to satisfy and easy to abuse: a\n * compressor that cuts everything is \"reversible\" and useless. The *retrieve rate* is\n * the tell. If the model keeps calling `smelt_retrieve`, smelt cut material the task\n * needed, and the round trip cost more tokens than the elision saved.\n *\n * So: `expansionRate` is not telemetry. It is the metric a caller tunes budgets\n * against, and the only number smelt is willing to have an opinion about — because it\n * measures it locally, per session, on the caller's own traffic.\n */\nexport interface RetrieveStats {\n /** Distinct blobs put into the store. */\n readonly elisionsStored: number;\n /** Total bytes held by the store. */\n readonly bytesStored: number;\n /** Every `retrieve()` call, including repeats and misses. */\n readonly retrieveCalls: number;\n /** Distinct hashes successfully retrieved at least once. */\n readonly uniqueRetrieved: number;\n /** Calls for a hash the store does not hold. Non-zero means a bug, not over-pruning. */\n readonly misses: number;\n /**\n * `uniqueRetrieved / elisionsStored`, or `0` when nothing has been stored.\n *\n * Read it as: *what fraction of what smelt hid did the model have to ask for back?*\n * There is no universally right value, and smelt does not ship a threshold it has\n * not measured. Rising across a workload is the signal.\n */\n readonly expansionRate: number;\n /**\n * The one degenerate outcome smelt is willing to name: **every distinct blob it hid\n * was asked for again.**\n *\n * smelt ships no expansion-rate threshold, because a threshold is a policy claim it\n * has no basis for and the right rate depends on how aggressive a budget the caller\n * chose — and a library printing warnings into someone else's process is bad manners.\n * This is not a threshold. At `uniqueRetrieved === elisionsStored` the elision\n * achieved nothing and cost a round trip: an arithmetic fact, not a preference. What\n * to do about it is the caller's call.\n *\n * `false` for an empty store — nothing was hidden, so nothing was defeated.\n */\n readonly allElisionsRetrieved: boolean;\n}\n\n/**\n * One row of a store's **ledger**: a rule, the distinct hashes it put, and how many\n * of those were retrieved at least once. Rows are sorted by rule, so two reads of one\n * store — or of one directory from two processes — render identically.\n *\n * `retrieved === stored` for a rule is the per-rule form of `allElisionsRetrieved`:\n * every cut that rule made was asked for back, an arithmetic fact and never a\n * threshold. What to do about it is the caller's call.\n */\nexport interface RuleLedgerEntry {\n /** The {@link ElisionReason.rule} id, e.g. `'sibling-collapse'`. */\n readonly rule: string;\n /** Distinct hashes put under this rule. */\n readonly stored: number;\n /** Of those, distinct hashes retrieved at least once. */\n readonly retrieved: number;\n}\n\n/**\n * Local, content-addressed storage for elided bytes. No network, no eviction in v1 —\n * evicting is how \"reversible\" quietly becomes \"reversible for a while\".\n */\nexport interface ElisionStore {\n /**\n * Store content, returning its hash. Idempotent for identical content.\n *\n * `reason` is the rule the content was cut by, when the caller is the applier — it\n * feeds the store's {@link ledger}. Optional, so a store written before ledgers and\n * a caller storing bytes for its own reasons both keep working; a put with no\n * reason is stored and never attributed.\n */\n put(content: string, reason?: ElisionReason): string;\n /** The stored content, or `undefined` if this store never held that hash. */\n peek(hash: string): string | undefined;\n /**\n * The stored content, *counted* as a retrieval. This is what the model's tool calls.\n *\n * @throws {UnknownHashError} when the hash is unknown.\n * @throws {StoreCorruptionError} — {@link DirectoryElisionStore} only — when the\n * bytes on disk no longer hash to their own name. Distinct from\n * `UnknownHashError` on purpose: \"we hold damaged bytes\" and \"it never existed\"\n * are different answers, and a caller that conflates them will report the wrong\n * one to its user.\n *\n * A {@link DirectoryElisionStore} whose journal cannot be written (a read-only\n * store directory, a full disk) still returns the bytes — verified bytes are never\n * withheld over a bookkeeping failure — and surfaces the lost count as a\n * `process.emitWarning` named `SmeltCounterWriteFailure` instead of throwing. Its\n * stats go quiet from that point; the retrieval itself succeeded.\n */\n retrieve(hash: string): string;\n /**\n * Whether this hash can be retrieved — **not** merely whether a slot bearing it\n * exists. `true` must mean the very next `retrieve(hash)` returns bytes, so a store\n * that verifies content on read verifies here too and raises its corruption error\n * rather than answering `true` for bytes it would then refuse. Uncounted: a check is\n * not the model asking for material back.\n */\n has(hash: string): boolean;\n /** A snapshot of the counters. See {@link RetrieveStats}. */\n stats(): RetrieveStats;\n /**\n * The per-rule ledger, when this store keeps one — both shipped stores do. Optional\n * so a custom store need not; a consumer that wants the feedback loop implements it\n * with the shared `ruleLedger()` derivation from `stats.ts`. Uncounted, like\n * `stats()`: reading the ledger never moves it.\n */\n ledger?(): readonly RuleLedgerEntry[];\n}\n\n/**\n * The retrieval tool a consumer exposes to its model. Deliberately not an MCP or\n * provider-specific shape — smelt does not know which SDK you use. The consumer adapts\n * this into its own tool schema; the contract is `hash in, exact bytes out`.\n */\nexport interface RetrieveTool {\n /** `'smelt_retrieve'`. Stable — consumers hard-code it in prompts. */\n readonly name: string;\n /** Prose the consumer can put straight into a tool description. */\n readonly description: string;\n /**\n * JSON-Schema-shaped parameter description, for consumers that want one.\n *\n * Strict-mode shaped: `additionalProperties: false` and a `required` naming every\n * property, so a consumer registering this under OpenAI's structured-outputs strict\n * mode is not refused at registration. See {@link createRetrieveTool}.\n */\n readonly inputSchema: {\n readonly type: 'object';\n readonly properties: {\n readonly hash: { readonly type: 'string'; readonly description: string };\n };\n readonly required: readonly ['hash'];\n readonly additionalProperties: false;\n };\n /**\n * @throws {UnknownHashError} when the hash is unknown.\n * @throws {StoreCorruptionError} when the backing store holds damaged bytes for\n * that hash — see {@link ElisionStore.retrieve}, whose contract this forwards\n * verbatim. Surface either to the model as a tool error, never as empty text.\n */\n invoke(input: { readonly hash: string }): string;\n}\n\n/**\n * One answer inside a batched retrieval: the exact bytes for a hash, or the store's\n * own refusal for it. A batch never fails as a whole — a model that asked for\n * eighteen blobs and typo'd one must still get the seventeen, and the one refusal\n * must still be the store's distinct error (`UnknownHashError` vs\n * `StoreCorruptionError`), never an empty string standing in for either.\n */\nexport type RetrievedBlock =\n | { readonly hash: string; readonly text: string }\n | { readonly hash: string; readonly error: Error };\n\n/**\n * The batched sibling of {@link RetrieveTool}: N hashes in, one {@link RetrievedBlock}\n * per hash out, in the order asked. Additive — `smelt_retrieve` is the frozen wire\n * surface and stays byte-identical beside this.\n *\n * Why it exists is a measured fact, not a convenience: every tool call is a new\n * request, and input tokens are billed per request, so a model expanding eighteen\n * markers one call at a time re-bills its whole transcript eighteen times. One\n * request for eighteen blocks changes what that costs without changing what the\n * expansion rate *means* — each hit inside the batch is journalled exactly as a\n * single call would journal it.\n */\nexport interface RetrieveBatchTool {\n /** `'smelt_retrieve_batch'`. Stable — consumers hard-code it in prompts. */\n readonly name: string;\n /** Prose the consumer can put straight into a tool description. */\n readonly description: string;\n /** Strict-mode shaped, like {@link RetrieveTool.inputSchema}. */\n readonly inputSchema: {\n readonly type: 'object';\n readonly properties: {\n readonly hashes: {\n readonly type: 'array';\n readonly items: { readonly type: 'string' };\n readonly description: string;\n };\n };\n readonly required: readonly ['hashes'];\n readonly additionalProperties: false;\n };\n /**\n * One block per hash, in order. Never throws for a hash the store refuses — that\n * refusal rides inside its block — but anything that is not the store's own\n * refusal (an I/O failure, a bug) still propagates.\n */\n invoke(input: { readonly hashes: readonly string[] }): readonly RetrievedBlock[];\n}\n\n// ---------------------------------------------------------------------------\n// Pluggable stages — interfaces in v1, nothing more\n// ---------------------------------------------------------------------------\n\n/** A candidate handed to a {@link RerankStage}: an opaque id plus the text to judge. */\nexport interface RerankCandidate {\n readonly id: string;\n readonly text: string;\n}\n\n/** A reranked candidate, most relevant first. `score` is the stage's own scale. */\nexport interface RerankedCandidate extends RerankCandidate {\n readonly score: number;\n}\n\n/**\n * Relevance reranking — a *seam*, not a feature.\n *\n * Hosted rerankers are good and smelt will never bundle one, because bundling would\n * break Law 1: the moment smelt ships a default reranker, `smelt()` can make a network\n * call that the caller did not ask for and cannot see. A consumer that wants one\n * implements this interface, wires its own key, and owns the fact that its context now\n * leaves the machine. That decision must be legible in the consumer's own source.\n */\nexport interface RerankStage {\n readonly id: string;\n /** May make network calls — that is the consumer's choice, made in the consumer's code. */\n rerank(\n candidates: readonly RerankCandidate[],\n query: string,\n ): Promise<readonly RerankedCandidate[]>;\n}\n\n/**\n * Learned distillation — rewriting content with a model instead of cutting it.\n *\n * Out of v1 for a reason beyond the network: a distilled paragraph cannot satisfy\n * Law 2. \"The model summarised this\" is not an explanation of what was removed, and\n * the removed material is no longer recoverable from the output. If this ever ships,\n * it ships as a stage that stores the original and explains itself in the same terms\n * every other rule does.\n */\nexport interface DistillStage {\n readonly id: string;\n distill(text: string, budgetBytes: number): Promise<string>;\n}\n"]}
|