@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.
Files changed (181) hide show
  1. package/dist/apply.d.ts.map +1 -1
  2. package/dist/apply.js +8 -2
  3. package/dist/apply.js.map +1 -1
  4. package/dist/cli/bin.d.ts.map +1 -1
  5. package/dist/cli/bin.js +9 -4
  6. package/dist/cli/bin.js.map +1 -1
  7. package/dist/cli/doctor.d.ts +66 -0
  8. package/dist/cli/doctor.d.ts.map +1 -0
  9. package/dist/cli/doctor.js +137 -0
  10. package/dist/cli/doctor.js.map +1 -0
  11. package/dist/cli/hooks.d.ts +36 -2
  12. package/dist/cli/hooks.d.ts.map +1 -1
  13. package/dist/cli/hooks.js +108 -64
  14. package/dist/cli/hooks.js.map +1 -1
  15. package/dist/cli/init.d.ts.map +1 -1
  16. package/dist/cli/init.js +12 -16
  17. package/dist/cli/init.js.map +1 -1
  18. package/dist/cli/installed.d.ts +56 -0
  19. package/dist/cli/installed.d.ts.map +1 -0
  20. package/dist/cli/installed.js +105 -0
  21. package/dist/cli/installed.js.map +1 -0
  22. package/dist/cli/lava.d.ts +31 -0
  23. package/dist/cli/lava.d.ts.map +1 -0
  24. package/dist/cli/lava.js +82 -0
  25. package/dist/cli/lava.js.map +1 -0
  26. package/dist/cli/report.d.ts +10 -1
  27. package/dist/cli/report.d.ts.map +1 -1
  28. package/dist/cli/report.js +16 -1
  29. package/dist/cli/report.js.map +1 -1
  30. package/dist/cli/setup.d.ts +87 -0
  31. package/dist/cli/setup.d.ts.map +1 -0
  32. package/dist/cli/setup.js +513 -0
  33. package/dist/cli/setup.js.map +1 -0
  34. package/dist/cli/shell.d.ts +11 -4
  35. package/dist/cli/shell.d.ts.map +1 -1
  36. package/dist/cli/shell.js.map +1 -1
  37. package/dist/cli/subcommands/agents.d.ts.map +1 -1
  38. package/dist/cli/subcommands/agents.js +2 -1
  39. package/dist/cli/subcommands/agents.js.map +1 -1
  40. package/dist/cli/subcommands/doctor.d.ts +15 -0
  41. package/dist/cli/subcommands/doctor.d.ts.map +1 -0
  42. package/dist/cli/subcommands/doctor.js +43 -0
  43. package/dist/cli/subcommands/doctor.js.map +1 -0
  44. package/dist/cli/subcommands/flags.d.ts +16 -0
  45. package/dist/cli/subcommands/flags.d.ts.map +1 -1
  46. package/dist/cli/subcommands/flags.js +43 -4
  47. package/dist/cli/subcommands/flags.js.map +1 -1
  48. package/dist/cli/subcommands/hooks.d.ts.map +1 -1
  49. package/dist/cli/subcommands/hooks.js +10 -2
  50. package/dist/cli/subcommands/hooks.js.map +1 -1
  51. package/dist/cli/subcommands/init.d.ts.map +1 -1
  52. package/dist/cli/subcommands/init.js +2 -1
  53. package/dist/cli/subcommands/init.js.map +1 -1
  54. package/dist/cli/subcommands/registry.d.ts +3 -1
  55. package/dist/cli/subcommands/registry.d.ts.map +1 -1
  56. package/dist/cli/subcommands/registry.js +6 -0
  57. package/dist/cli/subcommands/registry.js.map +1 -1
  58. package/dist/cli/subcommands/setup.d.ts +23 -0
  59. package/dist/cli/subcommands/setup.d.ts.map +1 -0
  60. package/dist/cli/subcommands/setup.js +87 -0
  61. package/dist/cli/subcommands/setup.js.map +1 -0
  62. package/dist/cli/subcommands/smelt.d.ts +3 -0
  63. package/dist/cli/subcommands/smelt.d.ts.map +1 -1
  64. package/dist/cli/subcommands/smelt.js +5 -1
  65. package/dist/cli/subcommands/smelt.js.map +1 -1
  66. package/dist/cli/subcommands/stats.d.ts +8 -3
  67. package/dist/cli/subcommands/stats.d.ts.map +1 -1
  68. package/dist/cli/subcommands/stats.js +15 -5
  69. package/dist/cli/subcommands/stats.js.map +1 -1
  70. package/dist/cli/subcommands/subcommand.d.ts +1 -1
  71. package/dist/cli/subcommands/subcommand.d.ts.map +1 -1
  72. package/dist/cli/subcommands/subcommand.js.map +1 -1
  73. package/dist/cli/wizard.d.ts +72 -0
  74. package/dist/cli/wizard.d.ts.map +1 -0
  75. package/dist/cli/wizard.js +86 -0
  76. package/dist/cli/wizard.js.map +1 -0
  77. package/dist/errors.d.ts +10 -0
  78. package/dist/errors.d.ts.map +1 -1
  79. package/dist/errors.js +10 -0
  80. package/dist/errors.js.map +1 -1
  81. package/dist/harness/claude-code.d.ts.map +1 -1
  82. package/dist/harness/claude-code.js +10 -0
  83. package/dist/harness/claude-code.js.map +1 -1
  84. package/dist/harness/codex.d.ts.map +1 -1
  85. package/dist/harness/codex.js +1 -0
  86. package/dist/harness/codex.js.map +1 -1
  87. package/dist/harness/grok.d.ts.map +1 -1
  88. package/dist/harness/grok.js +4 -1
  89. package/dist/harness/grok.js.map +1 -1
  90. package/dist/harness/kilocode.d.ts.map +1 -1
  91. package/dist/harness/kilocode.js +1 -1
  92. package/dist/harness/kilocode.js.map +1 -1
  93. package/dist/harness/opencode.d.ts.map +1 -1
  94. package/dist/harness/opencode.js +10 -0
  95. package/dist/harness/opencode.js.map +1 -1
  96. package/dist/harness/profile.d.ts +23 -2
  97. package/dist/harness/profile.d.ts.map +1 -1
  98. package/dist/harness/profile.js.map +1 -1
  99. package/dist/harness/snippet.d.ts +14 -1
  100. package/dist/harness/snippet.d.ts.map +1 -1
  101. package/dist/harness/snippet.js +18 -2
  102. package/dist/harness/snippet.js.map +1 -1
  103. package/dist/hooks/focus-terms.d.ts +57 -0
  104. package/dist/hooks/focus-terms.d.ts.map +1 -0
  105. package/dist/hooks/focus-terms.js +230 -0
  106. package/dist/hooks/focus-terms.js.map +1 -0
  107. package/dist/hooks/guard-core.d.ts +8 -16
  108. package/dist/hooks/guard-core.d.ts.map +1 -1
  109. package/dist/hooks/guard-core.js +22 -120
  110. package/dist/hooks/guard-core.js.map +1 -1
  111. package/dist/index.d.ts +16 -4
  112. package/dist/index.d.ts.map +1 -1
  113. package/dist/index.js +12 -3
  114. package/dist/index.js.map +1 -1
  115. package/dist/ops/index.d.ts +3 -3
  116. package/dist/ops/index.d.ts.map +1 -1
  117. package/dist/ops/index.js +2 -2
  118. package/dist/ops/index.js.map +1 -1
  119. package/dist/ops/verbs.d.ts +57 -1
  120. package/dist/ops/verbs.d.ts.map +1 -1
  121. package/dist/ops/verbs.js +49 -1
  122. package/dist/ops/verbs.js.map +1 -1
  123. package/dist/plan/auto.d.ts +9 -2
  124. package/dist/plan/auto.d.ts.map +1 -1
  125. package/dist/plan/auto.js +14 -2
  126. package/dist/plan/auto.js.map +1 -1
  127. package/dist/plan/diff.d.ts +45 -0
  128. package/dist/plan/diff.d.ts.map +1 -0
  129. package/dist/plan/diff.js +284 -0
  130. package/dist/plan/diff.js.map +1 -0
  131. package/dist/plan/json.d.ts +37 -0
  132. package/dist/plan/json.d.ts.map +1 -0
  133. package/dist/plan/json.js +181 -0
  134. package/dist/plan/json.js.map +1 -0
  135. package/dist/plan/kind.d.ts +28 -0
  136. package/dist/plan/kind.d.ts.map +1 -0
  137. package/dist/plan/kind.js +57 -0
  138. package/dist/plan/kind.js.map +1 -0
  139. package/dist/plan/offsets.d.ts +8 -0
  140. package/dist/plan/offsets.d.ts.map +1 -0
  141. package/dist/plan/offsets.js +19 -0
  142. package/dist/plan/offsets.js.map +1 -0
  143. package/dist/plan/planners.d.ts +15 -5
  144. package/dist/plan/planners.d.ts.map +1 -1
  145. package/dist/plan/planners.js +13 -5
  146. package/dist/plan/planners.js.map +1 -1
  147. package/dist/plan/structural.d.ts.map +1 -1
  148. package/dist/plan/structural.js +106 -14
  149. package/dist/plan/structural.js.map +1 -1
  150. package/dist/retrieve.d.ts +22 -1
  151. package/dist/retrieve.d.ts.map +1 -1
  152. package/dist/retrieve.js +59 -0
  153. package/dist/retrieve.js.map +1 -1
  154. package/dist/setup/recipe.d.ts +73 -0
  155. package/dist/setup/recipe.d.ts.map +1 -0
  156. package/dist/setup/recipe.js +76 -0
  157. package/dist/setup/recipe.js.map +1 -0
  158. package/dist/smelter.d.ts +4 -0
  159. package/dist/smelter.d.ts.map +1 -1
  160. package/dist/smelter.js +4 -0
  161. package/dist/smelter.js.map +1 -1
  162. package/dist/stats.d.ts +16 -1
  163. package/dist/stats.d.ts.map +1 -1
  164. package/dist/stats.js +29 -0
  165. package/dist/stats.js.map +1 -1
  166. package/dist/store-dir.d.ts +9 -2
  167. package/dist/store-dir.d.ts.map +1 -1
  168. package/dist/store-dir.js +48 -7
  169. package/dist/store-dir.js.map +1 -1
  170. package/dist/store.d.ts +4 -2
  171. package/dist/store.d.ts.map +1 -1
  172. package/dist/store.js +12 -2
  173. package/dist/store.js.map +1 -1
  174. package/dist/text/json-edit.d.ts +15 -1
  175. package/dist/text/json-edit.d.ts.map +1 -1
  176. package/dist/text/json-edit.js +76 -5
  177. package/dist/text/json-edit.js.map +1 -1
  178. package/dist/types.d.ts +116 -2
  179. package/dist/types.d.ts.map +1 -1
  180. package/dist/types.js.map +1 -1
  181. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wizard.d.ts","sourceRoot":"","sources":["../../src/cli/wizard.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,qFAAqF;AACrF,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAEtD,+EAA+E;AAC/E,MAAM,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;AAExD;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAC9B,UAAU,EAAE,MAAM,GACjB;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAS5C;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,SAAS,IAAI,EAAE,EACtB,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAC3B,OAAO,SAAI,GACV,OAAO,CAAC,IAAI,CAAC,CAWf;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,GAAG,MAAM,CAAC,CAQ7F;AAED,yFAAyF;AACzF,wBAAsB,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAOrF;AAED,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,6DAA6D;AAC7D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,mEAAmE;AACnE,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAC3B,KAAK,EAAE,SAAS,eAAe,EAAE,EACjC,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,IAAI,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,MAAM,GACtC,IAAI,CAON;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAI5D","sourcesContent":["import { chmodSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nimport { CliUsageError } from '../errors.ts';\nimport { answerReader } from './shell.ts';\nimport type { AnswerStream } from './shell.ts';\n\n/**\n * The wizard kit — the stream machinery every interactive verb shares, extracted\n * once because its third copy was the one that raced: `setup` re-typed the ask\n * adapter without awaiting a step, and two prompts were on screen at once. A kit is\n * not an abstraction here; it is the deletion of two copies of a thing that had\n * already drifted (review II, KOT-255).\n *\n * What lives here, and nothing else:\n *\n * - {@link wizardAsk}: the ask adapter — `answerReader`, the prompt echo, the trim,\n * and the EOF refusal, whose message is each verb's own (init, hooks and setup\n * owe the reader different last sentences).\n * - {@link walkSteps}: the step machine with real back-navigation — the loop\n * `hooks` always had, `init` mirrored, and `setup` faked with \"lands on the last\n * question\". A step is `(ask) => 'ok' | 'back'`; back at the first step is\n * answered, not ignored.\n * - {@link confirmLoop} / {@link confirmYesNo}: the confirm prompts, retry copy\n * included, because two verbs spelling \"yes to…\" differently is drift.\n * - {@link listPlannedFiles} / {@link writePlannedFile}: the plan listing and the\n * one file-write mechanic (mkdir, write, chmod) every apply loop performs.\n *\n * Pure IO plumbing: no verb knowledge, no domain facts, no rendering opinions — the\n * lava adapter stays outside, at the verb boundary, where one switch styles them all.\n */\n\n/** One question at a time, each answered by a line. What every wizard's `ask` is. */\nexport type Ask = (prompt: string) => Promise<string>;\n\n/** One wizard step: `'ok'` advances, `'back'` returns to the previous step. */\nexport type Step = (ask: Ask) => Promise<'ok' | 'back'>;\n\n/**\n * The ask adapter over an injected stream. `eofMessage` is the refusal's tail — the\n * one sentence that differs per verb, and the only one allowed to: setup's teaches\n * the non-interactive flags, hooks' says what happens to already-confirmed writes,\n * init's states the plain fact.\n */\nexport function wizardAsk(\n input: AnswerStream,\n output: (text: string) => void,\n eofMessage: string,\n): { ask: Ask; release: () => Promise<void> } {\n const lines = answerReader(input);\n const ask: Ask = async (prompt) => {\n output(prompt);\n const next = await lines.next();\n if (next === undefined) throw new CliUsageError(eofMessage);\n return next.trim();\n };\n return { ask, release: () => lines.release() };\n}\n\n/**\n * The step machine: steps in order, `back` moving one step back. The first step's\n * back is answered where the user can read it — there is nothing before it, and\n * pretending otherwise is how a wizard eats an answer. `startAt` is where a confirm's\n * `back` lands: the last step, not the first.\n */\nexport async function walkSteps(\n steps: readonly Step[],\n ask: Ask,\n say: (text: string) => void,\n startAt = 0,\n): Promise<void> {\n let index = startAt;\n while (index < steps.length) {\n const outcome = await steps[index]!(ask);\n if (outcome === 'back') {\n if (index === 0) say(`This is the first step — there is nothing before it.\\n`);\n else index -= 1;\n } else {\n index += 1;\n }\n }\n}\n\n/**\n * `confirm (yes / no / back)> ` — the three-way confirm. `retryCopy` completes the\n * \"yes to …, no to …, back to …\" sentence, which is verb knowledge.\n */\nexport async function confirmLoop(ask: Ask, retryCopy: string): Promise<'yes' | 'no' | 'back'> {\n for (;;) {\n const answer = await ask(`confirm (yes / no / back)> `);\n if (answer === 'back') return 'back';\n if (answer === 'no') return 'no';\n if (answer === 'yes') return 'yes';\n await ask(`${retryCopy}\\n`);\n }\n}\n\n/** `confirm (yes / no)> ` — the two-way confirm, for flows with no step to return to. */\nexport async function confirmYesNo(ask: Ask, retryCopy: string): Promise<'yes' | 'no'> {\n for (;;) {\n const answer = await ask(`confirm (yes / no)> `);\n if (answer === 'no') return 'no';\n if (answer === 'yes') return 'yes';\n await ask(`${retryCopy}\\n`);\n }\n}\n\n/** One planned file, as the listing and the write mechanic both see it. */\nexport interface PlannedFileLike {\n readonly name: string;\n readonly path: string;\n readonly content: string;\n readonly exists: boolean;\n readonly unchanged: boolean;\n readonly mode?: number;\n}\n\n/** One skipped file, with the reason the plan refused it. */\nexport interface PlannedSkipLike {\n readonly name: string;\n readonly why: string;\n}\n\n/** The ` name…padEnd(32) (fate)` listing every confirm prints. */\nexport function listPlannedFiles(\n say: (text: string) => void,\n files: readonly PlannedFileLike[],\n skipped: readonly PlannedSkipLike[],\n fate: (file: PlannedFileLike) => string,\n): void {\n for (const file of files) {\n say(` ${file.name.padEnd(32)} (${fate(file)})\\n`);\n }\n for (const skip of skipped) {\n say(` ${skip.name.padEnd(32)} (SKIPPED: ${skip.why})\\n`);\n }\n}\n\n/** The one write mechanic: mkdir, write, chmod — in that order, everywhere. */\nexport function writePlannedFile(file: PlannedFileLike): void {\n mkdirSync(dirname(file.path), { recursive: true });\n writeFileSync(file.path, file.content);\n if (file.mode !== undefined) chmodSync(file.path, file.mode);\n}\n"]}
@@ -0,0 +1,86 @@
1
+ import { chmodSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { CliUsageError } from '../errors.js';
4
+ import { answerReader } from './shell.js';
5
+ /**
6
+ * The ask adapter over an injected stream. `eofMessage` is the refusal's tail — the
7
+ * one sentence that differs per verb, and the only one allowed to: setup's teaches
8
+ * the non-interactive flags, hooks' says what happens to already-confirmed writes,
9
+ * init's states the plain fact.
10
+ */
11
+ export function wizardAsk(input, output, eofMessage) {
12
+ const lines = answerReader(input);
13
+ const ask = async (prompt) => {
14
+ output(prompt);
15
+ const next = await lines.next();
16
+ if (next === undefined)
17
+ throw new CliUsageError(eofMessage);
18
+ return next.trim();
19
+ };
20
+ return { ask, release: () => lines.release() };
21
+ }
22
+ /**
23
+ * The step machine: steps in order, `back` moving one step back. The first step's
24
+ * back is answered where the user can read it — there is nothing before it, and
25
+ * pretending otherwise is how a wizard eats an answer. `startAt` is where a confirm's
26
+ * `back` lands: the last step, not the first.
27
+ */
28
+ export async function walkSteps(steps, ask, say, startAt = 0) {
29
+ let index = startAt;
30
+ while (index < steps.length) {
31
+ const outcome = await steps[index](ask);
32
+ if (outcome === 'back') {
33
+ if (index === 0)
34
+ say(`This is the first step — there is nothing before it.\n`);
35
+ else
36
+ index -= 1;
37
+ }
38
+ else {
39
+ index += 1;
40
+ }
41
+ }
42
+ }
43
+ /**
44
+ * `confirm (yes / no / back)> ` — the three-way confirm. `retryCopy` completes the
45
+ * "yes to …, no to …, back to …" sentence, which is verb knowledge.
46
+ */
47
+ export async function confirmLoop(ask, retryCopy) {
48
+ for (;;) {
49
+ const answer = await ask(`confirm (yes / no / back)> `);
50
+ if (answer === 'back')
51
+ return 'back';
52
+ if (answer === 'no')
53
+ return 'no';
54
+ if (answer === 'yes')
55
+ return 'yes';
56
+ await ask(`${retryCopy}\n`);
57
+ }
58
+ }
59
+ /** `confirm (yes / no)> ` — the two-way confirm, for flows with no step to return to. */
60
+ export async function confirmYesNo(ask, retryCopy) {
61
+ for (;;) {
62
+ const answer = await ask(`confirm (yes / no)> `);
63
+ if (answer === 'no')
64
+ return 'no';
65
+ if (answer === 'yes')
66
+ return 'yes';
67
+ await ask(`${retryCopy}\n`);
68
+ }
69
+ }
70
+ /** The ` name…padEnd(32) (fate)` listing every confirm prints. */
71
+ export function listPlannedFiles(say, files, skipped, fate) {
72
+ for (const file of files) {
73
+ say(` ${file.name.padEnd(32)} (${fate(file)})\n`);
74
+ }
75
+ for (const skip of skipped) {
76
+ say(` ${skip.name.padEnd(32)} (SKIPPED: ${skip.why})\n`);
77
+ }
78
+ }
79
+ /** The one write mechanic: mkdir, write, chmod — in that order, everywhere. */
80
+ export function writePlannedFile(file) {
81
+ mkdirSync(dirname(file.path), { recursive: true });
82
+ writeFileSync(file.path, file.content);
83
+ if (file.mode !== undefined)
84
+ chmodSync(file.path, file.mode);
85
+ }
86
+ //# sourceMappingURL=wizard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../src/cli/wizard.ts"],"names":[],"mappings":"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","sourcesContent":["import { chmodSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname } from 'node:path';\n\nimport { CliUsageError } from '../errors.ts';\nimport { answerReader } from './shell.ts';\nimport type { AnswerStream } from './shell.ts';\n\n/**\n * The wizard kit — the stream machinery every interactive verb shares, extracted\n * once because its third copy was the one that raced: `setup` re-typed the ask\n * adapter without awaiting a step, and two prompts were on screen at once. A kit is\n * not an abstraction here; it is the deletion of two copies of a thing that had\n * already drifted (review II, KOT-255).\n *\n * What lives here, and nothing else:\n *\n * - {@link wizardAsk}: the ask adapter — `answerReader`, the prompt echo, the trim,\n * and the EOF refusal, whose message is each verb's own (init, hooks and setup\n * owe the reader different last sentences).\n * - {@link walkSteps}: the step machine with real back-navigation — the loop\n * `hooks` always had, `init` mirrored, and `setup` faked with \"lands on the last\n * question\". A step is `(ask) => 'ok' | 'back'`; back at the first step is\n * answered, not ignored.\n * - {@link confirmLoop} / {@link confirmYesNo}: the confirm prompts, retry copy\n * included, because two verbs spelling \"yes to…\" differently is drift.\n * - {@link listPlannedFiles} / {@link writePlannedFile}: the plan listing and the\n * one file-write mechanic (mkdir, write, chmod) every apply loop performs.\n *\n * Pure IO plumbing: no verb knowledge, no domain facts, no rendering opinions — the\n * lava adapter stays outside, at the verb boundary, where one switch styles them all.\n */\n\n/** One question at a time, each answered by a line. What every wizard's `ask` is. */\nexport type Ask = (prompt: string) => Promise<string>;\n\n/** One wizard step: `'ok'` advances, `'back'` returns to the previous step. */\nexport type Step = (ask: Ask) => Promise<'ok' | 'back'>;\n\n/**\n * The ask adapter over an injected stream. `eofMessage` is the refusal's tail — the\n * one sentence that differs per verb, and the only one allowed to: setup's teaches\n * the non-interactive flags, hooks' says what happens to already-confirmed writes,\n * init's states the plain fact.\n */\nexport function wizardAsk(\n input: AnswerStream,\n output: (text: string) => void,\n eofMessage: string,\n): { ask: Ask; release: () => Promise<void> } {\n const lines = answerReader(input);\n const ask: Ask = async (prompt) => {\n output(prompt);\n const next = await lines.next();\n if (next === undefined) throw new CliUsageError(eofMessage);\n return next.trim();\n };\n return { ask, release: () => lines.release() };\n}\n\n/**\n * The step machine: steps in order, `back` moving one step back. The first step's\n * back is answered where the user can read it — there is nothing before it, and\n * pretending otherwise is how a wizard eats an answer. `startAt` is where a confirm's\n * `back` lands: the last step, not the first.\n */\nexport async function walkSteps(\n steps: readonly Step[],\n ask: Ask,\n say: (text: string) => void,\n startAt = 0,\n): Promise<void> {\n let index = startAt;\n while (index < steps.length) {\n const outcome = await steps[index]!(ask);\n if (outcome === 'back') {\n if (index === 0) say(`This is the first step — there is nothing before it.\\n`);\n else index -= 1;\n } else {\n index += 1;\n }\n }\n}\n\n/**\n * `confirm (yes / no / back)> ` — the three-way confirm. `retryCopy` completes the\n * \"yes to …, no to …, back to …\" sentence, which is verb knowledge.\n */\nexport async function confirmLoop(ask: Ask, retryCopy: string): Promise<'yes' | 'no' | 'back'> {\n for (;;) {\n const answer = await ask(`confirm (yes / no / back)> `);\n if (answer === 'back') return 'back';\n if (answer === 'no') return 'no';\n if (answer === 'yes') return 'yes';\n await ask(`${retryCopy}\\n`);\n }\n}\n\n/** `confirm (yes / no)> ` — the two-way confirm, for flows with no step to return to. */\nexport async function confirmYesNo(ask: Ask, retryCopy: string): Promise<'yes' | 'no'> {\n for (;;) {\n const answer = await ask(`confirm (yes / no)> `);\n if (answer === 'no') return 'no';\n if (answer === 'yes') return 'yes';\n await ask(`${retryCopy}\\n`);\n }\n}\n\n/** One planned file, as the listing and the write mechanic both see it. */\nexport interface PlannedFileLike {\n readonly name: string;\n readonly path: string;\n readonly content: string;\n readonly exists: boolean;\n readonly unchanged: boolean;\n readonly mode?: number;\n}\n\n/** One skipped file, with the reason the plan refused it. */\nexport interface PlannedSkipLike {\n readonly name: string;\n readonly why: string;\n}\n\n/** The ` name…padEnd(32) (fate)` listing every confirm prints. */\nexport function listPlannedFiles(\n say: (text: string) => void,\n files: readonly PlannedFileLike[],\n skipped: readonly PlannedSkipLike[],\n fate: (file: PlannedFileLike) => string,\n): void {\n for (const file of files) {\n say(` ${file.name.padEnd(32)} (${fate(file)})\\n`);\n }\n for (const skip of skipped) {\n say(` ${skip.name.padEnd(32)} (SKIPPED: ${skip.why})\\n`);\n }\n}\n\n/** The one write mechanic: mkdir, write, chmod — in that order, everywhere. */\nexport function writePlannedFile(file: PlannedFileLike): void {\n mkdirSync(dirname(file.path), { recursive: true });\n writeFileSync(file.path, file.content);\n if (file.mode !== undefined) chmodSync(file.path, file.mode);\n}\n"]}
package/dist/errors.d.ts CHANGED
@@ -105,4 +105,14 @@ export declare class RepoMapIoError extends SmeltError {
105
105
  readonly name = "RepoMapIoError";
106
106
  constructor(operation: string, path: string, cause: unknown);
107
107
  }
108
+ /**
109
+ * A content-kind planner (`json`, `diff`) was asked to plan text that is not that kind.
110
+ * It refuses rather than approximating, for the reason the structural planner refuses
111
+ * an unknown language: output labelled `json/v1` that is really line windows would be
112
+ * undetectable from outside. `auto` never raises this — it probes the kind first and
113
+ * routes only what the probe proved.
114
+ */
115
+ export declare class ContentKindError extends SmeltError {
116
+ readonly name = "ContentKindError";
117
+ }
108
118
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACnC,SAAkB,IAAI,EAAE,MAAM,CAAgB;CAC/C;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,UAAU;IACjD,SAAkB,IAAI,yBAAyB;IAE/C,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAKxC;CACF;AAED;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,UAAU;IAChD,SAAkB,IAAI,wBAAwB;IAE9C,YAAY,OAAO,EAAE,MAAM,EAE1B;CACF;AAED,0FAA0F;AAC1F,qBAAa,uBAAwB,SAAQ,UAAU;IACrD,SAAkB,IAAI,6BAA6B;CACpD;AAED,mEAAmE;AACnE,qBAAa,qBAAsB,SAAQ,UAAU;IACnD,SAAkB,IAAI,2BAA2B;CAClD;AAED,uEAAuE;AACvE,qBAAa,uBAAwB,SAAQ,UAAU;IACrD,SAAkB,IAAI,6BAA6B;CACpD;AAED,uEAAuE;AACvE,qBAAa,gBAAiB,SAAQ,UAAU;IAC9C,SAAkB,IAAI,sBAAsB;IAE5C,YAAY,IAAI,EAAE,MAAM,EAIvB;CACF;AAED;;;;;GAKG;AACH,qBAAa,yBAA0B,SAAQ,UAAU;IACvD,SAAkB,IAAI,+BAA+B;IAErD,YAAY,SAAS,EAAE,MAAM,EAO5B;CACF;AAED;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,UAAU;IAC3C,SAAkB,IAAI,mBAAmB;CAC1C;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,UAAU;IAChD,SAAkB,IAAI,wBAAwB;IAE9C,YAAY,IAAI,EAAE,MAAM,EAKvB;CACF;AAED;;;;;;GAMG;AACH,qBAAa,oBAAqB,SAAQ,UAAU;IAClD,SAAkB,IAAI,0BAA0B;IAEhD,YAAY,IAAI,EAAE,MAAM,EAMvB;CACF;AAED;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;IAC9C,SAAkB,IAAI,sBAAsB;CAC7C;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,cAAe,SAAQ,UAAU;IAC5C,SAAkB,IAAI,oBAAoB;IAE1C,YAAY,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAM1D;CACF","sourcesContent":["/**\n * Every error smelt throws is one of these. Consumers can `instanceof SmeltError`\n * to tell \"the library said no\" apart from \"something else blew up\".\n */\nexport class SmeltError extends Error {\n override readonly name: string = 'SmeltError';\n}\n\n/**\n * A scaffold stub. It throws instead of returning a plausible-looking wrong answer,\n * because a context optimizer that quietly returns bad output is indistinguishable\n * from one that works. See CONTRIBUTING.md § \"Silence is the enemy\".\n */\nexport class NotImplementedError extends SmeltError {\n override readonly name = 'NotImplementedError';\n\n constructor(what: string, seeAlso: string) {\n super(\n `smelt: ${what} is not implemented yet. This is a scaffold stub — it throws rather ` +\n `than returning a plausible wrong answer. See ${seeAlso}.`,\n );\n }\n}\n\n/**\n * Thrown when something in the elision path tried to reach a non-local resource.\n * v1 has no network. See docs/ARCHITECTURE.md § \"Law 1 — zero network\".\n */\nexport class NetworkPolicyError extends SmeltError {\n override readonly name = 'NetworkPolicyError';\n\n constructor(message: string) {\n super(`smelt: ${message}`);\n }\n}\n\n/** A plan asked to elide two overlapping ranges. Applying it would corrupt the output. */\nexport class OverlappingElisionError extends SmeltError {\n override readonly name = 'OverlappingElisionError';\n}\n\n/** A plan referenced a byte range that is not inside the input. */\nexport class RangeOutOfBoundsError extends SmeltError {\n override readonly name = 'RangeOutOfBoundsError';\n}\n\n/** A grammar was requested that is not installed or not registered. */\nexport class GrammarUnavailableError extends SmeltError {\n override readonly name = 'GrammarUnavailableError';\n}\n\n/** `retrieve(hash)` was called with a hash the store does not hold. */\nexport class UnknownHashError extends SmeltError {\n override readonly name = 'UnknownHashError';\n\n constructor(hash: string) {\n super(\n `smelt: no stored content for hash \"${hash}\". It was never elided, or the store was reset.`,\n );\n }\n}\n\n/**\n * A planner was handed a `PlanInput` without `pricing`. The type makes `pricing`\n * required, so TypeScript callers cannot get here; a JS caller can, and the honest\n * answer is this error rather than a guessed marker cost — a planner pricing markers\n * itself is exactly the inversion the MarkerPricing seam removed.\n */\nexport class MissingMarkerPricingError extends SmeltError {\n override readonly name = 'MissingMarkerPricingError';\n\n constructor(plannerId: string) {\n super(\n `smelt: ${plannerId} was handed a PlanInput without \\`pricing\\`. A planner never ` +\n `guesses what a marker costs — the applier renders markers, so the applier ` +\n `prices them. Build one with markerPricing(language, marker) from apply.ts and ` +\n `put it on the input; createSmelter and the CLI construct it centrally.`,\n );\n }\n}\n\n/**\n * The CLI was invoked wrongly — a missing `--budget`, an unknown flag, a budget that\n * is not a number. Distinct from every other `SmeltError` so the CLI can exit with a\n * usage code rather than pretending the library refused.\n */\nexport class CliUsageError extends SmeltError {\n override readonly name = 'CliUsageError';\n}\n\n/**\n * Two different blobs hashed to the same key. Astronomically unlikely, and yet: the\n * alternative to throwing is handing the model the wrong bytes and calling it a\n * retrieval, which is precisely the silent failure this library exists to avoid.\n */\nexport class HashCollisionError extends SmeltError {\n override readonly name = 'HashCollisionError';\n\n constructor(hash: string) {\n super(\n `smelt: hash collision on \"${hash}\" — two different blobs share a key. Refusing to ` +\n `store, because retrieving would return the wrong bytes. Please report this.`,\n );\n }\n}\n\n/**\n * A persistent store holds bytes under this hash, but they no longer hash to it — a\n * torn write, a truncation, an edit behind the store's back. Deliberately distinct from\n * {@link UnknownHashError}: \"we hold damaged bytes\" and \"never existed\" call for\n * different responses, and returning the damaged bytes as a retrieval would be the\n * silent wrong answer this library exists to refuse.\n */\nexport class StoreCorruptionError extends SmeltError {\n override readonly name = 'StoreCorruptionError';\n\n constructor(hash: string) {\n super(\n `smelt: the bytes stored under hash \"${hash}\" do not hash to \"${hash}\". Refusing ` +\n `to return them — they are damaged, not merely unknown. The store directory was ` +\n `truncated or edited outside smelt.`,\n );\n }\n}\n\n/**\n * A directory offered as a persistent store carries a format marker this version of\n * smelt does not understand — or no parseable marker at all. Refusing beats guessing:\n * reinterpreting an unknown layout could hand back the wrong bytes with no error.\n */\nexport class StoreFormatError extends SmeltError {\n override readonly name = 'StoreFormatError';\n}\n\n/**\n * A filesystem call the repo map made failed: a root that is not there, a directory\n * that cannot be listed, a file that cannot be read, a cache entry that cannot be\n * written.\n *\n * It exists because of a hole in the one promise the consumer contract makes about\n * errors — **every error smelt throws is an `instanceof SmeltError`**.\n * `buildRepoMap({ root: '/nonexistent' })` used to throw the raw `ENOENT` that\n * `readdirSync` raises, so a caller doing precisely what the documentation says still\n * had a bare `Error` escaping past its `catch`. The guarantee is worth nothing with an\n * exception to it, so every `node:fs` call under `src/repomap/` now arrives here.\n *\n * The message names the path, because \"ENOENT\" without one is a bug report nobody can\n * act on, and the original error is kept as `cause`: wrapping is meant to bring the\n * failure inside the contract, never to hide what Node said.\n */\nexport class RepoMapIoError extends SmeltError {\n override readonly name = 'RepoMapIoError';\n\n constructor(operation: string, path: string, cause: unknown) {\n super(\n `smelt: could not ${operation} \"${path}\" while building the repo map: ` +\n `${describeIoCause(cause)}.`,\n { cause },\n );\n }\n}\n\n/** The `errno` code when Node supplied one, its message otherwise. Never invented. */\nfunction describeIoCause(cause: unknown): string {\n const code = (cause as { code?: string } | null | undefined)?.code;\n if (typeof code === 'string' && code !== '') return code;\n if (cause instanceof Error && cause.message !== '') return cause.message;\n return String(cause);\n}\n"]}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACnC,SAAkB,IAAI,EAAE,MAAM,CAAgB;CAC/C;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,UAAU;IACjD,SAAkB,IAAI,yBAAyB;IAE/C,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAKxC;CACF;AAED;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,UAAU;IAChD,SAAkB,IAAI,wBAAwB;IAE9C,YAAY,OAAO,EAAE,MAAM,EAE1B;CACF;AAED,0FAA0F;AAC1F,qBAAa,uBAAwB,SAAQ,UAAU;IACrD,SAAkB,IAAI,6BAA6B;CACpD;AAED,mEAAmE;AACnE,qBAAa,qBAAsB,SAAQ,UAAU;IACnD,SAAkB,IAAI,2BAA2B;CAClD;AAED,uEAAuE;AACvE,qBAAa,uBAAwB,SAAQ,UAAU;IACrD,SAAkB,IAAI,6BAA6B;CACpD;AAED,uEAAuE;AACvE,qBAAa,gBAAiB,SAAQ,UAAU;IAC9C,SAAkB,IAAI,sBAAsB;IAE5C,YAAY,IAAI,EAAE,MAAM,EAIvB;CACF;AAED;;;;;GAKG;AACH,qBAAa,yBAA0B,SAAQ,UAAU;IACvD,SAAkB,IAAI,+BAA+B;IAErD,YAAY,SAAS,EAAE,MAAM,EAO5B;CACF;AAED;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,UAAU;IAC3C,SAAkB,IAAI,mBAAmB;CAC1C;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,UAAU;IAChD,SAAkB,IAAI,wBAAwB;IAE9C,YAAY,IAAI,EAAE,MAAM,EAKvB;CACF;AAED;;;;;;GAMG;AACH,qBAAa,oBAAqB,SAAQ,UAAU;IAClD,SAAkB,IAAI,0BAA0B;IAEhD,YAAY,IAAI,EAAE,MAAM,EAMvB;CACF;AAED;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;IAC9C,SAAkB,IAAI,sBAAsB;CAC7C;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,cAAe,SAAQ,UAAU;IAC5C,SAAkB,IAAI,oBAAoB;IAE1C,YAAY,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAM1D;CACF;AAUD;;;;;;GAMG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;IAC9C,SAAkB,IAAI,sBAAsB;CAC7C","sourcesContent":["/**\n * Every error smelt throws is one of these. Consumers can `instanceof SmeltError`\n * to tell \"the library said no\" apart from \"something else blew up\".\n */\nexport class SmeltError extends Error {\n override readonly name: string = 'SmeltError';\n}\n\n/**\n * A scaffold stub. It throws instead of returning a plausible-looking wrong answer,\n * because a context optimizer that quietly returns bad output is indistinguishable\n * from one that works. See CONTRIBUTING.md § \"Silence is the enemy\".\n */\nexport class NotImplementedError extends SmeltError {\n override readonly name = 'NotImplementedError';\n\n constructor(what: string, seeAlso: string) {\n super(\n `smelt: ${what} is not implemented yet. This is a scaffold stub — it throws rather ` +\n `than returning a plausible wrong answer. See ${seeAlso}.`,\n );\n }\n}\n\n/**\n * Thrown when something in the elision path tried to reach a non-local resource.\n * v1 has no network. See docs/ARCHITECTURE.md § \"Law 1 — zero network\".\n */\nexport class NetworkPolicyError extends SmeltError {\n override readonly name = 'NetworkPolicyError';\n\n constructor(message: string) {\n super(`smelt: ${message}`);\n }\n}\n\n/** A plan asked to elide two overlapping ranges. Applying it would corrupt the output. */\nexport class OverlappingElisionError extends SmeltError {\n override readonly name = 'OverlappingElisionError';\n}\n\n/** A plan referenced a byte range that is not inside the input. */\nexport class RangeOutOfBoundsError extends SmeltError {\n override readonly name = 'RangeOutOfBoundsError';\n}\n\n/** A grammar was requested that is not installed or not registered. */\nexport class GrammarUnavailableError extends SmeltError {\n override readonly name = 'GrammarUnavailableError';\n}\n\n/** `retrieve(hash)` was called with a hash the store does not hold. */\nexport class UnknownHashError extends SmeltError {\n override readonly name = 'UnknownHashError';\n\n constructor(hash: string) {\n super(\n `smelt: no stored content for hash \"${hash}\". It was never elided, or the store was reset.`,\n );\n }\n}\n\n/**\n * A planner was handed a `PlanInput` without `pricing`. The type makes `pricing`\n * required, so TypeScript callers cannot get here; a JS caller can, and the honest\n * answer is this error rather than a guessed marker cost — a planner pricing markers\n * itself is exactly the inversion the MarkerPricing seam removed.\n */\nexport class MissingMarkerPricingError extends SmeltError {\n override readonly name = 'MissingMarkerPricingError';\n\n constructor(plannerId: string) {\n super(\n `smelt: ${plannerId} was handed a PlanInput without \\`pricing\\`. A planner never ` +\n `guesses what a marker costs — the applier renders markers, so the applier ` +\n `prices them. Build one with markerPricing(language, marker) from apply.ts and ` +\n `put it on the input; createSmelter and the CLI construct it centrally.`,\n );\n }\n}\n\n/**\n * The CLI was invoked wrongly — a missing `--budget`, an unknown flag, a budget that\n * is not a number. Distinct from every other `SmeltError` so the CLI can exit with a\n * usage code rather than pretending the library refused.\n */\nexport class CliUsageError extends SmeltError {\n override readonly name = 'CliUsageError';\n}\n\n/**\n * Two different blobs hashed to the same key. Astronomically unlikely, and yet: the\n * alternative to throwing is handing the model the wrong bytes and calling it a\n * retrieval, which is precisely the silent failure this library exists to avoid.\n */\nexport class HashCollisionError extends SmeltError {\n override readonly name = 'HashCollisionError';\n\n constructor(hash: string) {\n super(\n `smelt: hash collision on \"${hash}\" — two different blobs share a key. Refusing to ` +\n `store, because retrieving would return the wrong bytes. Please report this.`,\n );\n }\n}\n\n/**\n * A persistent store holds bytes under this hash, but they no longer hash to it — a\n * torn write, a truncation, an edit behind the store's back. Deliberately distinct from\n * {@link UnknownHashError}: \"we hold damaged bytes\" and \"never existed\" call for\n * different responses, and returning the damaged bytes as a retrieval would be the\n * silent wrong answer this library exists to refuse.\n */\nexport class StoreCorruptionError extends SmeltError {\n override readonly name = 'StoreCorruptionError';\n\n constructor(hash: string) {\n super(\n `smelt: the bytes stored under hash \"${hash}\" do not hash to \"${hash}\". Refusing ` +\n `to return them — they are damaged, not merely unknown. The store directory was ` +\n `truncated or edited outside smelt.`,\n );\n }\n}\n\n/**\n * A directory offered as a persistent store carries a format marker this version of\n * smelt does not understand — or no parseable marker at all. Refusing beats guessing:\n * reinterpreting an unknown layout could hand back the wrong bytes with no error.\n */\nexport class StoreFormatError extends SmeltError {\n override readonly name = 'StoreFormatError';\n}\n\n/**\n * A filesystem call the repo map made failed: a root that is not there, a directory\n * that cannot be listed, a file that cannot be read, a cache entry that cannot be\n * written.\n *\n * It exists because of a hole in the one promise the consumer contract makes about\n * errors — **every error smelt throws is an `instanceof SmeltError`**.\n * `buildRepoMap({ root: '/nonexistent' })` used to throw the raw `ENOENT` that\n * `readdirSync` raises, so a caller doing precisely what the documentation says still\n * had a bare `Error` escaping past its `catch`. The guarantee is worth nothing with an\n * exception to it, so every `node:fs` call under `src/repomap/` now arrives here.\n *\n * The message names the path, because \"ENOENT\" without one is a bug report nobody can\n * act on, and the original error is kept as `cause`: wrapping is meant to bring the\n * failure inside the contract, never to hide what Node said.\n */\nexport class RepoMapIoError extends SmeltError {\n override readonly name = 'RepoMapIoError';\n\n constructor(operation: string, path: string, cause: unknown) {\n super(\n `smelt: could not ${operation} \"${path}\" while building the repo map: ` +\n `${describeIoCause(cause)}.`,\n { cause },\n );\n }\n}\n\n/** The `errno` code when Node supplied one, its message otherwise. Never invented. */\nfunction describeIoCause(cause: unknown): string {\n const code = (cause as { code?: string } | null | undefined)?.code;\n if (typeof code === 'string' && code !== '') return code;\n if (cause instanceof Error && cause.message !== '') return cause.message;\n return String(cause);\n}\n\n/**\n * A content-kind planner (`json`, `diff`) was asked to plan text that is not that kind.\n * It refuses rather than approximating, for the reason the structural planner refuses\n * an unknown language: output labelled `json/v1` that is really line windows would be\n * undetectable from outside. `auto` never raises this — it probes the kind first and\n * routes only what the probe proved.\n */\nexport class ContentKindError extends SmeltError {\n override readonly name = 'ContentKindError';\n}\n"]}
package/dist/errors.js CHANGED
@@ -136,4 +136,14 @@ function describeIoCause(cause) {
136
136
  return cause.message;
137
137
  return String(cause);
138
138
  }
139
+ /**
140
+ * A content-kind planner (`json`, `diff`) was asked to plan text that is not that kind.
141
+ * It refuses rather than approximating, for the reason the structural planner refuses
142
+ * an unknown language: output labelled `json/v1` that is really line windows would be
143
+ * undetectable from outside. `auto` never raises this — it probes the kind first and
144
+ * routes only what the probe proved.
145
+ */
146
+ export class ContentKindError extends SmeltError {
147
+ name = 'ContentKindError';
148
+ }
139
149
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Every error smelt throws is one of these. Consumers can `instanceof SmeltError`\n * to tell \"the library said no\" apart from \"something else blew up\".\n */\nexport class SmeltError extends Error {\n override readonly name: string = 'SmeltError';\n}\n\n/**\n * A scaffold stub. It throws instead of returning a plausible-looking wrong answer,\n * because a context optimizer that quietly returns bad output is indistinguishable\n * from one that works. See CONTRIBUTING.md § \"Silence is the enemy\".\n */\nexport class NotImplementedError extends SmeltError {\n override readonly name = 'NotImplementedError';\n\n constructor(what: string, seeAlso: string) {\n super(\n `smelt: ${what} is not implemented yet. This is a scaffold stub — it throws rather ` +\n `than returning a plausible wrong answer. See ${seeAlso}.`,\n );\n }\n}\n\n/**\n * Thrown when something in the elision path tried to reach a non-local resource.\n * v1 has no network. See docs/ARCHITECTURE.md § \"Law 1 — zero network\".\n */\nexport class NetworkPolicyError extends SmeltError {\n override readonly name = 'NetworkPolicyError';\n\n constructor(message: string) {\n super(`smelt: ${message}`);\n }\n}\n\n/** A plan asked to elide two overlapping ranges. Applying it would corrupt the output. */\nexport class OverlappingElisionError extends SmeltError {\n override readonly name = 'OverlappingElisionError';\n}\n\n/** A plan referenced a byte range that is not inside the input. */\nexport class RangeOutOfBoundsError extends SmeltError {\n override readonly name = 'RangeOutOfBoundsError';\n}\n\n/** A grammar was requested that is not installed or not registered. */\nexport class GrammarUnavailableError extends SmeltError {\n override readonly name = 'GrammarUnavailableError';\n}\n\n/** `retrieve(hash)` was called with a hash the store does not hold. */\nexport class UnknownHashError extends SmeltError {\n override readonly name = 'UnknownHashError';\n\n constructor(hash: string) {\n super(\n `smelt: no stored content for hash \"${hash}\". It was never elided, or the store was reset.`,\n );\n }\n}\n\n/**\n * A planner was handed a `PlanInput` without `pricing`. The type makes `pricing`\n * required, so TypeScript callers cannot get here; a JS caller can, and the honest\n * answer is this error rather than a guessed marker cost — a planner pricing markers\n * itself is exactly the inversion the MarkerPricing seam removed.\n */\nexport class MissingMarkerPricingError extends SmeltError {\n override readonly name = 'MissingMarkerPricingError';\n\n constructor(plannerId: string) {\n super(\n `smelt: ${plannerId} was handed a PlanInput without \\`pricing\\`. A planner never ` +\n `guesses what a marker costs — the applier renders markers, so the applier ` +\n `prices them. Build one with markerPricing(language, marker) from apply.ts and ` +\n `put it on the input; createSmelter and the CLI construct it centrally.`,\n );\n }\n}\n\n/**\n * The CLI was invoked wrongly — a missing `--budget`, an unknown flag, a budget that\n * is not a number. Distinct from every other `SmeltError` so the CLI can exit with a\n * usage code rather than pretending the library refused.\n */\nexport class CliUsageError extends SmeltError {\n override readonly name = 'CliUsageError';\n}\n\n/**\n * Two different blobs hashed to the same key. Astronomically unlikely, and yet: the\n * alternative to throwing is handing the model the wrong bytes and calling it a\n * retrieval, which is precisely the silent failure this library exists to avoid.\n */\nexport class HashCollisionError extends SmeltError {\n override readonly name = 'HashCollisionError';\n\n constructor(hash: string) {\n super(\n `smelt: hash collision on \"${hash}\" — two different blobs share a key. Refusing to ` +\n `store, because retrieving would return the wrong bytes. Please report this.`,\n );\n }\n}\n\n/**\n * A persistent store holds bytes under this hash, but they no longer hash to it — a\n * torn write, a truncation, an edit behind the store's back. Deliberately distinct from\n * {@link UnknownHashError}: \"we hold damaged bytes\" and \"never existed\" call for\n * different responses, and returning the damaged bytes as a retrieval would be the\n * silent wrong answer this library exists to refuse.\n */\nexport class StoreCorruptionError extends SmeltError {\n override readonly name = 'StoreCorruptionError';\n\n constructor(hash: string) {\n super(\n `smelt: the bytes stored under hash \"${hash}\" do not hash to \"${hash}\". Refusing ` +\n `to return them — they are damaged, not merely unknown. The store directory was ` +\n `truncated or edited outside smelt.`,\n );\n }\n}\n\n/**\n * A directory offered as a persistent store carries a format marker this version of\n * smelt does not understand — or no parseable marker at all. Refusing beats guessing:\n * reinterpreting an unknown layout could hand back the wrong bytes with no error.\n */\nexport class StoreFormatError extends SmeltError {\n override readonly name = 'StoreFormatError';\n}\n\n/**\n * A filesystem call the repo map made failed: a root that is not there, a directory\n * that cannot be listed, a file that cannot be read, a cache entry that cannot be\n * written.\n *\n * It exists because of a hole in the one promise the consumer contract makes about\n * errors — **every error smelt throws is an `instanceof SmeltError`**.\n * `buildRepoMap({ root: '/nonexistent' })` used to throw the raw `ENOENT` that\n * `readdirSync` raises, so a caller doing precisely what the documentation says still\n * had a bare `Error` escaping past its `catch`. The guarantee is worth nothing with an\n * exception to it, so every `node:fs` call under `src/repomap/` now arrives here.\n *\n * The message names the path, because \"ENOENT\" without one is a bug report nobody can\n * act on, and the original error is kept as `cause`: wrapping is meant to bring the\n * failure inside the contract, never to hide what Node said.\n */\nexport class RepoMapIoError extends SmeltError {\n override readonly name = 'RepoMapIoError';\n\n constructor(operation: string, path: string, cause: unknown) {\n super(\n `smelt: could not ${operation} \"${path}\" while building the repo map: ` +\n `${describeIoCause(cause)}.`,\n { cause },\n );\n }\n}\n\n/** The `errno` code when Node supplied one, its message otherwise. Never invented. */\nfunction describeIoCause(cause: unknown): string {\n const code = (cause as { code?: string } | null | undefined)?.code;\n if (typeof code === 'string' && code !== '') return code;\n if (cause instanceof Error && cause.message !== '') return cause.message;\n return String(cause);\n}\n"]}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Every error smelt throws is one of these. Consumers can `instanceof SmeltError`\n * to tell \"the library said no\" apart from \"something else blew up\".\n */\nexport class SmeltError extends Error {\n override readonly name: string = 'SmeltError';\n}\n\n/**\n * A scaffold stub. It throws instead of returning a plausible-looking wrong answer,\n * because a context optimizer that quietly returns bad output is indistinguishable\n * from one that works. See CONTRIBUTING.md § \"Silence is the enemy\".\n */\nexport class NotImplementedError extends SmeltError {\n override readonly name = 'NotImplementedError';\n\n constructor(what: string, seeAlso: string) {\n super(\n `smelt: ${what} is not implemented yet. This is a scaffold stub — it throws rather ` +\n `than returning a plausible wrong answer. See ${seeAlso}.`,\n );\n }\n}\n\n/**\n * Thrown when something in the elision path tried to reach a non-local resource.\n * v1 has no network. See docs/ARCHITECTURE.md § \"Law 1 — zero network\".\n */\nexport class NetworkPolicyError extends SmeltError {\n override readonly name = 'NetworkPolicyError';\n\n constructor(message: string) {\n super(`smelt: ${message}`);\n }\n}\n\n/** A plan asked to elide two overlapping ranges. Applying it would corrupt the output. */\nexport class OverlappingElisionError extends SmeltError {\n override readonly name = 'OverlappingElisionError';\n}\n\n/** A plan referenced a byte range that is not inside the input. */\nexport class RangeOutOfBoundsError extends SmeltError {\n override readonly name = 'RangeOutOfBoundsError';\n}\n\n/** A grammar was requested that is not installed or not registered. */\nexport class GrammarUnavailableError extends SmeltError {\n override readonly name = 'GrammarUnavailableError';\n}\n\n/** `retrieve(hash)` was called with a hash the store does not hold. */\nexport class UnknownHashError extends SmeltError {\n override readonly name = 'UnknownHashError';\n\n constructor(hash: string) {\n super(\n `smelt: no stored content for hash \"${hash}\". It was never elided, or the store was reset.`,\n );\n }\n}\n\n/**\n * A planner was handed a `PlanInput` without `pricing`. The type makes `pricing`\n * required, so TypeScript callers cannot get here; a JS caller can, and the honest\n * answer is this error rather than a guessed marker cost — a planner pricing markers\n * itself is exactly the inversion the MarkerPricing seam removed.\n */\nexport class MissingMarkerPricingError extends SmeltError {\n override readonly name = 'MissingMarkerPricingError';\n\n constructor(plannerId: string) {\n super(\n `smelt: ${plannerId} was handed a PlanInput without \\`pricing\\`. A planner never ` +\n `guesses what a marker costs — the applier renders markers, so the applier ` +\n `prices them. Build one with markerPricing(language, marker) from apply.ts and ` +\n `put it on the input; createSmelter and the CLI construct it centrally.`,\n );\n }\n}\n\n/**\n * The CLI was invoked wrongly — a missing `--budget`, an unknown flag, a budget that\n * is not a number. Distinct from every other `SmeltError` so the CLI can exit with a\n * usage code rather than pretending the library refused.\n */\nexport class CliUsageError extends SmeltError {\n override readonly name = 'CliUsageError';\n}\n\n/**\n * Two different blobs hashed to the same key. Astronomically unlikely, and yet: the\n * alternative to throwing is handing the model the wrong bytes and calling it a\n * retrieval, which is precisely the silent failure this library exists to avoid.\n */\nexport class HashCollisionError extends SmeltError {\n override readonly name = 'HashCollisionError';\n\n constructor(hash: string) {\n super(\n `smelt: hash collision on \"${hash}\" — two different blobs share a key. Refusing to ` +\n `store, because retrieving would return the wrong bytes. Please report this.`,\n );\n }\n}\n\n/**\n * A persistent store holds bytes under this hash, but they no longer hash to it — a\n * torn write, a truncation, an edit behind the store's back. Deliberately distinct from\n * {@link UnknownHashError}: \"we hold damaged bytes\" and \"never existed\" call for\n * different responses, and returning the damaged bytes as a retrieval would be the\n * silent wrong answer this library exists to refuse.\n */\nexport class StoreCorruptionError extends SmeltError {\n override readonly name = 'StoreCorruptionError';\n\n constructor(hash: string) {\n super(\n `smelt: the bytes stored under hash \"${hash}\" do not hash to \"${hash}\". Refusing ` +\n `to return them — they are damaged, not merely unknown. The store directory was ` +\n `truncated or edited outside smelt.`,\n );\n }\n}\n\n/**\n * A directory offered as a persistent store carries a format marker this version of\n * smelt does not understand — or no parseable marker at all. Refusing beats guessing:\n * reinterpreting an unknown layout could hand back the wrong bytes with no error.\n */\nexport class StoreFormatError extends SmeltError {\n override readonly name = 'StoreFormatError';\n}\n\n/**\n * A filesystem call the repo map made failed: a root that is not there, a directory\n * that cannot be listed, a file that cannot be read, a cache entry that cannot be\n * written.\n *\n * It exists because of a hole in the one promise the consumer contract makes about\n * errors — **every error smelt throws is an `instanceof SmeltError`**.\n * `buildRepoMap({ root: '/nonexistent' })` used to throw the raw `ENOENT` that\n * `readdirSync` raises, so a caller doing precisely what the documentation says still\n * had a bare `Error` escaping past its `catch`. The guarantee is worth nothing with an\n * exception to it, so every `node:fs` call under `src/repomap/` now arrives here.\n *\n * The message names the path, because \"ENOENT\" without one is a bug report nobody can\n * act on, and the original error is kept as `cause`: wrapping is meant to bring the\n * failure inside the contract, never to hide what Node said.\n */\nexport class RepoMapIoError extends SmeltError {\n override readonly name = 'RepoMapIoError';\n\n constructor(operation: string, path: string, cause: unknown) {\n super(\n `smelt: could not ${operation} \"${path}\" while building the repo map: ` +\n `${describeIoCause(cause)}.`,\n { cause },\n );\n }\n}\n\n/** The `errno` code when Node supplied one, its message otherwise. Never invented. */\nfunction describeIoCause(cause: unknown): string {\n const code = (cause as { code?: string } | null | undefined)?.code;\n if (typeof code === 'string' && code !== '') return code;\n if (cause instanceof Error && cause.message !== '') return cause.message;\n return String(cause);\n}\n\n/**\n * A content-kind planner (`json`, `diff`) was asked to plan text that is not that kind.\n * It refuses rather than approximating, for the reason the structural planner refuses\n * an unknown language: output labelled `json/v1` that is really line windows would be\n * undetectable from outside. `auto` never raises this — it probes the kind first and\n * routes only what the probe proved.\n */\nexport class ContentKindError extends SmeltError {\n override readonly name = 'ContentKindError';\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"claude-code.d.ts","sourceRoot":"","sources":["../../src/harness/claude-code.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAiD1D,eAAO,MAAM,UAAU,EAAE,qBAoBxB,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport type { ShimmedHarnessProfile } from './profile.ts';\n\n/**\n * Claude Code — VERIFIED tier. Schema, install and removal, end to end.\n *\n * Schema per <https://code.claude.com/docs/en/hooks> (verified 2026-09-02; the deep\n * dive is docs/research/2026-09-02-agent-enforcement.md § 1):\n *\n * - stdin: `{ hook_event_name: \"PreToolUse\", tool_name, tool_input, cwd }`. For\n * `Read`, `tool_input.file_path` is already absolute and `offset`/`limit` mark a\n * windowed read; for `Bash`, `tool_input.command` is the full command string, and\n * a relative path in it resolves against the payload's `cwd` — the *session's*\n * working directory, which after the model `cd`s differs from the hook process's\n * own cwd.\n * - deny: `hookSpecificOutput.permissionDecision: \"deny\"` with\n * `permissionDecisionReason` — which is **shown to the model**, so the guard's\n * reason (the exact replacement command, the `smelt retrieve` contract) lands in\n * the transcript as steering.\n * - rewrite (opt-in, `hooks.enforcement: \"rewrite\"`): `updatedInput` replaces the\n * entire input object of the *same* tool (v2.0.10+), so a Bash command can be\n * substituted but a Read can never become a Bash call — Reads deny in every mode.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['Read'],\n bashTools: ['Bash'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['tool_input'],\n cwdKey: 'cwd',\n deny: (reason) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: reason,\n },\n }),\n rewrite: {\n document: ({ input, announcement }) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'allow',\n permissionDecisionReason: announcement,\n // updatedInput replaces the whole input object — unchanged fields ride along.\n updatedInput: input,\n },\n }),\n announce: 'reason',\n },\n};\n\nexport const claudeCode: ShimmedHarnessProfile = {\n id: 'claude-code',\n name: 'Claude Code',\n tier: 'verified',\n detect: ['.claude'],\n detectHome: ['.claude'],\n instructionFile: 'CLAUDE.md',\n instructions: 'snippet',\n caveats: [],\n hooks: HOOKS,\n install: [\n {\n kind: 'json-hooks',\n file: '.claude/settings.json',\n event: 'PreToolUse',\n matchers: ['Read', 'Bash'],\n entry: 'command-list',\n lifecycle: true,\n },\n ],\n};\n"]}
1
+ {"version":3,"file":"claude-code.d.ts","sourceRoot":"","sources":["../../src/harness/claude-code.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAqD1D,eAAO,MAAM,UAAU,EAAE,qBA0BxB,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport { MCP_RUN_ARGS } from '../setup/recipe.ts';\nimport type { ShimmedHarnessProfile } from './profile.ts';\n\n/**\n * Claude Code — VERIFIED tier. Schema, install and removal, end to end.\n *\n * Schema per <https://code.claude.com/docs/en/hooks> (verified 2026-09-02; the deep\n * dive is docs/research/2026-09-02-agent-enforcement.md § 1):\n *\n * - stdin: `{ hook_event_name: \"PreToolUse\", tool_name, tool_input, cwd }`. For\n * `Read`, `tool_input.file_path` is already absolute and `offset`/`limit` mark a\n * windowed read; for `Bash`, `tool_input.command` is the full command string, and\n * a relative path in it resolves against the payload's `cwd` — the *session's*\n * working directory, which after the model `cd`s differs from the hook process's\n * own cwd.\n * - deny: `hookSpecificOutput.permissionDecision: \"deny\"` with\n * `permissionDecisionReason` — which is **shown to the model**, so the guard's\n * reason (the exact replacement command, the `smelt retrieve` contract) lands in\n * the transcript as steering.\n * - rewrite (opt-in, `hooks.enforcement: \"rewrite\"`): `updatedInput` replaces the\n * entire input object of the *same* tool (v2.0.10+), so a Bash command can be\n * substituted but a Read can never become a Bash call — Reads deny in every mode.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['Read'],\n bashTools: ['Bash'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['tool_input'],\n cwdKey: 'cwd',\n deny: (reason) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: reason,\n },\n }),\n rewrite: {\n document: ({ input, announcement }) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'allow',\n permissionDecisionReason: announcement,\n // updatedInput replaces the whole input object — unchanged fields ride along.\n updatedInput: input,\n },\n }),\n announce: 'reason',\n },\n};\n\n// The registration's bytes come from the SetupRecipe's run command (MCP_RUN_ARGS,\n// derived once beside the fact) — so the CLI command the README teaches and the file\n// setup writes cannot disagree.\n\nexport const claudeCode: ShimmedHarnessProfile = {\n id: 'claude-code',\n name: 'Claude Code',\n tier: 'verified',\n detect: ['.claude'],\n detectHome: ['.claude'],\n instructionFile: 'CLAUDE.md',\n instructions: 'snippet',\n caveats: [],\n hooks: HOOKS,\n install: [\n {\n kind: 'json-hooks',\n file: '.claude/settings.json',\n event: 'PreToolUse',\n matchers: ['Read', 'Bash'],\n entry: 'command-list',\n lifecycle: true,\n },\n {\n kind: 'mcp-registration',\n file: '.mcp.json',\n path: ['mcpServers', 'smelt'],\n entry: () => ({ command: MCP_RUN_ARGS[0], args: MCP_RUN_ARGS.slice(1) }),\n },\n ],\n};\n"]}
@@ -1,3 +1,4 @@
1
+ import { MCP_RUN_ARGS } from '../setup/recipe.js';
1
2
  /**
2
3
  * Claude Code — VERIFIED tier. Schema, install and removal, end to end.
3
4
  *
@@ -44,6 +45,9 @@ const HOOKS = {
44
45
  announce: 'reason',
45
46
  },
46
47
  };
48
+ // The registration's bytes come from the SetupRecipe's run command (MCP_RUN_ARGS,
49
+ // derived once beside the fact) — so the CLI command the README teaches and the file
50
+ // setup writes cannot disagree.
47
51
  export const claudeCode = {
48
52
  id: 'claude-code',
49
53
  name: 'Claude Code',
@@ -63,6 +67,12 @@ export const claudeCode = {
63
67
  entry: 'command-list',
64
68
  lifecycle: true,
65
69
  },
70
+ {
71
+ kind: 'mcp-registration',
72
+ file: '.mcp.json',
73
+ path: ['mcpServers', 'smelt'],
74
+ entry: () => ({ command: MCP_RUN_ARGS[0], args: MCP_RUN_ARGS.slice(1) }),
75
+ },
66
76
  ],
67
77
  };
68
78
  //# sourceMappingURL=claude-code.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/harness/claude-code.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,KAAK,GAAsB;IAC/B,SAAS,EAAE,CAAC,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,MAAM,CAAC;IACnB,YAAY,EAAE,CAAC,WAAW,CAAC;IAC3B,aAAa,EAAE,CAAC,YAAY,CAAC;IAC7B,MAAM,EAAE,KAAK;IACb,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACjB,kBAAkB,EAAE;YAClB,aAAa,EAAE,YAAY;YAC3B,kBAAkB,EAAE,MAAM;YAC1B,wBAAwB,EAAE,MAAM;SACjC;KACF,CAAC;IACF,OAAO,EAAE;QACP,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;YACtC,kBAAkB,EAAE;gBAClB,aAAa,EAAE,YAAY;gBAC3B,kBAAkB,EAAE,OAAO;gBAC3B,wBAAwB,EAAE,YAAY;gBACtC,8EAA8E;gBAC9E,YAAY,EAAE,KAAK;aACpB;SACF,CAAC;QACF,QAAQ,EAAE,QAAQ;KACnB;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAA0B;IAC/C,EAAE,EAAE,aAAa;IACjB,IAAI,EAAE,aAAa;IACnB,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,CAAC,SAAS,CAAC;IACnB,UAAU,EAAE,CAAC,SAAS,CAAC;IACvB,eAAe,EAAE,WAAW;IAC5B,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE,EAAE;IACX,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE;QACP;YACE,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,uBAAuB;YAC7B,KAAK,EAAE,YAAY;YACnB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;YAC1B,KAAK,EAAE,cAAc;YACrB,SAAS,EAAE,IAAI;SAChB;KACF;CACF,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport type { ShimmedHarnessProfile } from './profile.ts';\n\n/**\n * Claude Code — VERIFIED tier. Schema, install and removal, end to end.\n *\n * Schema per <https://code.claude.com/docs/en/hooks> (verified 2026-09-02; the deep\n * dive is docs/research/2026-09-02-agent-enforcement.md § 1):\n *\n * - stdin: `{ hook_event_name: \"PreToolUse\", tool_name, tool_input, cwd }`. For\n * `Read`, `tool_input.file_path` is already absolute and `offset`/`limit` mark a\n * windowed read; for `Bash`, `tool_input.command` is the full command string, and\n * a relative path in it resolves against the payload's `cwd` — the *session's*\n * working directory, which after the model `cd`s differs from the hook process's\n * own cwd.\n * - deny: `hookSpecificOutput.permissionDecision: \"deny\"` with\n * `permissionDecisionReason` — which is **shown to the model**, so the guard's\n * reason (the exact replacement command, the `smelt retrieve` contract) lands in\n * the transcript as steering.\n * - rewrite (opt-in, `hooks.enforcement: \"rewrite\"`): `updatedInput` replaces the\n * entire input object of the *same* tool (v2.0.10+), so a Bash command can be\n * substituted but a Read can never become a Bash call — Reads deny in every mode.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['Read'],\n bashTools: ['Bash'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['tool_input'],\n cwdKey: 'cwd',\n deny: (reason) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: reason,\n },\n }),\n rewrite: {\n document: ({ input, announcement }) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'allow',\n permissionDecisionReason: announcement,\n // updatedInput replaces the whole input object — unchanged fields ride along.\n updatedInput: input,\n },\n }),\n announce: 'reason',\n },\n};\n\nexport const claudeCode: ShimmedHarnessProfile = {\n id: 'claude-code',\n name: 'Claude Code',\n tier: 'verified',\n detect: ['.claude'],\n detectHome: ['.claude'],\n instructionFile: 'CLAUDE.md',\n instructions: 'snippet',\n caveats: [],\n hooks: HOOKS,\n install: [\n {\n kind: 'json-hooks',\n file: '.claude/settings.json',\n event: 'PreToolUse',\n matchers: ['Read', 'Bash'],\n entry: 'command-list',\n lifecycle: true,\n },\n ],\n};\n"]}
1
+ {"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/harness/claude-code.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,KAAK,GAAsB;IAC/B,SAAS,EAAE,CAAC,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,MAAM,CAAC;IACnB,YAAY,EAAE,CAAC,WAAW,CAAC;IAC3B,aAAa,EAAE,CAAC,YAAY,CAAC;IAC7B,MAAM,EAAE,KAAK;IACb,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACjB,kBAAkB,EAAE;YAClB,aAAa,EAAE,YAAY;YAC3B,kBAAkB,EAAE,MAAM;YAC1B,wBAAwB,EAAE,MAAM;SACjC;KACF,CAAC;IACF,OAAO,EAAE;QACP,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;YACtC,kBAAkB,EAAE;gBAClB,aAAa,EAAE,YAAY;gBAC3B,kBAAkB,EAAE,OAAO;gBAC3B,wBAAwB,EAAE,YAAY;gBACtC,8EAA8E;gBAC9E,YAAY,EAAE,KAAK;aACpB;SACF,CAAC;QACF,QAAQ,EAAE,QAAQ;KACnB;CACF,CAAC;AAEF,kFAAkF;AAClF,qFAAqF;AACrF,gCAAgC;AAEhC,MAAM,CAAC,MAAM,UAAU,GAA0B;IAC/C,EAAE,EAAE,aAAa;IACjB,IAAI,EAAE,aAAa;IACnB,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,CAAC,SAAS,CAAC;IACnB,UAAU,EAAE,CAAC,SAAS,CAAC;IACvB,eAAe,EAAE,WAAW;IAC5B,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE,EAAE;IACX,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE;QACP;YACE,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,uBAAuB;YAC7B,KAAK,EAAE,YAAY;YACnB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;YAC1B,KAAK,EAAE,cAAc;YACrB,SAAS,EAAE,IAAI;SAChB;QACD;YACE,IAAI,EAAE,kBAAkB;YACxB,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,CAAC,YAAY,EAAE,OAAO,CAAC;YAC7B,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;SACzE;KACF;CACF,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport { MCP_RUN_ARGS } from '../setup/recipe.ts';\nimport type { ShimmedHarnessProfile } from './profile.ts';\n\n/**\n * Claude Code — VERIFIED tier. Schema, install and removal, end to end.\n *\n * Schema per <https://code.claude.com/docs/en/hooks> (verified 2026-09-02; the deep\n * dive is docs/research/2026-09-02-agent-enforcement.md § 1):\n *\n * - stdin: `{ hook_event_name: \"PreToolUse\", tool_name, tool_input, cwd }`. For\n * `Read`, `tool_input.file_path` is already absolute and `offset`/`limit` mark a\n * windowed read; for `Bash`, `tool_input.command` is the full command string, and\n * a relative path in it resolves against the payload's `cwd` — the *session's*\n * working directory, which after the model `cd`s differs from the hook process's\n * own cwd.\n * - deny: `hookSpecificOutput.permissionDecision: \"deny\"` with\n * `permissionDecisionReason` — which is **shown to the model**, so the guard's\n * reason (the exact replacement command, the `smelt retrieve` contract) lands in\n * the transcript as steering.\n * - rewrite (opt-in, `hooks.enforcement: \"rewrite\"`): `updatedInput` replaces the\n * entire input object of the *same* tool (v2.0.10+), so a Bash command can be\n * substituted but a Read can never become a Bash call — Reads deny in every mode.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['Read'],\n bashTools: ['Bash'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['tool_input'],\n cwdKey: 'cwd',\n deny: (reason) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: reason,\n },\n }),\n rewrite: {\n document: ({ input, announcement }) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'allow',\n permissionDecisionReason: announcement,\n // updatedInput replaces the whole input object — unchanged fields ride along.\n updatedInput: input,\n },\n }),\n announce: 'reason',\n },\n};\n\n// The registration's bytes come from the SetupRecipe's run command (MCP_RUN_ARGS,\n// derived once beside the fact) — so the CLI command the README teaches and the file\n// setup writes cannot disagree.\n\nexport const claudeCode: ShimmedHarnessProfile = {\n id: 'claude-code',\n name: 'Claude Code',\n tier: 'verified',\n detect: ['.claude'],\n detectHome: ['.claude'],\n instructionFile: 'CLAUDE.md',\n instructions: 'snippet',\n caveats: [],\n hooks: HOOKS,\n install: [\n {\n kind: 'json-hooks',\n file: '.claude/settings.json',\n event: 'PreToolUse',\n matchers: ['Read', 'Bash'],\n entry: 'command-list',\n lifecycle: true,\n },\n {\n kind: 'mcp-registration',\n file: '.mcp.json',\n path: ['mcpServers', 'smelt'],\n entry: () => ({ command: MCP_RUN_ARGS[0], args: MCP_RUN_ARGS.slice(1) }),\n },\n ],\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/harness/codex.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AA4D1D,eAAO,MAAM,KAAK,EAAE,qBAkCnB,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport type { ShimmedHarnessProfile } from './profile.ts';\nimport { SNIPPET_END_HASH, SNIPPET_START_HASH } from './snippet.ts';\n\n/**\n * Codex CLI — VERIFIED tier. Schema, install and removal, end to end.\n *\n * Codex's experimental hooks (`features.hooks`, rust-v0.114.0+, on by default from\n * 0.150.1) deliberately mirror Claude Code's schema — the survey is\n * docs/research/2026-09-02-agent-enforcement.md § 3, primary source\n * <https://developers.openai.com/codex/hooks>:\n *\n * - stdin: `{ tool_name, tool_input }`; shell input arrives as `tool_name: \"Bash\"`,\n * `tool_input.command`.\n * - deny: `hookSpecificOutput.permissionDecision: \"deny\"` + reason (or exit 2).\n * - rewrite: `permissionDecision: \"allow\"` with `updatedInput` — for Bash,\n * `updatedInput` **must include a string `command` field**, which the shared splice\n * always does.\n *\n * Two documented differences from Claude Code, both honoured here:\n * `permissionDecision: \"ask\"` is NOT supported (a parse error — the tool proceeds),\n * and this schema never emits it; project-layer hooks only run once the project is\n * trusted, which the installer's output says out loud, and which the `[features]`\n * block below is what enables.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['Read'],\n bashTools: ['Bash', 'shell'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['tool_input'],\n deny: (reason) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: reason,\n },\n }),\n rewrite: {\n document: ({ input, announcement }) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'allow',\n permissionDecisionReason: announcement,\n updatedInput: input,\n },\n }),\n announce: 'reason',\n },\n};\n\n/** The Codex `config.toml` block: enables the hooks feature, marker-bracketed. */\nfunction codexConfigTomlBlock(): string {\n return `${SNIPPET_START_HASH}\n# Enables Codex's hooks feature so .codex/hooks.json is honored. Project-level hooks\n# run only once this project is trusted. Written by \\`smelt hooks install\\`.\n[features]\nhooks = true\n${SNIPPET_END_HASH}\n`;\n}\n\nexport const codex: ShimmedHarnessProfile = {\n id: 'codex',\n name: 'Codex CLI',\n shortName: 'Codex',\n tier: 'verified',\n detect: ['.codex'],\n detectHome: ['.codex'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: [\n 'project-level Codex hooks run only once the project is trusted (features.hooks; see docs/research/2026-09-02-agent-enforcement.md § 3)',\n ],\n hooks: HOOKS,\n install: [\n {\n kind: 'json-hooks',\n file: '.codex/hooks.json',\n event: 'PreToolUse',\n matchers: ['Read', 'Bash'],\n entry: 'command-list',\n lifecycle: true,\n },\n {\n kind: 'marker-block',\n file: '.codex/config.toml',\n block: codexConfigTomlBlock,\n start: SNIPPET_START_HASH,\n end: SNIPPET_END_HASH,\n skipWhen: {\n contains: '[features]',\n why: 'already has a [features] table — add `hooks = true` to it yourself',\n },\n },\n ],\n};\n"]}
1
+ {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/harness/codex.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AA4D1D,eAAO,MAAM,KAAK,EAAE,qBAmCnB,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport type { ShimmedHarnessProfile } from './profile.ts';\nimport { SNIPPET_END_HASH, SNIPPET_START_HASH } from './snippet.ts';\n\n/**\n * Codex CLI — VERIFIED tier. Schema, install and removal, end to end.\n *\n * Codex's experimental hooks (`features.hooks`, rust-v0.114.0+, on by default from\n * 0.150.1) deliberately mirror Claude Code's schema — the survey is\n * docs/research/2026-09-02-agent-enforcement.md § 3, primary source\n * <https://developers.openai.com/codex/hooks>:\n *\n * - stdin: `{ tool_name, tool_input }`; shell input arrives as `tool_name: \"Bash\"`,\n * `tool_input.command`.\n * - deny: `hookSpecificOutput.permissionDecision: \"deny\"` + reason (or exit 2).\n * - rewrite: `permissionDecision: \"allow\"` with `updatedInput` — for Bash,\n * `updatedInput` **must include a string `command` field**, which the shared splice\n * always does.\n *\n * Two documented differences from Claude Code, both honoured here:\n * `permissionDecision: \"ask\"` is NOT supported (a parse error — the tool proceeds),\n * and this schema never emits it; project-layer hooks only run once the project is\n * trusted, which the installer's output says out loud, and which the `[features]`\n * block below is what enables.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['Read'],\n bashTools: ['Bash', 'shell'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['tool_input'],\n deny: (reason) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: reason,\n },\n }),\n rewrite: {\n document: ({ input, announcement }) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'allow',\n permissionDecisionReason: announcement,\n updatedInput: input,\n },\n }),\n announce: 'reason',\n },\n};\n\n/** The Codex `config.toml` block: enables the hooks feature, marker-bracketed. */\nfunction codexConfigTomlBlock(): string {\n return `${SNIPPET_START_HASH}\n# Enables Codex's hooks feature so .codex/hooks.json is honored. Project-level hooks\n# run only once this project is trusted. Written by \\`smelt hooks install\\`.\n[features]\nhooks = true\n${SNIPPET_END_HASH}\n`;\n}\n\nexport const codex: ShimmedHarnessProfile = {\n id: 'codex',\n name: 'Codex CLI',\n shortName: 'Codex',\n tier: 'verified',\n detect: ['.codex'],\n detectHome: ['.codex'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: [\n 'project-level Codex hooks run only once the project is trusted (features.hooks; see docs/research/2026-09-02-agent-enforcement.md § 3)',\n 'MCP registration is manual for Codex (TOML) — packages/mcp/README.md has the snippet; setup does not edit TOML',\n ],\n hooks: HOOKS,\n install: [\n {\n kind: 'json-hooks',\n file: '.codex/hooks.json',\n event: 'PreToolUse',\n matchers: ['Read', 'Bash'],\n entry: 'command-list',\n lifecycle: true,\n },\n {\n kind: 'marker-block',\n file: '.codex/config.toml',\n block: codexConfigTomlBlock,\n start: SNIPPET_START_HASH,\n end: SNIPPET_END_HASH,\n skipWhen: {\n contains: '[features]',\n why: 'already has a [features] table — add `hooks = true` to it yourself',\n },\n },\n ],\n};\n"]}
@@ -65,6 +65,7 @@ export const codex = {
65
65
  instructions: 'snippet',
66
66
  caveats: [
67
67
  'project-level Codex hooks run only once the project is trusted (features.hooks; see docs/research/2026-09-02-agent-enforcement.md § 3)',
68
+ 'MCP registration is manual for Codex (TOML) — packages/mcp/README.md has the snippet; setup does not edit TOML',
68
69
  ],
69
70
  hooks: HOOKS,
70
71
  install: [
@@ -1 +1 @@
1
- {"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/harness/codex.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,KAAK,GAAsB;IAC/B,SAAS,EAAE,CAAC,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;IAC5B,YAAY,EAAE,CAAC,WAAW,CAAC;IAC3B,aAAa,EAAE,CAAC,YAAY,CAAC;IAC7B,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACjB,kBAAkB,EAAE;YAClB,aAAa,EAAE,YAAY;YAC3B,kBAAkB,EAAE,MAAM;YAC1B,wBAAwB,EAAE,MAAM;SACjC;KACF,CAAC;IACF,OAAO,EAAE;QACP,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;YACtC,kBAAkB,EAAE;gBAClB,aAAa,EAAE,YAAY;gBAC3B,kBAAkB,EAAE,OAAO;gBAC3B,wBAAwB,EAAE,YAAY;gBACtC,YAAY,EAAE,KAAK;aACpB;SACF,CAAC;QACF,QAAQ,EAAE,QAAQ;KACnB;CACF,CAAC;AAEF,kFAAkF;AAClF,SAAS,oBAAoB;IAC3B,OAAO,GAAG,kBAAkB;;;;;EAK5B,gBAAgB;CACjB,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAA0B;IAC1C,EAAE,EAAE,OAAO;IACX,IAAI,EAAE,WAAW;IACjB,SAAS,EAAE,OAAO;IAClB,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,CAAC,QAAQ,CAAC;IAClB,UAAU,EAAE,CAAC,QAAQ,CAAC;IACtB,eAAe,EAAE,WAAW;IAC5B,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE;QACP,wIAAwI;KACzI;IACD,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE;QACP;YACE,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,mBAAmB;YACzB,KAAK,EAAE,YAAY;YACnB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;YAC1B,KAAK,EAAE,cAAc;YACrB,SAAS,EAAE,IAAI;SAChB;QACD;YACE,IAAI,EAAE,cAAc;YACpB,IAAI,EAAE,oBAAoB;YAC1B,KAAK,EAAE,oBAAoB;YAC3B,KAAK,EAAE,kBAAkB;YACzB,GAAG,EAAE,gBAAgB;YACrB,QAAQ,EAAE;gBACR,QAAQ,EAAE,YAAY;gBACtB,GAAG,EAAE,oEAAoE;aAC1E;SACF;KACF;CACF,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport type { ShimmedHarnessProfile } from './profile.ts';\nimport { SNIPPET_END_HASH, SNIPPET_START_HASH } from './snippet.ts';\n\n/**\n * Codex CLI — VERIFIED tier. Schema, install and removal, end to end.\n *\n * Codex's experimental hooks (`features.hooks`, rust-v0.114.0+, on by default from\n * 0.150.1) deliberately mirror Claude Code's schema — the survey is\n * docs/research/2026-09-02-agent-enforcement.md § 3, primary source\n * <https://developers.openai.com/codex/hooks>:\n *\n * - stdin: `{ tool_name, tool_input }`; shell input arrives as `tool_name: \"Bash\"`,\n * `tool_input.command`.\n * - deny: `hookSpecificOutput.permissionDecision: \"deny\"` + reason (or exit 2).\n * - rewrite: `permissionDecision: \"allow\"` with `updatedInput` — for Bash,\n * `updatedInput` **must include a string `command` field**, which the shared splice\n * always does.\n *\n * Two documented differences from Claude Code, both honoured here:\n * `permissionDecision: \"ask\"` is NOT supported (a parse error — the tool proceeds),\n * and this schema never emits it; project-layer hooks only run once the project is\n * trusted, which the installer's output says out loud, and which the `[features]`\n * block below is what enables.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['Read'],\n bashTools: ['Bash', 'shell'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['tool_input'],\n deny: (reason) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: reason,\n },\n }),\n rewrite: {\n document: ({ input, announcement }) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'allow',\n permissionDecisionReason: announcement,\n updatedInput: input,\n },\n }),\n announce: 'reason',\n },\n};\n\n/** The Codex `config.toml` block: enables the hooks feature, marker-bracketed. */\nfunction codexConfigTomlBlock(): string {\n return `${SNIPPET_START_HASH}\n# Enables Codex's hooks feature so .codex/hooks.json is honored. Project-level hooks\n# run only once this project is trusted. Written by \\`smelt hooks install\\`.\n[features]\nhooks = true\n${SNIPPET_END_HASH}\n`;\n}\n\nexport const codex: ShimmedHarnessProfile = {\n id: 'codex',\n name: 'Codex CLI',\n shortName: 'Codex',\n tier: 'verified',\n detect: ['.codex'],\n detectHome: ['.codex'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: [\n 'project-level Codex hooks run only once the project is trusted (features.hooks; see docs/research/2026-09-02-agent-enforcement.md § 3)',\n ],\n hooks: HOOKS,\n install: [\n {\n kind: 'json-hooks',\n file: '.codex/hooks.json',\n event: 'PreToolUse',\n matchers: ['Read', 'Bash'],\n entry: 'command-list',\n lifecycle: true,\n },\n {\n kind: 'marker-block',\n file: '.codex/config.toml',\n block: codexConfigTomlBlock,\n start: SNIPPET_START_HASH,\n end: SNIPPET_END_HASH,\n skipWhen: {\n contains: '[features]',\n why: 'already has a [features] table — add `hooks = true` to it yourself',\n },\n },\n ],\n};\n"]}
1
+ {"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/harness/codex.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,KAAK,GAAsB;IAC/B,SAAS,EAAE,CAAC,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;IAC5B,YAAY,EAAE,CAAC,WAAW,CAAC;IAC3B,aAAa,EAAE,CAAC,YAAY,CAAC;IAC7B,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACjB,kBAAkB,EAAE;YAClB,aAAa,EAAE,YAAY;YAC3B,kBAAkB,EAAE,MAAM;YAC1B,wBAAwB,EAAE,MAAM;SACjC;KACF,CAAC;IACF,OAAO,EAAE;QACP,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;YACtC,kBAAkB,EAAE;gBAClB,aAAa,EAAE,YAAY;gBAC3B,kBAAkB,EAAE,OAAO;gBAC3B,wBAAwB,EAAE,YAAY;gBACtC,YAAY,EAAE,KAAK;aACpB;SACF,CAAC;QACF,QAAQ,EAAE,QAAQ;KACnB;CACF,CAAC;AAEF,kFAAkF;AAClF,SAAS,oBAAoB;IAC3B,OAAO,GAAG,kBAAkB;;;;;EAK5B,gBAAgB;CACjB,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAA0B;IAC1C,EAAE,EAAE,OAAO;IACX,IAAI,EAAE,WAAW;IACjB,SAAS,EAAE,OAAO;IAClB,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,CAAC,QAAQ,CAAC;IAClB,UAAU,EAAE,CAAC,QAAQ,CAAC;IACtB,eAAe,EAAE,WAAW;IAC5B,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE;QACP,wIAAwI;QACxI,gHAAgH;KACjH;IACD,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE;QACP;YACE,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,mBAAmB;YACzB,KAAK,EAAE,YAAY;YACnB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;YAC1B,KAAK,EAAE,cAAc;YACrB,SAAS,EAAE,IAAI;SAChB;QACD;YACE,IAAI,EAAE,cAAc;YACpB,IAAI,EAAE,oBAAoB;YAC1B,KAAK,EAAE,oBAAoB;YAC3B,KAAK,EAAE,kBAAkB;YACzB,GAAG,EAAE,gBAAgB;YACrB,QAAQ,EAAE;gBACR,QAAQ,EAAE,YAAY;gBACtB,GAAG,EAAE,oEAAoE;aAC1E;SACF;KACF;CACF,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport type { ShimmedHarnessProfile } from './profile.ts';\nimport { SNIPPET_END_HASH, SNIPPET_START_HASH } from './snippet.ts';\n\n/**\n * Codex CLI — VERIFIED tier. Schema, install and removal, end to end.\n *\n * Codex's experimental hooks (`features.hooks`, rust-v0.114.0+, on by default from\n * 0.150.1) deliberately mirror Claude Code's schema — the survey is\n * docs/research/2026-09-02-agent-enforcement.md § 3, primary source\n * <https://developers.openai.com/codex/hooks>:\n *\n * - stdin: `{ tool_name, tool_input }`; shell input arrives as `tool_name: \"Bash\"`,\n * `tool_input.command`.\n * - deny: `hookSpecificOutput.permissionDecision: \"deny\"` + reason (or exit 2).\n * - rewrite: `permissionDecision: \"allow\"` with `updatedInput` — for Bash,\n * `updatedInput` **must include a string `command` field**, which the shared splice\n * always does.\n *\n * Two documented differences from Claude Code, both honoured here:\n * `permissionDecision: \"ask\"` is NOT supported (a parse error — the tool proceeds),\n * and this schema never emits it; project-layer hooks only run once the project is\n * trusted, which the installer's output says out loud, and which the `[features]`\n * block below is what enables.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['Read'],\n bashTools: ['Bash', 'shell'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['tool_input'],\n deny: (reason) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: reason,\n },\n }),\n rewrite: {\n document: ({ input, announcement }) => ({\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'allow',\n permissionDecisionReason: announcement,\n updatedInput: input,\n },\n }),\n announce: 'reason',\n },\n};\n\n/** The Codex `config.toml` block: enables the hooks feature, marker-bracketed. */\nfunction codexConfigTomlBlock(): string {\n return `${SNIPPET_START_HASH}\n# Enables Codex's hooks feature so .codex/hooks.json is honored. Project-level hooks\n# run only once this project is trusted. Written by \\`smelt hooks install\\`.\n[features]\nhooks = true\n${SNIPPET_END_HASH}\n`;\n}\n\nexport const codex: ShimmedHarnessProfile = {\n id: 'codex',\n name: 'Codex CLI',\n shortName: 'Codex',\n tier: 'verified',\n detect: ['.codex'],\n detectHome: ['.codex'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: [\n 'project-level Codex hooks run only once the project is trusted (features.hooks; see docs/research/2026-09-02-agent-enforcement.md § 3)',\n 'MCP registration is manual for Codex (TOML) — packages/mcp/README.md has the snippet; setup does not edit TOML',\n ],\n hooks: HOOKS,\n install: [\n {\n kind: 'json-hooks',\n file: '.codex/hooks.json',\n event: 'PreToolUse',\n matchers: ['Read', 'Bash'],\n entry: 'command-list',\n lifecycle: true,\n },\n {\n kind: 'marker-block',\n file: '.codex/config.toml',\n block: codexConfigTomlBlock,\n start: SNIPPET_START_HASH,\n end: SNIPPET_END_HASH,\n skipWhen: {\n contains: '[features]',\n why: 'already has a [features] table — add `hooks = true` to it yourself',\n },\n },\n ],\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"grok.d.ts","sourceRoot":"","sources":["../../src/harness/grok.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AA2B1D,eAAO,MAAM,IAAI,EAAE,qBAqBlB,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport type { ShimmedHarnessProfile } from './profile.ts';\n\n/**\n * Grok CLI — EXPERIMENTAL tier. Schema, install and removal, end to end.\n *\n * Schema mapped from the capability matrix\n * (docs/research/2026-09-02-harness-capability-matrix.md, Grok CLI row; primary source\n * <https://docs.x.ai/build/features/hooks>), not yet smoke-tested against the real\n * binary. Note the matrix's provenance warning: the official CLI is\n * `xai-org/grok-build` (binary `grok`) — `superagent-ai/grok-cli` is a third-party\n * clone with a different hook surface.\n *\n * - event: `PreToolUse`; stdin `{ tool_name, tool_input }` (Claude-Code-like tool\n * names).\n * - deny: `{ \"decision\": \"deny\", \"reason\": … }` or exit 2. **Deny-only**: the input\n * is read-only to hooks, so there is no `rewrite` here — under\n * `hooks.enforcement: \"rewrite\"` this harness falls back to the deny, whose reason\n * still carries the exact replacement pipeline.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['Read', 'read_file', 'ReadFile'],\n bashTools: ['Bash', 'shell', 'run_shell_command'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['tool_input'],\n deny: (reason) => ({ decision: 'deny', reason }),\n};\n\nexport const grok: ShimmedHarnessProfile = {\n id: 'grok',\n name: 'Grok CLI',\n shortName: 'Grok',\n tier: 'experimental',\n detect: ['.grok'],\n detectHome: ['.grok'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: ['deny-only hooks: input rewrite is not supported, so rewrite mode falls back to deny'],\n hooks: HOOKS,\n install: [\n {\n kind: 'json-hooks',\n file: '.grok/hooks.json',\n event: 'PreToolUse',\n matchers: ['Read', 'Bash'],\n entry: 'command-list',\n lifecycle: false,\n },\n ],\n};\n"]}
1
+ {"version":3,"file":"grok.d.ts","sourceRoot":"","sources":["../../src/harness/grok.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AA2B1D,eAAO,MAAM,IAAI,EAAE,qBAwBlB,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport type { ShimmedHarnessProfile } from './profile.ts';\n\n/**\n * Grok CLI — EXPERIMENTAL tier. Schema, install and removal, end to end.\n *\n * Schema mapped from the capability matrix\n * (docs/research/2026-09-02-harness-capability-matrix.md, Grok CLI row; primary source\n * <https://docs.x.ai/build/features/hooks>), not yet smoke-tested against the real\n * binary. Note the matrix's provenance warning: the official CLI is\n * `xai-org/grok-build` (binary `grok`) — `superagent-ai/grok-cli` is a third-party\n * clone with a different hook surface.\n *\n * - event: `PreToolUse`; stdin `{ tool_name, tool_input }` (Claude-Code-like tool\n * names).\n * - deny: `{ \"decision\": \"deny\", \"reason\": … }` or exit 2. **Deny-only**: the input\n * is read-only to hooks, so there is no `rewrite` here — under\n * `hooks.enforcement: \"rewrite\"` this harness falls back to the deny, whose reason\n * still carries the exact replacement pipeline.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['Read', 'read_file', 'ReadFile'],\n bashTools: ['Bash', 'shell', 'run_shell_command'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['tool_input'],\n deny: (reason) => ({ decision: 'deny', reason }),\n};\n\nexport const grok: ShimmedHarnessProfile = {\n id: 'grok',\n name: 'Grok CLI',\n shortName: 'Grok',\n tier: 'experimental',\n detect: ['.grok'],\n detectHome: ['.grok'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: [\n 'deny-only hooks: input rewrite is not supported, so rewrite mode falls back to deny',\n 'MCP registration is manual for Grok (TOML) — packages/mcp/README.md has the snippet; setup does not edit TOML',\n ],\n hooks: HOOKS,\n install: [\n {\n kind: 'json-hooks',\n file: '.grok/hooks.json',\n event: 'PreToolUse',\n matchers: ['Read', 'Bash'],\n entry: 'command-list',\n lifecycle: false,\n },\n ],\n};\n"]}
@@ -31,7 +31,10 @@ export const grok = {
31
31
  detectHome: ['.grok'],
32
32
  instructionFile: 'AGENTS.md',
33
33
  instructions: 'snippet',
34
- caveats: ['deny-only hooks: input rewrite is not supported, so rewrite mode falls back to deny'],
34
+ caveats: [
35
+ 'deny-only hooks: input rewrite is not supported, so rewrite mode falls back to deny',
36
+ 'MCP registration is manual for Grok (TOML) — packages/mcp/README.md has the snippet; setup does not edit TOML',
37
+ ],
35
38
  hooks: HOOKS,
36
39
  install: [
37
40
  {
@@ -1 +1 @@
1
- {"version":3,"file":"grok.js","sourceRoot":"","sources":["../../src/harness/grok.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,KAAK,GAAsB;IAC/B,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,CAAC;IAC5C,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,mBAAmB,CAAC;IACjD,YAAY,EAAE,CAAC,WAAW,CAAC;IAC3B,aAAa,EAAE,CAAC,YAAY,CAAC;IAC7B,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;CACjD,CAAC;AAEF,MAAM,CAAC,MAAM,IAAI,GAA0B;IACzC,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,UAAU;IAChB,SAAS,EAAE,MAAM;IACjB,IAAI,EAAE,cAAc;IACpB,MAAM,EAAE,CAAC,OAAO,CAAC;IACjB,UAAU,EAAE,CAAC,OAAO,CAAC;IACrB,eAAe,EAAE,WAAW;IAC5B,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE,CAAC,qFAAqF,CAAC;IAChG,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE;QACP;YACE,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,kBAAkB;YACxB,KAAK,EAAE,YAAY;YACnB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;YAC1B,KAAK,EAAE,cAAc;YACrB,SAAS,EAAE,KAAK;SACjB;KACF;CACF,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport type { ShimmedHarnessProfile } from './profile.ts';\n\n/**\n * Grok CLI — EXPERIMENTAL tier. Schema, install and removal, end to end.\n *\n * Schema mapped from the capability matrix\n * (docs/research/2026-09-02-harness-capability-matrix.md, Grok CLI row; primary source\n * <https://docs.x.ai/build/features/hooks>), not yet smoke-tested against the real\n * binary. Note the matrix's provenance warning: the official CLI is\n * `xai-org/grok-build` (binary `grok`) — `superagent-ai/grok-cli` is a third-party\n * clone with a different hook surface.\n *\n * - event: `PreToolUse`; stdin `{ tool_name, tool_input }` (Claude-Code-like tool\n * names).\n * - deny: `{ \"decision\": \"deny\", \"reason\": … }` or exit 2. **Deny-only**: the input\n * is read-only to hooks, so there is no `rewrite` here — under\n * `hooks.enforcement: \"rewrite\"` this harness falls back to the deny, whose reason\n * still carries the exact replacement pipeline.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['Read', 'read_file', 'ReadFile'],\n bashTools: ['Bash', 'shell', 'run_shell_command'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['tool_input'],\n deny: (reason) => ({ decision: 'deny', reason }),\n};\n\nexport const grok: ShimmedHarnessProfile = {\n id: 'grok',\n name: 'Grok CLI',\n shortName: 'Grok',\n tier: 'experimental',\n detect: ['.grok'],\n detectHome: ['.grok'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: ['deny-only hooks: input rewrite is not supported, so rewrite mode falls back to deny'],\n hooks: HOOKS,\n install: [\n {\n kind: 'json-hooks',\n file: '.grok/hooks.json',\n event: 'PreToolUse',\n matchers: ['Read', 'Bash'],\n entry: 'command-list',\n lifecycle: false,\n },\n ],\n};\n"]}
1
+ {"version":3,"file":"grok.js","sourceRoot":"","sources":["../../src/harness/grok.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,KAAK,GAAsB;IAC/B,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,CAAC;IAC5C,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,mBAAmB,CAAC;IACjD,YAAY,EAAE,CAAC,WAAW,CAAC;IAC3B,aAAa,EAAE,CAAC,YAAY,CAAC;IAC7B,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;CACjD,CAAC;AAEF,MAAM,CAAC,MAAM,IAAI,GAA0B;IACzC,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,UAAU;IAChB,SAAS,EAAE,MAAM;IACjB,IAAI,EAAE,cAAc;IACpB,MAAM,EAAE,CAAC,OAAO,CAAC;IACjB,UAAU,EAAE,CAAC,OAAO,CAAC;IACrB,eAAe,EAAE,WAAW;IAC5B,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE;QACP,qFAAqF;QACrF,+GAA+G;KAChH;IACD,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE;QACP;YACE,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,kBAAkB;YACxB,KAAK,EAAE,YAAY;YACnB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;YAC1B,KAAK,EAAE,cAAc;YACrB,SAAS,EAAE,KAAK;SACjB;KACF;CACF,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport type { ShimmedHarnessProfile } from './profile.ts';\n\n/**\n * Grok CLI — EXPERIMENTAL tier. Schema, install and removal, end to end.\n *\n * Schema mapped from the capability matrix\n * (docs/research/2026-09-02-harness-capability-matrix.md, Grok CLI row; primary source\n * <https://docs.x.ai/build/features/hooks>), not yet smoke-tested against the real\n * binary. Note the matrix's provenance warning: the official CLI is\n * `xai-org/grok-build` (binary `grok`) — `superagent-ai/grok-cli` is a third-party\n * clone with a different hook surface.\n *\n * - event: `PreToolUse`; stdin `{ tool_name, tool_input }` (Claude-Code-like tool\n * names).\n * - deny: `{ \"decision\": \"deny\", \"reason\": … }` or exit 2. **Deny-only**: the input\n * is read-only to hooks, so there is no `rewrite` here — under\n * `hooks.enforcement: \"rewrite\"` this harness falls back to the deny, whose reason\n * still carries the exact replacement pipeline.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['Read', 'read_file', 'ReadFile'],\n bashTools: ['Bash', 'shell', 'run_shell_command'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['tool_input'],\n deny: (reason) => ({ decision: 'deny', reason }),\n};\n\nexport const grok: ShimmedHarnessProfile = {\n id: 'grok',\n name: 'Grok CLI',\n shortName: 'Grok',\n tier: 'experimental',\n detect: ['.grok'],\n detectHome: ['.grok'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: [\n 'deny-only hooks: input rewrite is not supported, so rewrite mode falls back to deny',\n 'MCP registration is manual for Grok (TOML) — packages/mcp/README.md has the snippet; setup does not edit TOML',\n ],\n hooks: HOOKS,\n install: [\n {\n kind: 'json-hooks',\n file: '.grok/hooks.json',\n event: 'PreToolUse',\n matchers: ['Read', 'Bash'],\n entry: 'command-list',\n lifecycle: false,\n },\n ],\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"kilocode.d.ts","sourceRoot":"","sources":["../../src/harness/kilocode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAyB,cAAc,EAAE,MAAM,cAAc,CAAC;AAwB1E,eAAO,MAAM,QAAQ,EAAE,cAYtB,CAAC","sourcesContent":["import type { HarnessInstallContext, HarnessProfile } from './profile.ts';\nimport { instructionSnippet } from './snippet.ts';\n\n/**\n * KiloCode — ADVISORY tier. Instructions only, and honest about it.\n *\n * No first-class hook API (Kilo-Org/kilocode#5827), so nothing here is enforced: what\n * ships is a rules file this harness owns whole — the shared snippet plus the two\n * manual enforcement legs the snippet has no room for. `remove` deletes it.\n */\nfunction kilocodeRulesSource(ctx: HarnessInstallContext): string {\n return `${instructionSnippet(ctx.thresholdBytes, ctx.budgetBytes)}\n<!-- smelt:hooks v1 advisory notes -->\n\nKiloCode has no first-class hook API (Kilo-Org/kilocode#5827), so nothing above is\nenforced — it is advisory. Two manual legs make it harder to bypass:\n\n1. Permissions: in your KiloCode per-tool permission config, set raw-read/execute\n tools to \"ask\" so oversized reads surface for review instead of passing silently.\n2. MCP: expose smelt through an MCP server and prefer its tools; a resident server\n also keeps smelt's grammar cache warm across calls.\n`;\n}\n\nexport const kilocode: HarnessProfile = {\n id: 'kilocode',\n name: 'KiloCode',\n tier: 'advisory',\n detect: ['.kilocode'],\n detectHome: ['.config/kilo'],\n instructionFile: '.kilocode/rules/smelt.md',\n instructions: kilocodeRulesSource,\n caveats: [\n 'no first-class hooks (Kilo-Org/kilocode#5827): enforcement is permissions config + MCP, both manual',\n ],\n install: [],\n};\n"]}
1
+ {"version":3,"file":"kilocode.d.ts","sourceRoot":"","sources":["../../src/harness/kilocode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAyB,cAAc,EAAE,MAAM,cAAc,CAAC;AAwB1E,eAAO,MAAM,QAAQ,EAAE,cAYtB,CAAC","sourcesContent":["import type { HarnessInstallContext, HarnessProfile } from './profile.ts';\nimport { instructionSnippet } from './snippet.ts';\n\n/**\n * KiloCode — ADVISORY tier. Instructions only, and honest about it.\n *\n * No first-class hook API (Kilo-Org/kilocode#5827), so nothing here is enforced: what\n * ships is a rules file this harness owns whole — the shared snippet plus the two\n * manual enforcement legs the snippet has no room for. `remove` deletes it.\n */\nfunction kilocodeRulesSource(ctx: HarnessInstallContext): string {\n return `${instructionSnippet(ctx.thresholdBytes, ctx.budgetBytes, ctx.writtenBy)}\n<!-- smelt:hooks v1 advisory notes -->\n\nKiloCode has no first-class hook API (Kilo-Org/kilocode#5827), so nothing above is\nenforced — it is advisory. Two manual legs make it harder to bypass:\n\n1. Permissions: in your KiloCode per-tool permission config, set raw-read/execute\n tools to \"ask\" so oversized reads surface for review instead of passing silently.\n2. MCP: expose smelt through an MCP server and prefer its tools; a resident server\n also keeps smelt's grammar cache warm across calls.\n`;\n}\n\nexport const kilocode: HarnessProfile = {\n id: 'kilocode',\n name: 'KiloCode',\n tier: 'advisory',\n detect: ['.kilocode'],\n detectHome: ['.config/kilo'],\n instructionFile: '.kilocode/rules/smelt.md',\n instructions: kilocodeRulesSource,\n caveats: [\n 'no first-class hooks (Kilo-Org/kilocode#5827): enforcement is permissions config + MCP, both manual',\n ],\n install: [],\n};\n"]}
@@ -7,7 +7,7 @@ import { instructionSnippet } from './snippet.js';
7
7
  * manual enforcement legs the snippet has no room for. `remove` deletes it.
8
8
  */
9
9
  function kilocodeRulesSource(ctx) {
10
- return `${instructionSnippet(ctx.thresholdBytes, ctx.budgetBytes)}
10
+ return `${instructionSnippet(ctx.thresholdBytes, ctx.budgetBytes, ctx.writtenBy)}
11
11
  <!-- smelt:hooks v1 advisory notes -->
12
12
 
13
13
  KiloCode has no first-class hook API (Kilo-Org/kilocode#5827), so nothing above is
@@ -1 +1 @@
1
- {"version":3,"file":"kilocode.js","sourceRoot":"","sources":["../../src/harness/kilocode.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD;;;;;;GAMG;AACH,SAAS,mBAAmB,CAAC,GAA0B;IACrD,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,WAAW,CAAC;;;;;;;;;;CAUlE,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAmB;IACtC,EAAE,EAAE,UAAU;IACd,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,CAAC,WAAW,CAAC;IACrB,UAAU,EAAE,CAAC,cAAc,CAAC;IAC5B,eAAe,EAAE,0BAA0B;IAC3C,YAAY,EAAE,mBAAmB;IACjC,OAAO,EAAE;QACP,qGAAqG;KACtG;IACD,OAAO,EAAE,EAAE;CACZ,CAAC","sourcesContent":["import type { HarnessInstallContext, HarnessProfile } from './profile.ts';\nimport { instructionSnippet } from './snippet.ts';\n\n/**\n * KiloCode — ADVISORY tier. Instructions only, and honest about it.\n *\n * No first-class hook API (Kilo-Org/kilocode#5827), so nothing here is enforced: what\n * ships is a rules file this harness owns whole — the shared snippet plus the two\n * manual enforcement legs the snippet has no room for. `remove` deletes it.\n */\nfunction kilocodeRulesSource(ctx: HarnessInstallContext): string {\n return `${instructionSnippet(ctx.thresholdBytes, ctx.budgetBytes)}\n<!-- smelt:hooks v1 advisory notes -->\n\nKiloCode has no first-class hook API (Kilo-Org/kilocode#5827), so nothing above is\nenforced — it is advisory. Two manual legs make it harder to bypass:\n\n1. Permissions: in your KiloCode per-tool permission config, set raw-read/execute\n tools to \"ask\" so oversized reads surface for review instead of passing silently.\n2. MCP: expose smelt through an MCP server and prefer its tools; a resident server\n also keeps smelt's grammar cache warm across calls.\n`;\n}\n\nexport const kilocode: HarnessProfile = {\n id: 'kilocode',\n name: 'KiloCode',\n tier: 'advisory',\n detect: ['.kilocode'],\n detectHome: ['.config/kilo'],\n instructionFile: '.kilocode/rules/smelt.md',\n instructions: kilocodeRulesSource,\n caveats: [\n 'no first-class hooks (Kilo-Org/kilocode#5827): enforcement is permissions config + MCP, both manual',\n ],\n install: [],\n};\n"]}
1
+ {"version":3,"file":"kilocode.js","sourceRoot":"","sources":["../../src/harness/kilocode.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD;;;;;;GAMG;AACH,SAAS,mBAAmB,CAAC,GAA0B;IACrD,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,SAAS,CAAC;;;;;;;;;;CAUjF,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAmB;IACtC,EAAE,EAAE,UAAU;IACd,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,CAAC,WAAW,CAAC;IACrB,UAAU,EAAE,CAAC,cAAc,CAAC;IAC5B,eAAe,EAAE,0BAA0B;IAC3C,YAAY,EAAE,mBAAmB;IACjC,OAAO,EAAE;QACP,qGAAqG;KACtG;IACD,OAAO,EAAE,EAAE;CACZ,CAAC","sourcesContent":["import type { HarnessInstallContext, HarnessProfile } from './profile.ts';\nimport { instructionSnippet } from './snippet.ts';\n\n/**\n * KiloCode — ADVISORY tier. Instructions only, and honest about it.\n *\n * No first-class hook API (Kilo-Org/kilocode#5827), so nothing here is enforced: what\n * ships is a rules file this harness owns whole — the shared snippet plus the two\n * manual enforcement legs the snippet has no room for. `remove` deletes it.\n */\nfunction kilocodeRulesSource(ctx: HarnessInstallContext): string {\n return `${instructionSnippet(ctx.thresholdBytes, ctx.budgetBytes, ctx.writtenBy)}\n<!-- smelt:hooks v1 advisory notes -->\n\nKiloCode has no first-class hook API (Kilo-Org/kilocode#5827), so nothing above is\nenforced — it is advisory. Two manual legs make it harder to bypass:\n\n1. Permissions: in your KiloCode per-tool permission config, set raw-read/execute\n tools to \"ask\" so oversized reads surface for review instead of passing silently.\n2. MCP: expose smelt through an MCP server and prefer its tools; a resident server\n also keeps smelt's grammar cache warm across calls.\n`;\n}\n\nexport const kilocode: HarnessProfile = {\n id: 'kilocode',\n name: 'KiloCode',\n tier: 'advisory',\n detect: ['.kilocode'],\n detectHome: ['.config/kilo'],\n instructionFile: '.kilocode/rules/smelt.md',\n instructions: kilocodeRulesSource,\n caveats: [\n 'no first-class hooks (Kilo-Org/kilocode#5827): enforcement is permissions config + MCP, both manual',\n ],\n install: [],\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../src/harness/opencode.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAyB,cAAc,EAAE,MAAM,cAAc,CAAC;AA6E1E,eAAO,MAAM,QAAQ,EAAE,cAmBtB,CAAC","sourcesContent":["import {\n DENIED_WITHOUT_REASON,\n REWRITE_ANNOUNCEMENT_JOIN,\n REWRITE_ANNOUNCEMENT_OPENING,\n} from '../hooks/shim.ts';\n\nimport { guardCoreScriptPath, portablePath } from './paths.ts';\nimport type { HarnessInstallContext, HarnessProfile } from './profile.ts';\n\n/**\n * opencode — EXPERIMENTAL tier. Install and removal, end to end.\n *\n * The one harness with no shim script: its hook API is a JavaScript plugin, not a\n * stdin schema, so what the installer writes *is* the adapter. The plugin imports the\n * built guard core directly (the same module every shim calls) and maps\n * `tool.execute.before` onto it, so all policy still lives in exactly one place.\n *\n * Its two announcements — the rewrite sentence and the reasonless deny — are spliced\n * in from `hooks/shim.ts`'s constants rather than hand-typed into the template, where\n * nothing could see them drift from what the shims print.\n */\nfunction opencodePluginSource(ctx: HarnessInstallContext): string {\n const guardCore = portablePath(ctx.cwd, guardCoreScriptPath());\n return `// smelt:hooks v1 — opencode plugin shim. EXPERIMENTAL tier: mapped from the\n// capability matrix (docs/research/2026-09-02-harness-capability-matrix.md, opencode\n// row; https://opencode.ai/docs/plugins/). This template's deny/pass/window paths\n// were exercised directly against the built guard core (verified\n// 2026-09-02), but a live opencode session has not been smoke-tested — that needs\n// provider credentials. Caveat carried from the matrix: MCP tools can bypass plugin\n// hooks (sst/opencode#2319) — this guard sees built-in tools only.\n//\n// Thin adapter: maps tool.execute.before onto the smelt guard core (zero\n// dependencies), which owns every decision. Deny mode throws (opencode surfaces the\n// reason to the model); rewrite mode substitutes the faithful replacement command —\n// announced on stderr, because the plugin API has no reason channel on a rewrite\n// and a substitution must never be silent. Both announcements below are spliced in\n// from the shims' own constants, so this copy cannot drift from theirs.\nimport { pathToFileURL } from 'node:url';\n\nconst GUARD_CORE = ${JSON.stringify(guardCore)};\nconst core = await import(pathToFileURL(GUARD_CORE).href);\n\nexport const SmeltGuard = async () => ({\n 'tool.execute.before': async (input, output) => {\n const tool = input?.tool;\n const args = output?.args ?? {};\n let request;\n if (tool === 'read' && typeof args.filePath === 'string') {\n request = {\n tool: 'Read',\n input: {\n path: args.filePath,\n offsetLimited: args.offset !== undefined || args.limit !== undefined,\n },\n };\n } else if (tool === 'bash' && typeof args.command === 'string') {\n request = { tool: 'Bash', input: { command: args.command } };\n } else {\n return;\n }\n const warn = (text) => process.stderr.write(text + '\\\\n');\n const settings = core.readGuardSettings(process.cwd(), warn);\n const decision = core.decide(request, settings, process.cwd());\n if (decision.action !== 'deny') return;\n if (\n settings.enforcement === 'rewrite' &&\n request.tool === 'Bash' &&\n decision.suggestion !== undefined\n ) {\n warn(\n ${JSON.stringify(REWRITE_ANNOUNCEMENT_OPENING)} +\n decision.suggestion +\n ${JSON.stringify(REWRITE_ANNOUNCEMENT_JOIN)} +\n (decision.reason ?? ''),\n );\n output.args.command = decision.suggestion;\n return;\n }\n throw new Error(decision.reason ?? ${JSON.stringify(DENIED_WITHOUT_REASON)});\n },\n});\n`;\n}\n\nexport const opencode: HarnessProfile = {\n id: 'opencode',\n name: 'opencode',\n tier: 'experimental',\n detect: ['.opencode', 'opencode.json'],\n detectHome: ['.config/opencode'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: [\n 'MCP tools can bypass opencode plugin hooks (sst/opencode#2319) — the guard sees built-in tools only',\n ],\n install: [\n {\n kind: 'own-file',\n file: '.opencode/plugin/smelt-guard.js',\n content: opencodePluginSource,\n guardOnly: true,\n },\n ],\n};\n"]}
1
+ {"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../src/harness/opencode.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAyB,cAAc,EAAE,MAAM,cAAc,CAAC;AAiF1E,eAAO,MAAM,QAAQ,EAAE,cAyBtB,CAAC","sourcesContent":["import {\n DENIED_WITHOUT_REASON,\n REWRITE_ANNOUNCEMENT_JOIN,\n REWRITE_ANNOUNCEMENT_OPENING,\n} from '../hooks/shim.ts';\n\nimport { MCP_RUN_ARGS } from '../setup/recipe.ts';\nimport { guardCoreScriptPath, portablePath } from './paths.ts';\nimport type { HarnessInstallContext, HarnessProfile } from './profile.ts';\n\n/**\n * opencode — EXPERIMENTAL tier. Install and removal, end to end.\n *\n * The one harness with no shim script: its hook API is a JavaScript plugin, not a\n * stdin schema, so what the installer writes *is* the adapter. The plugin imports the\n * built guard core directly (the same module every shim calls) and maps\n * `tool.execute.before` onto it, so all policy still lives in exactly one place.\n *\n * Its two announcements — the rewrite sentence and the reasonless deny — are spliced\n * in from `hooks/shim.ts`'s constants rather than hand-typed into the template, where\n * nothing could see them drift from what the shims print.\n */\nfunction opencodePluginSource(ctx: HarnessInstallContext): string {\n const guardCore = portablePath(ctx.cwd, guardCoreScriptPath());\n return `// smelt:hooks v1 — opencode plugin shim. EXPERIMENTAL tier: mapped from the\n// capability matrix (docs/research/2026-09-02-harness-capability-matrix.md, opencode\n// row; https://opencode.ai/docs/plugins/). This template's deny/pass/window paths\n// were exercised directly against the built guard core (verified\n// 2026-09-02), but a live opencode session has not been smoke-tested — that needs\n// provider credentials. Caveat carried from the matrix: MCP tools can bypass plugin\n// hooks (sst/opencode#2319) — this guard sees built-in tools only.\n//\n// Thin adapter: maps tool.execute.before onto the smelt guard core (zero\n// dependencies), which owns every decision. Deny mode throws (opencode surfaces the\n// reason to the model); rewrite mode substitutes the faithful replacement command —\n// announced on stderr, because the plugin API has no reason channel on a rewrite\n// and a substitution must never be silent. Both announcements below are spliced in\n// from the shims' own constants, so this copy cannot drift from theirs.\nimport { pathToFileURL } from 'node:url';\n\nconst GUARD_CORE = ${JSON.stringify(guardCore)};\nconst core = await import(pathToFileURL(GUARD_CORE).href);\n\nexport const SmeltGuard = async () => ({\n 'tool.execute.before': async (input, output) => {\n const tool = input?.tool;\n const args = output?.args ?? {};\n let request;\n if (tool === 'read' && typeof args.filePath === 'string') {\n request = {\n tool: 'Read',\n input: {\n path: args.filePath,\n offsetLimited: args.offset !== undefined || args.limit !== undefined,\n },\n };\n } else if (tool === 'bash' && typeof args.command === 'string') {\n request = { tool: 'Bash', input: { command: args.command } };\n } else {\n return;\n }\n const warn = (text) => process.stderr.write(text + '\\\\n');\n const settings = core.readGuardSettings(process.cwd(), warn);\n const decision = core.decide(request, settings, process.cwd());\n if (decision.action !== 'deny') return;\n if (\n settings.enforcement === 'rewrite' &&\n request.tool === 'Bash' &&\n decision.suggestion !== undefined\n ) {\n warn(\n ${JSON.stringify(REWRITE_ANNOUNCEMENT_OPENING)} +\n decision.suggestion +\n ${JSON.stringify(REWRITE_ANNOUNCEMENT_JOIN)} +\n (decision.reason ?? ''),\n );\n output.args.command = decision.suggestion;\n return;\n }\n throw new Error(decision.reason ?? ${JSON.stringify(DENIED_WITHOUT_REASON)});\n },\n});\n`;\n}\n\n// Same derivation as claude-code's, from the one place it lives (MCP_RUN_ARGS):\n// the recipe's run command in opencode's spawn shape (a single `command` array,\n// `type: \"local\"`).\n\nexport const opencode: HarnessProfile = {\n id: 'opencode',\n name: 'opencode',\n tier: 'experimental',\n detect: ['.opencode', 'opencode.json'],\n detectHome: ['.config/opencode'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: [\n 'MCP tools can bypass opencode plugin hooks (sst/opencode#2319) — the guard sees built-in tools only',\n ],\n install: [\n {\n kind: 'own-file',\n file: '.opencode/plugin/smelt-guard.js',\n content: opencodePluginSource,\n guardOnly: true,\n },\n {\n kind: 'mcp-registration',\n file: 'opencode.json',\n path: ['mcp', 'smelt'],\n entry: () => ({ type: 'local', command: [...MCP_RUN_ARGS] }),\n },\n ],\n};\n"]}
@@ -1,4 +1,5 @@
1
1
  import { DENIED_WITHOUT_REASON, REWRITE_ANNOUNCEMENT_JOIN, REWRITE_ANNOUNCEMENT_OPENING, } from '../hooks/shim.js';
2
+ import { MCP_RUN_ARGS } from '../setup/recipe.js';
2
3
  import { guardCoreScriptPath, portablePath } from './paths.js';
3
4
  /**
4
5
  * opencode — EXPERIMENTAL tier. Install and removal, end to end.
@@ -74,6 +75,9 @@ export const SmeltGuard = async () => ({
74
75
  });
75
76
  `;
76
77
  }
78
+ // Same derivation as claude-code's, from the one place it lives (MCP_RUN_ARGS):
79
+ // the recipe's run command in opencode's spawn shape (a single `command` array,
80
+ // `type: "local"`).
77
81
  export const opencode = {
78
82
  id: 'opencode',
79
83
  name: 'opencode',
@@ -92,6 +96,12 @@ export const opencode = {
92
96
  content: opencodePluginSource,
93
97
  guardOnly: true,
94
98
  },
99
+ {
100
+ kind: 'mcp-registration',
101
+ file: 'opencode.json',
102
+ path: ['mcp', 'smelt'],
103
+ entry: () => ({ type: 'local', command: [...MCP_RUN_ARGS] }),
104
+ },
95
105
  ],
96
106
  };
97
107
  //# sourceMappingURL=opencode.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"opencode.js","sourceRoot":"","sources":["../../src/harness/opencode.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/D;;;;;;;;;;;GAWG;AACH,SAAS,oBAAoB,CAAC,GAA0B;IACtD,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAC/D,OAAO;;;;;;;;;;;;;;;;qBAgBY,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA+BpC,IAAI,CAAC,SAAS,CAAC,4BAA4B,CAAC;;YAE1C,IAAI,CAAC,SAAS,CAAC,yBAAyB,CAAC;;;;;;yCAMZ,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC;;;CAG7E,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAmB;IACtC,EAAE,EAAE,UAAU;IACd,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,cAAc;IACpB,MAAM,EAAE,CAAC,WAAW,EAAE,eAAe,CAAC;IACtC,UAAU,EAAE,CAAC,kBAAkB,CAAC;IAChC,eAAe,EAAE,WAAW;IAC5B,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE;QACP,qGAAqG;KACtG;IACD,OAAO,EAAE;QACP;YACE,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,iCAAiC;YACvC,OAAO,EAAE,oBAAoB;YAC7B,SAAS,EAAE,IAAI;SAChB;KACF;CACF,CAAC","sourcesContent":["import {\n DENIED_WITHOUT_REASON,\n REWRITE_ANNOUNCEMENT_JOIN,\n REWRITE_ANNOUNCEMENT_OPENING,\n} from '../hooks/shim.ts';\n\nimport { guardCoreScriptPath, portablePath } from './paths.ts';\nimport type { HarnessInstallContext, HarnessProfile } from './profile.ts';\n\n/**\n * opencode — EXPERIMENTAL tier. Install and removal, end to end.\n *\n * The one harness with no shim script: its hook API is a JavaScript plugin, not a\n * stdin schema, so what the installer writes *is* the adapter. The plugin imports the\n * built guard core directly (the same module every shim calls) and maps\n * `tool.execute.before` onto it, so all policy still lives in exactly one place.\n *\n * Its two announcements — the rewrite sentence and the reasonless deny — are spliced\n * in from `hooks/shim.ts`'s constants rather than hand-typed into the template, where\n * nothing could see them drift from what the shims print.\n */\nfunction opencodePluginSource(ctx: HarnessInstallContext): string {\n const guardCore = portablePath(ctx.cwd, guardCoreScriptPath());\n return `// smelt:hooks v1 — opencode plugin shim. EXPERIMENTAL tier: mapped from the\n// capability matrix (docs/research/2026-09-02-harness-capability-matrix.md, opencode\n// row; https://opencode.ai/docs/plugins/). This template's deny/pass/window paths\n// were exercised directly against the built guard core (verified\n// 2026-09-02), but a live opencode session has not been smoke-tested — that needs\n// provider credentials. Caveat carried from the matrix: MCP tools can bypass plugin\n// hooks (sst/opencode#2319) — this guard sees built-in tools only.\n//\n// Thin adapter: maps tool.execute.before onto the smelt guard core (zero\n// dependencies), which owns every decision. Deny mode throws (opencode surfaces the\n// reason to the model); rewrite mode substitutes the faithful replacement command —\n// announced on stderr, because the plugin API has no reason channel on a rewrite\n// and a substitution must never be silent. Both announcements below are spliced in\n// from the shims' own constants, so this copy cannot drift from theirs.\nimport { pathToFileURL } from 'node:url';\n\nconst GUARD_CORE = ${JSON.stringify(guardCore)};\nconst core = await import(pathToFileURL(GUARD_CORE).href);\n\nexport const SmeltGuard = async () => ({\n 'tool.execute.before': async (input, output) => {\n const tool = input?.tool;\n const args = output?.args ?? {};\n let request;\n if (tool === 'read' && typeof args.filePath === 'string') {\n request = {\n tool: 'Read',\n input: {\n path: args.filePath,\n offsetLimited: args.offset !== undefined || args.limit !== undefined,\n },\n };\n } else if (tool === 'bash' && typeof args.command === 'string') {\n request = { tool: 'Bash', input: { command: args.command } };\n } else {\n return;\n }\n const warn = (text) => process.stderr.write(text + '\\\\n');\n const settings = core.readGuardSettings(process.cwd(), warn);\n const decision = core.decide(request, settings, process.cwd());\n if (decision.action !== 'deny') return;\n if (\n settings.enforcement === 'rewrite' &&\n request.tool === 'Bash' &&\n decision.suggestion !== undefined\n ) {\n warn(\n ${JSON.stringify(REWRITE_ANNOUNCEMENT_OPENING)} +\n decision.suggestion +\n ${JSON.stringify(REWRITE_ANNOUNCEMENT_JOIN)} +\n (decision.reason ?? ''),\n );\n output.args.command = decision.suggestion;\n return;\n }\n throw new Error(decision.reason ?? ${JSON.stringify(DENIED_WITHOUT_REASON)});\n },\n});\n`;\n}\n\nexport const opencode: HarnessProfile = {\n id: 'opencode',\n name: 'opencode',\n tier: 'experimental',\n detect: ['.opencode', 'opencode.json'],\n detectHome: ['.config/opencode'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: [\n 'MCP tools can bypass opencode plugin hooks (sst/opencode#2319) — the guard sees built-in tools only',\n ],\n install: [\n {\n kind: 'own-file',\n file: '.opencode/plugin/smelt-guard.js',\n content: opencodePluginSource,\n guardOnly: true,\n },\n ],\n};\n"]}
1
+ {"version":3,"file":"opencode.js","sourceRoot":"","sources":["../../src/harness/opencode.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/D;;;;;;;;;;;GAWG;AACH,SAAS,oBAAoB,CAAC,GAA0B;IACtD,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAC/D,OAAO;;;;;;;;;;;;;;;;qBAgBY,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA+BpC,IAAI,CAAC,SAAS,CAAC,4BAA4B,CAAC;;YAE1C,IAAI,CAAC,SAAS,CAAC,yBAAyB,CAAC;;;;;;yCAMZ,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC;;;CAG7E,CAAC;AACF,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,oBAAoB;AAEpB,MAAM,CAAC,MAAM,QAAQ,GAAmB;IACtC,EAAE,EAAE,UAAU;IACd,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,cAAc;IACpB,MAAM,EAAE,CAAC,WAAW,EAAE,eAAe,CAAC;IACtC,UAAU,EAAE,CAAC,kBAAkB,CAAC;IAChC,eAAe,EAAE,WAAW;IAC5B,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE;QACP,qGAAqG;KACtG;IACD,OAAO,EAAE;QACP;YACE,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,iCAAiC;YACvC,OAAO,EAAE,oBAAoB;YAC7B,SAAS,EAAE,IAAI;SAChB;QACD;YACE,IAAI,EAAE,kBAAkB;YACxB,IAAI,EAAE,eAAe;YACrB,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC;YACtB,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,YAAY,CAAC,EAAE,CAAC;SAC7D;KACF;CACF,CAAC","sourcesContent":["import {\n DENIED_WITHOUT_REASON,\n REWRITE_ANNOUNCEMENT_JOIN,\n REWRITE_ANNOUNCEMENT_OPENING,\n} from '../hooks/shim.ts';\n\nimport { MCP_RUN_ARGS } from '../setup/recipe.ts';\nimport { guardCoreScriptPath, portablePath } from './paths.ts';\nimport type { HarnessInstallContext, HarnessProfile } from './profile.ts';\n\n/**\n * opencode — EXPERIMENTAL tier. Install and removal, end to end.\n *\n * The one harness with no shim script: its hook API is a JavaScript plugin, not a\n * stdin schema, so what the installer writes *is* the adapter. The plugin imports the\n * built guard core directly (the same module every shim calls) and maps\n * `tool.execute.before` onto it, so all policy still lives in exactly one place.\n *\n * Its two announcements — the rewrite sentence and the reasonless deny — are spliced\n * in from `hooks/shim.ts`'s constants rather than hand-typed into the template, where\n * nothing could see them drift from what the shims print.\n */\nfunction opencodePluginSource(ctx: HarnessInstallContext): string {\n const guardCore = portablePath(ctx.cwd, guardCoreScriptPath());\n return `// smelt:hooks v1 — opencode plugin shim. EXPERIMENTAL tier: mapped from the\n// capability matrix (docs/research/2026-09-02-harness-capability-matrix.md, opencode\n// row; https://opencode.ai/docs/plugins/). This template's deny/pass/window paths\n// were exercised directly against the built guard core (verified\n// 2026-09-02), but a live opencode session has not been smoke-tested — that needs\n// provider credentials. Caveat carried from the matrix: MCP tools can bypass plugin\n// hooks (sst/opencode#2319) — this guard sees built-in tools only.\n//\n// Thin adapter: maps tool.execute.before onto the smelt guard core (zero\n// dependencies), which owns every decision. Deny mode throws (opencode surfaces the\n// reason to the model); rewrite mode substitutes the faithful replacement command —\n// announced on stderr, because the plugin API has no reason channel on a rewrite\n// and a substitution must never be silent. Both announcements below are spliced in\n// from the shims' own constants, so this copy cannot drift from theirs.\nimport { pathToFileURL } from 'node:url';\n\nconst GUARD_CORE = ${JSON.stringify(guardCore)};\nconst core = await import(pathToFileURL(GUARD_CORE).href);\n\nexport const SmeltGuard = async () => ({\n 'tool.execute.before': async (input, output) => {\n const tool = input?.tool;\n const args = output?.args ?? {};\n let request;\n if (tool === 'read' && typeof args.filePath === 'string') {\n request = {\n tool: 'Read',\n input: {\n path: args.filePath,\n offsetLimited: args.offset !== undefined || args.limit !== undefined,\n },\n };\n } else if (tool === 'bash' && typeof args.command === 'string') {\n request = { tool: 'Bash', input: { command: args.command } };\n } else {\n return;\n }\n const warn = (text) => process.stderr.write(text + '\\\\n');\n const settings = core.readGuardSettings(process.cwd(), warn);\n const decision = core.decide(request, settings, process.cwd());\n if (decision.action !== 'deny') return;\n if (\n settings.enforcement === 'rewrite' &&\n request.tool === 'Bash' &&\n decision.suggestion !== undefined\n ) {\n warn(\n ${JSON.stringify(REWRITE_ANNOUNCEMENT_OPENING)} +\n decision.suggestion +\n ${JSON.stringify(REWRITE_ANNOUNCEMENT_JOIN)} +\n (decision.reason ?? ''),\n );\n output.args.command = decision.suggestion;\n return;\n }\n throw new Error(decision.reason ?? ${JSON.stringify(DENIED_WITHOUT_REASON)});\n },\n});\n`;\n}\n\n// Same derivation as claude-code's, from the one place it lives (MCP_RUN_ARGS):\n// the recipe's run command in opencode's spawn shape (a single `command` array,\n// `type: \"local\"`).\n\nexport const opencode: HarnessProfile = {\n id: 'opencode',\n name: 'opencode',\n tier: 'experimental',\n detect: ['.opencode', 'opencode.json'],\n detectHome: ['.config/opencode'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: [\n 'MCP tools can bypass opencode plugin hooks (sst/opencode#2319) — the guard sees built-in tools only',\n ],\n install: [\n {\n kind: 'own-file',\n file: '.opencode/plugin/smelt-guard.js',\n content: opencodePluginSource,\n guardOnly: true,\n },\n {\n kind: 'mcp-registration',\n file: 'opencode.json',\n path: ['mcp', 'smelt'],\n entry: () => ({ type: 'local', command: [...MCP_RUN_ARGS] }),\n },\n ],\n};\n"]}
@@ -111,6 +111,8 @@ export declare function harnessLabel(profile: HarnessProfile): string;
111
111
  export interface HarnessInstallContext {
112
112
  /** Project directory: every path a renderer emits is portable relative to it. */
113
113
  readonly cwd: string;
114
+ /** The release writing these bytes — stamped into shared blocks for `smelt doctor`. */
115
+ readonly writtenBy?: string;
114
116
  readonly guard: boolean;
115
117
  readonly statsOnStop: boolean;
116
118
  readonly mapOnStart: boolean;
@@ -125,9 +127,10 @@ export type HarnessFileContent = (ctx: HarnessInstallContext) => string;
125
127
  /**
126
128
  * One artefact `hooks install` writes. The kind is also the un-write: `json-hooks` is
127
129
  * merged in and strip-merged out, `marker-block` is upserted and stripped,
128
- * `own-file` is written and deleted.
130
+ * `own-file` is written and deleted, `mcp-registration` is nested-merged in and
131
+ * lifted back out.
129
132
  */
130
- export type HarnessInstallStep = HarnessJsonHooks | HarnessMarkerBlock | HarnessOwnFile;
133
+ export type HarnessInstallStep = HarnessJsonHooks | HarnessMarkerBlock | HarnessOwnFile | HarnessMcpRegistration;
131
134
  /**
132
135
  * A JSON settings/hooks file the harness reads. Our entries are merged in
133
136
  * byte-faithfully and, on `remove`, stripped back out with everything foreign left
@@ -190,6 +193,24 @@ export interface HarnessOwnFile {
190
193
  /** True when the file exists only to wire the guard — the guard toggle gates it. */
191
194
  readonly guardOnly: boolean;
192
195
  }
196
+ /**
197
+ * An MCP server registration inside a JSON config the harness reads — Claude Code's
198
+ * `.mcp.json` (`mcpServers.smelt`), opencode's `opencode.json` (`mcp.smelt`). The
199
+ * entry is merged in byte-faithfully beside any other servers the user registered,
200
+ * and on `remove` it is lifted back out; a container this install created empty is
201
+ * removed with it, so a file that never carried the key round-trips byte-identical.
202
+ * Harnesses whose registration is TOML (Codex, Grok) do not declare this step — a
203
+ * hand-edit into TOML is exactly the edit `text/json-edit.ts` exists to refuse.
204
+ */
205
+ export interface HarnessMcpRegistration {
206
+ readonly kind: 'mcp-registration';
207
+ /** Project-relative path of the config file. */
208
+ readonly file: string;
209
+ /** The container key, then the server's name: `['mcpServers', 'smelt']`. */
210
+ readonly path: readonly [string, string];
211
+ /** The server entry as a JSON value — the bytes are the editor's. */
212
+ readonly entry: (ctx: HarnessInstallContext) => unknown;
213
+ }
193
214
  /**
194
215
  * A profile that ships a shim script. Only these have a shim path: `shimScriptPath`
195
216
  * takes one, so a harness with no shim cannot name a script that was never built.