@smeltjs/core 0.3.0 → 0.4.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 (89) hide show
  1. package/dist/agents/guide.d.ts +68 -0
  2. package/dist/agents/guide.d.ts.map +1 -0
  3. package/dist/agents/guide.js +72 -0
  4. package/dist/agents/guide.js.map +1 -0
  5. package/dist/agents/instructions.d.ts +133 -0
  6. package/dist/agents/instructions.d.ts.map +1 -0
  7. package/dist/agents/instructions.js +212 -0
  8. package/dist/agents/instructions.js.map +1 -0
  9. package/dist/agents/lint.d.ts +154 -0
  10. package/dist/agents/lint.d.ts.map +1 -0
  11. package/dist/agents/lint.js +761 -0
  12. package/dist/agents/lint.js.map +1 -0
  13. package/dist/agents/split.d.ts +113 -0
  14. package/dist/agents/split.d.ts.map +1 -0
  15. package/dist/agents/split.js +234 -0
  16. package/dist/agents/split.js.map +1 -0
  17. package/dist/cli/agents.d.ts +39 -0
  18. package/dist/cli/agents.d.ts.map +1 -0
  19. package/dist/cli/agents.js +121 -0
  20. package/dist/cli/agents.js.map +1 -0
  21. package/dist/cli/args.d.ts +1 -0
  22. package/dist/cli/args.d.ts.map +1 -1
  23. package/dist/cli/args.js.map +1 -1
  24. package/dist/cli/config.d.ts +20 -0
  25. package/dist/cli/config.d.ts.map +1 -1
  26. package/dist/cli/config.js +34 -1
  27. package/dist/cli/config.js.map +1 -1
  28. package/dist/cli/hooks.d.ts +25 -16
  29. package/dist/cli/hooks.d.ts.map +1 -1
  30. package/dist/cli/hooks.js +71 -178
  31. package/dist/cli/hooks.js.map +1 -1
  32. package/dist/cli/report.d.ts +23 -0
  33. package/dist/cli/report.d.ts.map +1 -1
  34. package/dist/cli/report.js +140 -2
  35. package/dist/cli/report.js.map +1 -1
  36. package/dist/cli/run.d.ts +5 -3
  37. package/dist/cli/run.d.ts.map +1 -1
  38. package/dist/cli/run.js +2 -1
  39. package/dist/cli/run.js.map +1 -1
  40. package/dist/cli/subcommands/agents.d.ts +72 -0
  41. package/dist/cli/subcommands/agents.d.ts.map +1 -0
  42. package/dist/cli/subcommands/agents.js +162 -0
  43. package/dist/cli/subcommands/agents.js.map +1 -0
  44. package/dist/cli/subcommands/flags.d.ts +3 -0
  45. package/dist/cli/subcommands/flags.d.ts.map +1 -1
  46. package/dist/cli/subcommands/flags.js +13 -1
  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 +19 -3
  50. package/dist/cli/subcommands/hooks.js.map +1 -1
  51. package/dist/cli/subcommands/registry.d.ts +2 -1
  52. package/dist/cli/subcommands/registry.d.ts.map +1 -1
  53. package/dist/cli/subcommands/registry.js +3 -0
  54. package/dist/cli/subcommands/registry.js.map +1 -1
  55. package/dist/cli/subcommands/subcommand.d.ts +1 -1
  56. package/dist/cli/subcommands/subcommand.d.ts.map +1 -1
  57. package/dist/cli/subcommands/subcommand.js.map +1 -1
  58. package/dist/harness/codex.d.ts.map +1 -1
  59. package/dist/harness/codex.js +1 -0
  60. package/dist/harness/codex.js.map +1 -1
  61. package/dist/harness/gemini.d.ts.map +1 -1
  62. package/dist/harness/gemini.js +1 -0
  63. package/dist/harness/gemini.js.map +1 -1
  64. package/dist/harness/grok.d.ts.map +1 -1
  65. package/dist/harness/grok.js +1 -0
  66. package/dist/harness/grok.js.map +1 -1
  67. package/dist/harness/hermes.d.ts.map +1 -1
  68. package/dist/harness/hermes.js +1 -0
  69. package/dist/harness/hermes.js.map +1 -1
  70. package/dist/harness/profile.d.ts +26 -1
  71. package/dist/harness/profile.d.ts.map +1 -1
  72. package/dist/harness/profile.js +15 -0
  73. package/dist/harness/profile.js.map +1 -1
  74. package/dist/harness/registry.d.ts +55 -6
  75. package/dist/harness/registry.d.ts.map +1 -1
  76. package/dist/harness/registry.js +63 -6
  77. package/dist/harness/registry.js.map +1 -1
  78. package/dist/index.d.ts +10 -0
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +8 -0
  81. package/dist/index.js.map +1 -1
  82. package/dist/text/json-edit.d.ts +71 -0
  83. package/dist/text/json-edit.d.ts.map +1 -0
  84. package/dist/text/json-edit.js +219 -0
  85. package/dist/text/json-edit.js.map +1 -0
  86. package/dist/types.d.ts +0 -11
  87. package/dist/types.d.ts.map +1 -1
  88. package/dist/types.js.map +1 -1
  89. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"hermes.d.ts","sourceRoot":"","sources":["../../src/harness/hermes.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAyB,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAmDjF,eAAO,MAAM,MAAM,EAAE,qBAqBpB,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport { nodeCommand, shimScriptPath } from './paths.ts';\nimport type { HarnessInstallContext, ShimmedHarnessProfile } from './profile.ts';\nimport { SNIPPET_END_HASH, SNIPPET_START_HASH } from './snippet.ts';\n\n/**\n * Hermes Agent — 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, Hermes Agent row; primary\n * source <https://hermes-agent.nousresearch.com/docs/user-guide/features/hooks>), not\n * yet smoke-tested against the real binary.\n *\n * - event: `pre_tool_call`; stdin carries the tool name and its arguments\n * (`tool_name` + `args`, with `tool_input` accepted as a fallback spelling).\n * - deny: `{ \"action\": \"block\", \"reason\": … }`.\n * - rewrite: `{ \"action\": \"modify\", \"args\": { … } }` — a **shallow merge** into the\n * tool's arguments, so this schema sends only the `command` key and the harness\n * keeps the rest. The modify document has **no reason field**, so the substitution\n * is announced on stderr — a rewrite must never be silent.\n *\n * Carried caveat from the matrix: Hermes has a known bypass where memory tools ignore\n * `disabled_toolsets` (<https://github.com/NousResearch/hermes-agent/issues/46171>) —\n * tool gating there is leaky at the edges, so treat this hook as best-effort steering,\n * not a boundary.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['read_file', 'Read', 'ReadFile'],\n bashTools: ['execute_command', 'terminal', 'bash', 'Bash', 'shell'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['args', 'tool_input'],\n deny: (reason) => ({ action: 'block', reason }),\n rewrite: {\n // A shallow merge: only the key that changed travels, never the spliced whole.\n document: ({ suggestion }) => ({ action: 'modify', args: { command: suggestion } }),\n announce: 'stderr',\n },\n};\n\n/** Hermes hook config, as a mergeable snippet — their config is a home-level YAML. */\nfunction hermesHooksYaml(ctx: HarnessInstallContext): string {\n return `${SNIPPET_START_HASH}\n# Hermes Agent hook config for the smelt guard. EXPERIMENTAL tier: schema mapped from\n# the capability matrix (docs/research/2026-09-02-harness-capability-matrix.md, Hermes\n# row), not yet smoke-tested against the real binary. If Hermes does not read this\n# file directly, merge the \\`hooks:\\` section into ~/.hermes/config.yaml.\nhooks:\n pre_tool_call:\n - command: ${nodeCommand(ctx.cwd, shimScriptPath(hermes))}\n${SNIPPET_END_HASH}\n`;\n}\n\nexport const hermes: ShimmedHarnessProfile = {\n id: 'hermes',\n name: 'Hermes Agent',\n tier: 'experimental',\n detect: ['.hermes', '.hermes.md'],\n detectHome: ['.hermes'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: [\n 'Hermes memory tools bypass disabled_toolsets (NousResearch/hermes-agent#46171) — treat tool gating there as leaky',\n 'hook config may need merging into ~/.hermes/config.yaml by hand; the written file says how',\n ],\n hooks: HOOKS,\n install: [\n {\n kind: 'own-file',\n file: '.hermes/hooks.yaml',\n content: hermesHooksYaml,\n guardOnly: true,\n },\n ],\n};\n"]}
1
+ {"version":3,"file":"hermes.d.ts","sourceRoot":"","sources":["../../src/harness/hermes.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAyB,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAmDjF,eAAO,MAAM,MAAM,EAAE,qBAsBpB,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport { nodeCommand, shimScriptPath } from './paths.ts';\nimport type { HarnessInstallContext, ShimmedHarnessProfile } from './profile.ts';\nimport { SNIPPET_END_HASH, SNIPPET_START_HASH } from './snippet.ts';\n\n/**\n * Hermes Agent — 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, Hermes Agent row; primary\n * source <https://hermes-agent.nousresearch.com/docs/user-guide/features/hooks>), not\n * yet smoke-tested against the real binary.\n *\n * - event: `pre_tool_call`; stdin carries the tool name and its arguments\n * (`tool_name` + `args`, with `tool_input` accepted as a fallback spelling).\n * - deny: `{ \"action\": \"block\", \"reason\": … }`.\n * - rewrite: `{ \"action\": \"modify\", \"args\": { … } }` — a **shallow merge** into the\n * tool's arguments, so this schema sends only the `command` key and the harness\n * keeps the rest. The modify document has **no reason field**, so the substitution\n * is announced on stderr — a rewrite must never be silent.\n *\n * Carried caveat from the matrix: Hermes has a known bypass where memory tools ignore\n * `disabled_toolsets` (<https://github.com/NousResearch/hermes-agent/issues/46171>) —\n * tool gating there is leaky at the edges, so treat this hook as best-effort steering,\n * not a boundary.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['read_file', 'Read', 'ReadFile'],\n bashTools: ['execute_command', 'terminal', 'bash', 'Bash', 'shell'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['args', 'tool_input'],\n deny: (reason) => ({ action: 'block', reason }),\n rewrite: {\n // A shallow merge: only the key that changed travels, never the spliced whole.\n document: ({ suggestion }) => ({ action: 'modify', args: { command: suggestion } }),\n announce: 'stderr',\n },\n};\n\n/** Hermes hook config, as a mergeable snippet — their config is a home-level YAML. */\nfunction hermesHooksYaml(ctx: HarnessInstallContext): string {\n return `${SNIPPET_START_HASH}\n# Hermes Agent hook config for the smelt guard. EXPERIMENTAL tier: schema mapped from\n# the capability matrix (docs/research/2026-09-02-harness-capability-matrix.md, Hermes\n# row), not yet smoke-tested against the real binary. If Hermes does not read this\n# file directly, merge the \\`hooks:\\` section into ~/.hermes/config.yaml.\nhooks:\n pre_tool_call:\n - command: ${nodeCommand(ctx.cwd, shimScriptPath(hermes))}\n${SNIPPET_END_HASH}\n`;\n}\n\nexport const hermes: ShimmedHarnessProfile = {\n id: 'hermes',\n name: 'Hermes Agent',\n shortName: 'Hermes',\n tier: 'experimental',\n detect: ['.hermes', '.hermes.md'],\n detectHome: ['.hermes'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: [\n 'Hermes memory tools bypass disabled_toolsets (NousResearch/hermes-agent#46171) — treat tool gating there as leaky',\n 'hook config may need merging into ~/.hermes/config.yaml by hand; the written file says how',\n ],\n hooks: HOOKS,\n install: [\n {\n kind: 'own-file',\n file: '.hermes/hooks.yaml',\n content: hermesHooksYaml,\n guardOnly: true,\n },\n ],\n};\n"]}
@@ -49,6 +49,7 @@ ${SNIPPET_END_HASH}
49
49
  export const hermes = {
50
50
  id: 'hermes',
51
51
  name: 'Hermes Agent',
52
+ shortName: 'Hermes',
52
53
  tier: 'experimental',
53
54
  detect: ['.hermes', '.hermes.md'],
54
55
  detectHome: ['.hermes'],
@@ -1 +1 @@
1
- {"version":3,"file":"hermes.js","sourceRoot":"","sources":["../../src/harness/hermes.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,KAAK,GAAsB;IAC/B,SAAS,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC;IAC5C,SAAS,EAAE,CAAC,iBAAiB,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC;IACnE,YAAY,EAAE,CAAC,WAAW,CAAC;IAC3B,aAAa,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC;IACrC,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAC/C,OAAO,EAAE;QACP,+EAA+E;QAC/E,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,CAAC;QACnF,QAAQ,EAAE,QAAQ;KACnB;CACF,CAAC;AAEF,sFAAsF;AACtF,SAAS,eAAe,CAAC,GAA0B;IACjD,OAAO,GAAG,kBAAkB;;;;;;;iBAOb,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;EAC3D,gBAAgB;CACjB,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAA0B;IAC3C,EAAE,EAAE,QAAQ;IACZ,IAAI,EAAE,cAAc;IACpB,IAAI,EAAE,cAAc;IACpB,MAAM,EAAE,CAAC,SAAS,EAAE,YAAY,CAAC;IACjC,UAAU,EAAE,CAAC,SAAS,CAAC;IACvB,eAAe,EAAE,WAAW;IAC5B,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE;QACP,mHAAmH;QACnH,4FAA4F;KAC7F;IACD,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE;QACP;YACE,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,oBAAoB;YAC1B,OAAO,EAAE,eAAe;YACxB,SAAS,EAAE,IAAI;SAChB;KACF;CACF,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport { nodeCommand, shimScriptPath } from './paths.ts';\nimport type { HarnessInstallContext, ShimmedHarnessProfile } from './profile.ts';\nimport { SNIPPET_END_HASH, SNIPPET_START_HASH } from './snippet.ts';\n\n/**\n * Hermes Agent — 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, Hermes Agent row; primary\n * source <https://hermes-agent.nousresearch.com/docs/user-guide/features/hooks>), not\n * yet smoke-tested against the real binary.\n *\n * - event: `pre_tool_call`; stdin carries the tool name and its arguments\n * (`tool_name` + `args`, with `tool_input` accepted as a fallback spelling).\n * - deny: `{ \"action\": \"block\", \"reason\": … }`.\n * - rewrite: `{ \"action\": \"modify\", \"args\": { … } }` — a **shallow merge** into the\n * tool's arguments, so this schema sends only the `command` key and the harness\n * keeps the rest. The modify document has **no reason field**, so the substitution\n * is announced on stderr — a rewrite must never be silent.\n *\n * Carried caveat from the matrix: Hermes has a known bypass where memory tools ignore\n * `disabled_toolsets` (<https://github.com/NousResearch/hermes-agent/issues/46171>) —\n * tool gating there is leaky at the edges, so treat this hook as best-effort steering,\n * not a boundary.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['read_file', 'Read', 'ReadFile'],\n bashTools: ['execute_command', 'terminal', 'bash', 'Bash', 'shell'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['args', 'tool_input'],\n deny: (reason) => ({ action: 'block', reason }),\n rewrite: {\n // A shallow merge: only the key that changed travels, never the spliced whole.\n document: ({ suggestion }) => ({ action: 'modify', args: { command: suggestion } }),\n announce: 'stderr',\n },\n};\n\n/** Hermes hook config, as a mergeable snippet — their config is a home-level YAML. */\nfunction hermesHooksYaml(ctx: HarnessInstallContext): string {\n return `${SNIPPET_START_HASH}\n# Hermes Agent hook config for the smelt guard. EXPERIMENTAL tier: schema mapped from\n# the capability matrix (docs/research/2026-09-02-harness-capability-matrix.md, Hermes\n# row), not yet smoke-tested against the real binary. If Hermes does not read this\n# file directly, merge the \\`hooks:\\` section into ~/.hermes/config.yaml.\nhooks:\n pre_tool_call:\n - command: ${nodeCommand(ctx.cwd, shimScriptPath(hermes))}\n${SNIPPET_END_HASH}\n`;\n}\n\nexport const hermes: ShimmedHarnessProfile = {\n id: 'hermes',\n name: 'Hermes Agent',\n tier: 'experimental',\n detect: ['.hermes', '.hermes.md'],\n detectHome: ['.hermes'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: [\n 'Hermes memory tools bypass disabled_toolsets (NousResearch/hermes-agent#46171) — treat tool gating there as leaky',\n 'hook config may need merging into ~/.hermes/config.yaml by hand; the written file says how',\n ],\n hooks: HOOKS,\n install: [\n {\n kind: 'own-file',\n file: '.hermes/hooks.yaml',\n content: hermesHooksYaml,\n guardOnly: true,\n },\n ],\n};\n"]}
1
+ {"version":3,"file":"hermes.js","sourceRoot":"","sources":["../../src/harness/hermes.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,KAAK,GAAsB;IAC/B,SAAS,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC;IAC5C,SAAS,EAAE,CAAC,iBAAiB,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC;IACnE,YAAY,EAAE,CAAC,WAAW,CAAC;IAC3B,aAAa,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC;IACrC,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAC/C,OAAO,EAAE;QACP,+EAA+E;QAC/E,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,CAAC;QACnF,QAAQ,EAAE,QAAQ;KACnB;CACF,CAAC;AAEF,sFAAsF;AACtF,SAAS,eAAe,CAAC,GAA0B;IACjD,OAAO,GAAG,kBAAkB;;;;;;;iBAOb,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;EAC3D,gBAAgB;CACjB,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAA0B;IAC3C,EAAE,EAAE,QAAQ;IACZ,IAAI,EAAE,cAAc;IACpB,SAAS,EAAE,QAAQ;IACnB,IAAI,EAAE,cAAc;IACpB,MAAM,EAAE,CAAC,SAAS,EAAE,YAAY,CAAC;IACjC,UAAU,EAAE,CAAC,SAAS,CAAC;IACvB,eAAe,EAAE,WAAW;IAC5B,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE;QACP,mHAAmH;QACnH,4FAA4F;KAC7F;IACD,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE;QACP;YACE,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,oBAAoB;YAC1B,OAAO,EAAE,eAAe;YACxB,SAAS,EAAE,IAAI;SAChB;KACF;CACF,CAAC","sourcesContent":["import type { HarnessHookSchema } from '../hooks/shim.ts';\n\nimport { nodeCommand, shimScriptPath } from './paths.ts';\nimport type { HarnessInstallContext, ShimmedHarnessProfile } from './profile.ts';\nimport { SNIPPET_END_HASH, SNIPPET_START_HASH } from './snippet.ts';\n\n/**\n * Hermes Agent — 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, Hermes Agent row; primary\n * source <https://hermes-agent.nousresearch.com/docs/user-guide/features/hooks>), not\n * yet smoke-tested against the real binary.\n *\n * - event: `pre_tool_call`; stdin carries the tool name and its arguments\n * (`tool_name` + `args`, with `tool_input` accepted as a fallback spelling).\n * - deny: `{ \"action\": \"block\", \"reason\": … }`.\n * - rewrite: `{ \"action\": \"modify\", \"args\": { … } }` — a **shallow merge** into the\n * tool's arguments, so this schema sends only the `command` key and the harness\n * keeps the rest. The modify document has **no reason field**, so the substitution\n * is announced on stderr — a rewrite must never be silent.\n *\n * Carried caveat from the matrix: Hermes has a known bypass where memory tools ignore\n * `disabled_toolsets` (<https://github.com/NousResearch/hermes-agent/issues/46171>) —\n * tool gating there is leaky at the edges, so treat this hook as best-effort steering,\n * not a boundary.\n */\nconst HOOKS: HarnessHookSchema = {\n readTools: ['read_file', 'Read', 'ReadFile'],\n bashTools: ['execute_command', 'terminal', 'bash', 'Bash', 'shell'],\n toolNameKeys: ['tool_name'],\n toolInputKeys: ['args', 'tool_input'],\n deny: (reason) => ({ action: 'block', reason }),\n rewrite: {\n // A shallow merge: only the key that changed travels, never the spliced whole.\n document: ({ suggestion }) => ({ action: 'modify', args: { command: suggestion } }),\n announce: 'stderr',\n },\n};\n\n/** Hermes hook config, as a mergeable snippet — their config is a home-level YAML. */\nfunction hermesHooksYaml(ctx: HarnessInstallContext): string {\n return `${SNIPPET_START_HASH}\n# Hermes Agent hook config for the smelt guard. EXPERIMENTAL tier: schema mapped from\n# the capability matrix (docs/research/2026-09-02-harness-capability-matrix.md, Hermes\n# row), not yet smoke-tested against the real binary. If Hermes does not read this\n# file directly, merge the \\`hooks:\\` section into ~/.hermes/config.yaml.\nhooks:\n pre_tool_call:\n - command: ${nodeCommand(ctx.cwd, shimScriptPath(hermes))}\n${SNIPPET_END_HASH}\n`;\n}\n\nexport const hermes: ShimmedHarnessProfile = {\n id: 'hermes',\n name: 'Hermes Agent',\n shortName: 'Hermes',\n tier: 'experimental',\n detect: ['.hermes', '.hermes.md'],\n detectHome: ['.hermes'],\n instructionFile: 'AGENTS.md',\n instructions: 'snippet',\n caveats: [\n 'Hermes memory tools bypass disabled_toolsets (NousResearch/hermes-agent#46171) — treat tool gating there as leaky',\n 'hook config may need merging into ~/.hermes/config.yaml by hand; the written file says how',\n ],\n hooks: HOOKS,\n install: [\n {\n kind: 'own-file',\n file: '.hermes/hooks.yaml',\n content: hermesHooksYaml,\n guardOnly: true,\n },\n ],\n};\n"]}
@@ -31,6 +31,15 @@ export interface HarnessProfile {
31
31
  readonly id: HarnessId;
32
32
  /** The harness's own name, as its makers spell it. Shown wherever a tier is. */
33
33
  readonly name: string;
34
+ /**
35
+ * The name a *list* of harnesses uses, where the maker's own spelling carries a
36
+ * category suffix a list does not need — "Codex CLI" is "Codex" in a tier clause,
37
+ * "Hermes Agent" is "Hermes". Optional, and defaulted by {@link harnessLabel} to
38
+ * {@link name}: only a harness whose prose name differs from its full name carries
39
+ * one, and it carries it here rather than in each of the four places a tier list is
40
+ * rendered. "Claude Code" has no suffix to drop, and so has none.
41
+ */
42
+ readonly shortName?: string;
34
43
  readonly tier: HarnessTier;
35
44
  /** Paths (relative to the project) whose existence means "this harness is in use here". */
36
45
  readonly detect: readonly string[];
@@ -81,6 +90,19 @@ export type HarnessTier = 'verified' | 'experimental' | 'advisory';
81
90
  export type HarnessId = 'claude-code' | 'codex' | 'gemini' | 'grok' | 'hermes' | 'cursor' | 'opencode' | 'cline' | 'kilocode' | 'aider';
82
91
  /** One line of honesty per tier, shown wherever a tier label appears. */
83
92
  export declare const TIER_HONESTY: Record<HarnessTier, string>;
93
+ /**
94
+ * The tiers, in the order every rendered tier list walks them — most claimed first.
95
+ * {@link TIER_HONESTY}'s key order *is* that order, so a tier cannot exist without a
96
+ * line saying what it means, and the order lives once.
97
+ */
98
+ export declare const HARNESS_TIERS: readonly HarnessTier[];
99
+ /**
100
+ * How a list of harnesses spells this one: {@link HarnessProfile.shortName} where the
101
+ * maker's own name carries a suffix a list does not need, else {@link
102
+ * HarnessProfile.name}. The help's tier clause, the wizard's sentences and the site's
103
+ * tier table all render through this, so they cannot spell a harness three ways.
104
+ */
105
+ export declare function harnessLabel(profile: HarnessProfile): string;
84
106
  /**
85
107
  * What the wizard settled on, as the installer's renderers see it: the toggles, the
86
108
  * guard's settings, and the project directory every path is relative to. A renderer
@@ -92,6 +114,8 @@ export interface HarnessInstallContext {
92
114
  readonly guard: boolean;
93
115
  readonly statsOnStop: boolean;
94
116
  readonly mapOnStart: boolean;
117
+ /** Opt-in `smelt agents lint` at session start — shares SessionStart with the map. */
118
+ readonly lintOnStart: boolean;
95
119
  readonly thresholdBytes: number;
96
120
  /** The `--budget` every suggested command and the snippet quote. */
97
121
  readonly budgetBytes: number;
@@ -128,7 +152,8 @@ export interface HarnessJsonHooks {
128
152
  readonly entry: 'command-list' | 'bare-command';
129
153
  /**
130
154
  * True for a harness whose schema also carries the session-lifecycle events this
131
- * preset offers — `smelt stats` on Stop, `smelt map` on SessionStart. The other
155
+ * preset offers — `smelt stats` on Stop, `smelt map` and `smelt agents lint` on
156
+ * SessionStart. The other
132
157
  * harnesses wire the guard only, and the wizard's toggles say so.
133
158
  */
134
159
  readonly lifecycle: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../src/harness/profile.ts"],"names":[],"mappings":"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","sourcesContent":["import { shimFromSchema } from '../hooks/shim.ts';\nimport type { HarnessHookSchema, ShimAdapter } from '../hooks/shim.ts';\n\n/**\n * Everything smelt knows about one agent harness, in one place.\n *\n * Before this module existed, a harness's facts lived at seven edit sites: the\n * `HarnessId` union, the `HARNESSES` array, a shim file, a case in `planInstall`, a\n * case in `planRemove`, `MANAGED_EVENTS`, a test fixture — plus a hand-typed id list\n * in the `--harness` help text, which could not be derived because the registry lived\n * in `cli/hooks.ts` and `cli/hooks.ts` imports `CLI_NAME` from `cli/args.ts`. The\n * registry was in the wrong module, and the cycle was the proof.\n *\n * A `HarnessProfile` is the single adapter carrying every per-harness fact, and the\n * registry (`registry.ts`) is `Record<HarnessId, HarnessProfile>` so totality stays a\n * compile error: a new `HarnessId` without a profile does not build. It imports\n * nothing from `cli/`, so `cli/args.ts` derives the `--harness` list and every other\n * rendered view is derived too — the managed event names, the files a re-run reads its\n * toggles back from, the id list in an error message.\n *\n * Installing and removing are **data**, not switch statements: `install` is the list\n * of artefacts `smelt hooks install` writes for this harness, and each artefact's kind\n * is also how `smelt hooks remove` takes it back out — a JSON hook file is merged and\n * strip-merged, a marker block is upserted and stripped, a file that is entirely ours\n * is written and deleted. `planInstall`/`planRemove` fold over the list.\n *\n * The optional sections state capability, never invent it: a harness ships a shim\n * exactly when it carries a {@link hooks} schema (or, for a harness a table cannot\n * express, a hand-written {@link shim}), and only such a profile has a shim script\n * path. What a profile does not claim, the installer does not write.\n */\nexport interface HarnessProfile {\n readonly id: HarnessId;\n /** The harness's own name, as its makers spell it. Shown wherever a tier is. */\n readonly name: string;\n readonly tier: HarnessTier;\n /** Paths (relative to the project) whose existence means \"this harness is in use here\". */\n readonly detect: readonly string[];\n /** Paths relative to the user's home directory — \"installed on this machine\". */\n readonly detectHome: readonly string[];\n /** The standing-instructions file this harness reads (capability matrix column d). */\n readonly instructionFile: string;\n /**\n * The standing-instructions layer — belt and braces under every shim, and the *only*\n * layer an advisory harness has:\n *\n * - `'snippet'`: upsert the shared marker-bracketed block into\n * {@link instructionFile} (a file several harnesses read, like `AGENTS.md`, is\n * planned once); `remove` strips the block and leaves every other byte.\n * - a renderer: a whole file this harness owns, snippet included — KiloCode's rules\n * file carries two manual enforcement legs the shared snippet has no room for;\n * `remove` deletes it.\n */\n readonly instructions: 'snippet' | HarnessFileContent;\n /** Caveats carried from the capability matrix, shown at install time. */\n readonly caveats: readonly string[];\n /**\n * Everything else `hooks install` writes for this harness, in the order it is\n * written and listed. Empty means instructions only: the advisory tier, where\n * nothing enforces anything and the output says so.\n */\n readonly install: readonly HarnessInstallStep[];\n /**\n * This harness's native pre-tool hook schema, as data. Present exactly when the\n * harness ships a shim script (`dist/hooks/shims/<id>.js`) — absent for the advisory\n * tier, and for opencode, whose hook API is a JavaScript plugin rather than a stdin\n * schema.\n */\n readonly hooks?: HarnessHookSchema;\n /**\n * The escape hatch: a hand-written adapter, for a harness whose schema a table\n * cannot express. Wins over {@link hooks} when both are present.\n */\n readonly shim?: ShimAdapter;\n}\n\n/** How much smelt is willing to claim about a harness. */\nexport type HarnessTier = 'verified' | 'experimental' | 'advisory';\n\n/**\n * Every harness the preset knows. The registry is keyed by this union, so adding an id\n * without writing its profile is a compile error — and the `--harness` help list, the\n * wizard's table and every error message read the registry, never a second list.\n */\nexport type HarnessId =\n | 'claude-code'\n | 'codex'\n | 'gemini'\n | 'grok'\n | 'hermes'\n | 'cursor'\n | 'opencode'\n | 'cline'\n | 'kilocode'\n | 'aider';\n\n/** One line of honesty per tier, shown wherever a tier label appears. */\nexport const TIER_HONESTY: Record<HarnessTier, string> = {\n verified: 'schema verified against primary docs and pinned by fixtures',\n experimental:\n 'schema mapped from the 2026-09-02 capability matrix, not yet smoke-tested against the real binary',\n advisory: 'no usable hook API — instructions only, nothing enforces them',\n};\n\n/**\n * What the wizard settled on, as the installer's renderers see it: the toggles, the\n * guard's settings, and the project directory every path is relative to. A renderer\n * takes this and returns bytes; nothing writes.\n */\nexport interface HarnessInstallContext {\n /** Project directory: every path a renderer emits is portable relative to it. */\n readonly cwd: string;\n readonly guard: boolean;\n readonly statsOnStop: boolean;\n readonly mapOnStart: boolean;\n readonly thresholdBytes: number;\n /** The `--budget` every suggested command and the snippet quote. */\n readonly budgetBytes: number;\n}\n\n/** A file's bytes, rendered from the wizard's choices. */\nexport type HarnessFileContent = (ctx: HarnessInstallContext) => string;\n\n/**\n * One artefact `hooks install` writes. The kind is also the un-write: `json-hooks` is\n * merged in and strip-merged out, `marker-block` is upserted and stripped,\n * `own-file` is written and deleted.\n */\nexport type HarnessInstallStep = HarnessJsonHooks | HarnessMarkerBlock | HarnessOwnFile;\n\n/**\n * A JSON settings/hooks file the harness reads. Our entries are merged in\n * byte-faithfully and, on `remove`, stripped back out with everything foreign left\n * exactly as it was.\n */\nexport interface HarnessJsonHooks {\n readonly kind: 'json-hooks';\n /** Project-relative path of the file. */\n readonly file: string;\n /** The pre-tool event, in this harness's spelling (`PreToolUse`, `BeforeTool`, …). */\n readonly event: string;\n /**\n * One hook entry per matcher — the tool names the guard wants to see. `undefined`\n * is a matcher-less entry, for a harness whose hook fires on every tool.\n */\n readonly matchers: readonly (string | undefined)[];\n /**\n * The entry shape: `'command-list'` is Claude Code's `{matcher, hooks:[{type,\n * command}]}`, which Codex, Gemini and Grok mirror; `'bare-command'` is Cursor's\n * `{command}`.\n */\n readonly entry: 'command-list' | 'bare-command';\n /**\n * True for a harness whose schema also carries the session-lifecycle events this\n * preset offers — `smelt stats` on Stop, `smelt map` on SessionStart. The other\n * harnesses wire the guard only, and the wizard's toggles say so.\n */\n readonly lifecycle: boolean;\n /** Top-level keys a *fresh* file must carry (Cursor's `version: 1`). */\n readonly shape?: { readonly version: number };\n}\n\n/** A marker-bracketed block inside a file smelt shares with its owner. */\nexport interface HarnessMarkerBlock {\n readonly kind: 'marker-block';\n readonly file: string;\n readonly block: HarnessFileContent;\n /** The marker line opening the block — also how `remove` finds it. */\n readonly start: string;\n readonly end: string;\n /**\n * Refuse the file instead of editing it when it already carries `contains` and none\n * of ours: Codex's `config.toml` may have a hand-written `[features]` table, and an\n * installer that merged into it would be editing what it was never asked to.\n */\n readonly skipWhen?: { readonly contains: string; readonly why: string };\n}\n\n/** A file that is entirely smelt's: written whole, deleted whole. */\nexport interface HarnessOwnFile {\n readonly kind: 'own-file';\n readonly file: string;\n readonly content: HarnessFileContent;\n /** chmod after writing (Cline's hook must be executable). */\n readonly mode?: number;\n /** True when the file exists only to wire the guard — the guard toggle gates it. */\n readonly guardOnly: boolean;\n}\n\n/**\n * A profile that ships a shim script. Only these have a shim path: `shimScriptPath`\n * takes one, so a harness with no shim cannot name a script that was never built.\n */\nexport type ShimmedHarnessProfile = HarnessProfile &\n ({ readonly hooks: HarnessHookSchema } | { readonly shim: ShimAdapter });\n\n/** True for a profile that ships a shim script — the narrowing `shimScriptPath` needs. */\nexport function hasShim(profile: HarnessProfile): profile is ShimmedHarnessProfile {\n return profile.hooks !== undefined || profile.shim !== undefined;\n}\n\n/**\n * The adapter this profile's shim script runs: the one its {@link HarnessProfile.hooks}\n * schema describes, or the hand-written escape hatch where it carries one.\n */\nexport function shimAdapterOf(profile: ShimmedHarnessProfile): ShimAdapter {\n const { hooks, shim } = profile;\n if (shim !== undefined) return shim;\n /* v8 ignore next 5 -- unreachable: a ShimmedHarnessProfile carries one or the other */\n if (hooks === undefined) {\n throw new Error(\n `smelt: harness \"${profile.id}\" claims a shim but carries neither a hook schema nor an adapter.`,\n );\n }\n return shimFromSchema(hooks);\n}\n"]}
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../src/harness/profile.ts"],"names":[],"mappings":"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","sourcesContent":["import { shimFromSchema } from '../hooks/shim.ts';\nimport type { HarnessHookSchema, ShimAdapter } from '../hooks/shim.ts';\n\n/**\n * Everything smelt knows about one agent harness, in one place.\n *\n * Before this module existed, a harness's facts lived at seven edit sites: the\n * `HarnessId` union, the `HARNESSES` array, a shim file, a case in `planInstall`, a\n * case in `planRemove`, `MANAGED_EVENTS`, a test fixture — plus a hand-typed id list\n * in the `--harness` help text, which could not be derived because the registry lived\n * in `cli/hooks.ts` and `cli/hooks.ts` imports `CLI_NAME` from `cli/args.ts`. The\n * registry was in the wrong module, and the cycle was the proof.\n *\n * A `HarnessProfile` is the single adapter carrying every per-harness fact, and the\n * registry (`registry.ts`) is `Record<HarnessId, HarnessProfile>` so totality stays a\n * compile error: a new `HarnessId` without a profile does not build. It imports\n * nothing from `cli/`, so `cli/args.ts` derives the `--harness` list and every other\n * rendered view is derived too — the managed event names, the files a re-run reads its\n * toggles back from, the id list in an error message.\n *\n * Installing and removing are **data**, not switch statements: `install` is the list\n * of artefacts `smelt hooks install` writes for this harness, and each artefact's kind\n * is also how `smelt hooks remove` takes it back out — a JSON hook file is merged and\n * strip-merged, a marker block is upserted and stripped, a file that is entirely ours\n * is written and deleted. `planInstall`/`planRemove` fold over the list.\n *\n * The optional sections state capability, never invent it: a harness ships a shim\n * exactly when it carries a {@link hooks} schema (or, for a harness a table cannot\n * express, a hand-written {@link shim}), and only such a profile has a shim script\n * path. What a profile does not claim, the installer does not write.\n */\nexport interface HarnessProfile {\n readonly id: HarnessId;\n /** The harness's own name, as its makers spell it. Shown wherever a tier is. */\n readonly name: string;\n /**\n * The name a *list* of harnesses uses, where the maker's own spelling carries a\n * category suffix a list does not need — \"Codex CLI\" is \"Codex\" in a tier clause,\n * \"Hermes Agent\" is \"Hermes\". Optional, and defaulted by {@link harnessLabel} to\n * {@link name}: only a harness whose prose name differs from its full name carries\n * one, and it carries it here rather than in each of the four places a tier list is\n * rendered. \"Claude Code\" has no suffix to drop, and so has none.\n */\n readonly shortName?: string;\n readonly tier: HarnessTier;\n /** Paths (relative to the project) whose existence means \"this harness is in use here\". */\n readonly detect: readonly string[];\n /** Paths relative to the user's home directory — \"installed on this machine\". */\n readonly detectHome: readonly string[];\n /** The standing-instructions file this harness reads (capability matrix column d). */\n readonly instructionFile: string;\n /**\n * The standing-instructions layer — belt and braces under every shim, and the *only*\n * layer an advisory harness has:\n *\n * - `'snippet'`: upsert the shared marker-bracketed block into\n * {@link instructionFile} (a file several harnesses read, like `AGENTS.md`, is\n * planned once); `remove` strips the block and leaves every other byte.\n * - a renderer: a whole file this harness owns, snippet included — KiloCode's rules\n * file carries two manual enforcement legs the shared snippet has no room for;\n * `remove` deletes it.\n */\n readonly instructions: 'snippet' | HarnessFileContent;\n /** Caveats carried from the capability matrix, shown at install time. */\n readonly caveats: readonly string[];\n /**\n * Everything else `hooks install` writes for this harness, in the order it is\n * written and listed. Empty means instructions only: the advisory tier, where\n * nothing enforces anything and the output says so.\n */\n readonly install: readonly HarnessInstallStep[];\n /**\n * This harness's native pre-tool hook schema, as data. Present exactly when the\n * harness ships a shim script (`dist/hooks/shims/<id>.js`) — absent for the advisory\n * tier, and for opencode, whose hook API is a JavaScript plugin rather than a stdin\n * schema.\n */\n readonly hooks?: HarnessHookSchema;\n /**\n * The escape hatch: a hand-written adapter, for a harness whose schema a table\n * cannot express. Wins over {@link hooks} when both are present.\n */\n readonly shim?: ShimAdapter;\n}\n\n/** How much smelt is willing to claim about a harness. */\nexport type HarnessTier = 'verified' | 'experimental' | 'advisory';\n\n/**\n * Every harness the preset knows. The registry is keyed by this union, so adding an id\n * without writing its profile is a compile error — and the `--harness` help list, the\n * wizard's table and every error message read the registry, never a second list.\n */\nexport type HarnessId =\n | 'claude-code'\n | 'codex'\n | 'gemini'\n | 'grok'\n | 'hermes'\n | 'cursor'\n | 'opencode'\n | 'cline'\n | 'kilocode'\n | 'aider';\n\n/** One line of honesty per tier, shown wherever a tier label appears. */\nexport const TIER_HONESTY: Record<HarnessTier, string> = {\n verified: 'schema verified against primary docs and pinned by fixtures',\n experimental:\n 'schema mapped from the 2026-09-02 capability matrix, not yet smoke-tested against the real binary',\n advisory: 'no usable hook API — instructions only, nothing enforces them',\n};\n\n/**\n * The tiers, in the order every rendered tier list walks them — most claimed first.\n * {@link TIER_HONESTY}'s key order *is* that order, so a tier cannot exist without a\n * line saying what it means, and the order lives once.\n */\nexport const HARNESS_TIERS: readonly HarnessTier[] = Object.keys(TIER_HONESTY) as HarnessTier[];\n\n/**\n * How a list of harnesses spells this one: {@link HarnessProfile.shortName} where the\n * maker's own name carries a suffix a list does not need, else {@link\n * HarnessProfile.name}. The help's tier clause, the wizard's sentences and the site's\n * tier table all render through this, so they cannot spell a harness three ways.\n */\nexport function harnessLabel(profile: HarnessProfile): string {\n return profile.shortName ?? profile.name;\n}\n\n/**\n * What the wizard settled on, as the installer's renderers see it: the toggles, the\n * guard's settings, and the project directory every path is relative to. A renderer\n * takes this and returns bytes; nothing writes.\n */\nexport interface HarnessInstallContext {\n /** Project directory: every path a renderer emits is portable relative to it. */\n readonly cwd: string;\n readonly guard: boolean;\n readonly statsOnStop: boolean;\n readonly mapOnStart: boolean;\n /** Opt-in `smelt agents lint` at session start — shares SessionStart with the map. */\n readonly lintOnStart: boolean;\n readonly thresholdBytes: number;\n /** The `--budget` every suggested command and the snippet quote. */\n readonly budgetBytes: number;\n}\n\n/** A file's bytes, rendered from the wizard's choices. */\nexport type HarnessFileContent = (ctx: HarnessInstallContext) => string;\n\n/**\n * One artefact `hooks install` writes. The kind is also the un-write: `json-hooks` is\n * merged in and strip-merged out, `marker-block` is upserted and stripped,\n * `own-file` is written and deleted.\n */\nexport type HarnessInstallStep = HarnessJsonHooks | HarnessMarkerBlock | HarnessOwnFile;\n\n/**\n * A JSON settings/hooks file the harness reads. Our entries are merged in\n * byte-faithfully and, on `remove`, stripped back out with everything foreign left\n * exactly as it was.\n */\nexport interface HarnessJsonHooks {\n readonly kind: 'json-hooks';\n /** Project-relative path of the file. */\n readonly file: string;\n /** The pre-tool event, in this harness's spelling (`PreToolUse`, `BeforeTool`, …). */\n readonly event: string;\n /**\n * One hook entry per matcher — the tool names the guard wants to see. `undefined`\n * is a matcher-less entry, for a harness whose hook fires on every tool.\n */\n readonly matchers: readonly (string | undefined)[];\n /**\n * The entry shape: `'command-list'` is Claude Code's `{matcher, hooks:[{type,\n * command}]}`, which Codex, Gemini and Grok mirror; `'bare-command'` is Cursor's\n * `{command}`.\n */\n readonly entry: 'command-list' | 'bare-command';\n /**\n * True for a harness whose schema also carries the session-lifecycle events this\n * preset offers — `smelt stats` on Stop, `smelt map` and `smelt agents lint` on\n * SessionStart. The other\n * harnesses wire the guard only, and the wizard's toggles say so.\n */\n readonly lifecycle: boolean;\n /** Top-level keys a *fresh* file must carry (Cursor's `version: 1`). */\n readonly shape?: { readonly version: number };\n}\n\n/** A marker-bracketed block inside a file smelt shares with its owner. */\nexport interface HarnessMarkerBlock {\n readonly kind: 'marker-block';\n readonly file: string;\n readonly block: HarnessFileContent;\n /** The marker line opening the block — also how `remove` finds it. */\n readonly start: string;\n readonly end: string;\n /**\n * Refuse the file instead of editing it when it already carries `contains` and none\n * of ours: Codex's `config.toml` may have a hand-written `[features]` table, and an\n * installer that merged into it would be editing what it was never asked to.\n */\n readonly skipWhen?: { readonly contains: string; readonly why: string };\n}\n\n/** A file that is entirely smelt's: written whole, deleted whole. */\nexport interface HarnessOwnFile {\n readonly kind: 'own-file';\n readonly file: string;\n readonly content: HarnessFileContent;\n /** chmod after writing (Cline's hook must be executable). */\n readonly mode?: number;\n /** True when the file exists only to wire the guard — the guard toggle gates it. */\n readonly guardOnly: boolean;\n}\n\n/**\n * A profile that ships a shim script. Only these have a shim path: `shimScriptPath`\n * takes one, so a harness with no shim cannot name a script that was never built.\n */\nexport type ShimmedHarnessProfile = HarnessProfile &\n ({ readonly hooks: HarnessHookSchema } | { readonly shim: ShimAdapter });\n\n/** True for a profile that ships a shim script — the narrowing `shimScriptPath` needs. */\nexport function hasShim(profile: HarnessProfile): profile is ShimmedHarnessProfile {\n return profile.hooks !== undefined || profile.shim !== undefined;\n}\n\n/**\n * The adapter this profile's shim script runs: the one its {@link HarnessProfile.hooks}\n * schema describes, or the hand-written escape hatch where it carries one.\n */\nexport function shimAdapterOf(profile: ShimmedHarnessProfile): ShimAdapter {\n const { hooks, shim } = profile;\n if (shim !== undefined) return shim;\n /* v8 ignore next 5 -- unreachable: a ShimmedHarnessProfile carries one or the other */\n if (hooks === undefined) {\n throw new Error(\n `smelt: harness \"${profile.id}\" claims a shim but carries neither a hook schema nor an adapter.`,\n );\n }\n return shimFromSchema(hooks);\n}\n"]}
@@ -5,6 +5,21 @@ export const TIER_HONESTY = {
5
5
  experimental: 'schema mapped from the 2026-09-02 capability matrix, not yet smoke-tested against the real binary',
6
6
  advisory: 'no usable hook API — instructions only, nothing enforces them',
7
7
  };
8
+ /**
9
+ * The tiers, in the order every rendered tier list walks them — most claimed first.
10
+ * {@link TIER_HONESTY}'s key order *is* that order, so a tier cannot exist without a
11
+ * line saying what it means, and the order lives once.
12
+ */
13
+ export const HARNESS_TIERS = Object.keys(TIER_HONESTY);
14
+ /**
15
+ * How a list of harnesses spells this one: {@link HarnessProfile.shortName} where the
16
+ * maker's own name carries a suffix a list does not need, else {@link
17
+ * HarnessProfile.name}. The help's tier clause, the wizard's sentences and the site's
18
+ * tier table all render through this, so they cannot spell a harness three ways.
19
+ */
20
+ export function harnessLabel(profile) {
21
+ return profile.shortName ?? profile.name;
22
+ }
8
23
  /** True for a profile that ships a shim script — the narrowing `shimScriptPath` needs. */
9
24
  export function hasShim(profile) {
10
25
  return profile.hooks !== undefined || profile.shim !== undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/harness/profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAgGlD,yEAAyE;AACzE,MAAM,CAAC,MAAM,YAAY,GAAgC;IACvD,QAAQ,EAAE,6DAA6D;IACvE,YAAY,EACV,mGAAmG;IACrG,QAAQ,EAAE,+DAA+D;CAC1E,CAAC;AA8FF,0FAA0F;AAC1F,MAAM,UAAU,OAAO,CAAC,OAAuB;IAC7C,OAAO,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC;AACnE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,OAA8B;IAC1D,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IAChC,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACpC,uFAAuF;IACvF,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,mBAAmB,OAAO,CAAC,EAAE,mEAAmE,CACjG,CAAC;IACJ,CAAC;IACD,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC","sourcesContent":["import { shimFromSchema } from '../hooks/shim.ts';\nimport type { HarnessHookSchema, ShimAdapter } from '../hooks/shim.ts';\n\n/**\n * Everything smelt knows about one agent harness, in one place.\n *\n * Before this module existed, a harness's facts lived at seven edit sites: the\n * `HarnessId` union, the `HARNESSES` array, a shim file, a case in `planInstall`, a\n * case in `planRemove`, `MANAGED_EVENTS`, a test fixture — plus a hand-typed id list\n * in the `--harness` help text, which could not be derived because the registry lived\n * in `cli/hooks.ts` and `cli/hooks.ts` imports `CLI_NAME` from `cli/args.ts`. The\n * registry was in the wrong module, and the cycle was the proof.\n *\n * A `HarnessProfile` is the single adapter carrying every per-harness fact, and the\n * registry (`registry.ts`) is `Record<HarnessId, HarnessProfile>` so totality stays a\n * compile error: a new `HarnessId` without a profile does not build. It imports\n * nothing from `cli/`, so `cli/args.ts` derives the `--harness` list and every other\n * rendered view is derived too — the managed event names, the files a re-run reads its\n * toggles back from, the id list in an error message.\n *\n * Installing and removing are **data**, not switch statements: `install` is the list\n * of artefacts `smelt hooks install` writes for this harness, and each artefact's kind\n * is also how `smelt hooks remove` takes it back out — a JSON hook file is merged and\n * strip-merged, a marker block is upserted and stripped, a file that is entirely ours\n * is written and deleted. `planInstall`/`planRemove` fold over the list.\n *\n * The optional sections state capability, never invent it: a harness ships a shim\n * exactly when it carries a {@link hooks} schema (or, for a harness a table cannot\n * express, a hand-written {@link shim}), and only such a profile has a shim script\n * path. What a profile does not claim, the installer does not write.\n */\nexport interface HarnessProfile {\n readonly id: HarnessId;\n /** The harness's own name, as its makers spell it. Shown wherever a tier is. */\n readonly name: string;\n readonly tier: HarnessTier;\n /** Paths (relative to the project) whose existence means \"this harness is in use here\". */\n readonly detect: readonly string[];\n /** Paths relative to the user's home directory — \"installed on this machine\". */\n readonly detectHome: readonly string[];\n /** The standing-instructions file this harness reads (capability matrix column d). */\n readonly instructionFile: string;\n /**\n * The standing-instructions layer — belt and braces under every shim, and the *only*\n * layer an advisory harness has:\n *\n * - `'snippet'`: upsert the shared marker-bracketed block into\n * {@link instructionFile} (a file several harnesses read, like `AGENTS.md`, is\n * planned once); `remove` strips the block and leaves every other byte.\n * - a renderer: a whole file this harness owns, snippet included — KiloCode's rules\n * file carries two manual enforcement legs the shared snippet has no room for;\n * `remove` deletes it.\n */\n readonly instructions: 'snippet' | HarnessFileContent;\n /** Caveats carried from the capability matrix, shown at install time. */\n readonly caveats: readonly string[];\n /**\n * Everything else `hooks install` writes for this harness, in the order it is\n * written and listed. Empty means instructions only: the advisory tier, where\n * nothing enforces anything and the output says so.\n */\n readonly install: readonly HarnessInstallStep[];\n /**\n * This harness's native pre-tool hook schema, as data. Present exactly when the\n * harness ships a shim script (`dist/hooks/shims/<id>.js`) — absent for the advisory\n * tier, and for opencode, whose hook API is a JavaScript plugin rather than a stdin\n * schema.\n */\n readonly hooks?: HarnessHookSchema;\n /**\n * The escape hatch: a hand-written adapter, for a harness whose schema a table\n * cannot express. Wins over {@link hooks} when both are present.\n */\n readonly shim?: ShimAdapter;\n}\n\n/** How much smelt is willing to claim about a harness. */\nexport type HarnessTier = 'verified' | 'experimental' | 'advisory';\n\n/**\n * Every harness the preset knows. The registry is keyed by this union, so adding an id\n * without writing its profile is a compile error — and the `--harness` help list, the\n * wizard's table and every error message read the registry, never a second list.\n */\nexport type HarnessId =\n | 'claude-code'\n | 'codex'\n | 'gemini'\n | 'grok'\n | 'hermes'\n | 'cursor'\n | 'opencode'\n | 'cline'\n | 'kilocode'\n | 'aider';\n\n/** One line of honesty per tier, shown wherever a tier label appears. */\nexport const TIER_HONESTY: Record<HarnessTier, string> = {\n verified: 'schema verified against primary docs and pinned by fixtures',\n experimental:\n 'schema mapped from the 2026-09-02 capability matrix, not yet smoke-tested against the real binary',\n advisory: 'no usable hook API — instructions only, nothing enforces them',\n};\n\n/**\n * What the wizard settled on, as the installer's renderers see it: the toggles, the\n * guard's settings, and the project directory every path is relative to. A renderer\n * takes this and returns bytes; nothing writes.\n */\nexport interface HarnessInstallContext {\n /** Project directory: every path a renderer emits is portable relative to it. */\n readonly cwd: string;\n readonly guard: boolean;\n readonly statsOnStop: boolean;\n readonly mapOnStart: boolean;\n readonly thresholdBytes: number;\n /** The `--budget` every suggested command and the snippet quote. */\n readonly budgetBytes: number;\n}\n\n/** A file's bytes, rendered from the wizard's choices. */\nexport type HarnessFileContent = (ctx: HarnessInstallContext) => string;\n\n/**\n * One artefact `hooks install` writes. The kind is also the un-write: `json-hooks` is\n * merged in and strip-merged out, `marker-block` is upserted and stripped,\n * `own-file` is written and deleted.\n */\nexport type HarnessInstallStep = HarnessJsonHooks | HarnessMarkerBlock | HarnessOwnFile;\n\n/**\n * A JSON settings/hooks file the harness reads. Our entries are merged in\n * byte-faithfully and, on `remove`, stripped back out with everything foreign left\n * exactly as it was.\n */\nexport interface HarnessJsonHooks {\n readonly kind: 'json-hooks';\n /** Project-relative path of the file. */\n readonly file: string;\n /** The pre-tool event, in this harness's spelling (`PreToolUse`, `BeforeTool`, …). */\n readonly event: string;\n /**\n * One hook entry per matcher — the tool names the guard wants to see. `undefined`\n * is a matcher-less entry, for a harness whose hook fires on every tool.\n */\n readonly matchers: readonly (string | undefined)[];\n /**\n * The entry shape: `'command-list'` is Claude Code's `{matcher, hooks:[{type,\n * command}]}`, which Codex, Gemini and Grok mirror; `'bare-command'` is Cursor's\n * `{command}`.\n */\n readonly entry: 'command-list' | 'bare-command';\n /**\n * True for a harness whose schema also carries the session-lifecycle events this\n * preset offers — `smelt stats` on Stop, `smelt map` on SessionStart. The other\n * harnesses wire the guard only, and the wizard's toggles say so.\n */\n readonly lifecycle: boolean;\n /** Top-level keys a *fresh* file must carry (Cursor's `version: 1`). */\n readonly shape?: { readonly version: number };\n}\n\n/** A marker-bracketed block inside a file smelt shares with its owner. */\nexport interface HarnessMarkerBlock {\n readonly kind: 'marker-block';\n readonly file: string;\n readonly block: HarnessFileContent;\n /** The marker line opening the block — also how `remove` finds it. */\n readonly start: string;\n readonly end: string;\n /**\n * Refuse the file instead of editing it when it already carries `contains` and none\n * of ours: Codex's `config.toml` may have a hand-written `[features]` table, and an\n * installer that merged into it would be editing what it was never asked to.\n */\n readonly skipWhen?: { readonly contains: string; readonly why: string };\n}\n\n/** A file that is entirely smelt's: written whole, deleted whole. */\nexport interface HarnessOwnFile {\n readonly kind: 'own-file';\n readonly file: string;\n readonly content: HarnessFileContent;\n /** chmod after writing (Cline's hook must be executable). */\n readonly mode?: number;\n /** True when the file exists only to wire the guard — the guard toggle gates it. */\n readonly guardOnly: boolean;\n}\n\n/**\n * A profile that ships a shim script. Only these have a shim path: `shimScriptPath`\n * takes one, so a harness with no shim cannot name a script that was never built.\n */\nexport type ShimmedHarnessProfile = HarnessProfile &\n ({ readonly hooks: HarnessHookSchema } | { readonly shim: ShimAdapter });\n\n/** True for a profile that ships a shim script — the narrowing `shimScriptPath` needs. */\nexport function hasShim(profile: HarnessProfile): profile is ShimmedHarnessProfile {\n return profile.hooks !== undefined || profile.shim !== undefined;\n}\n\n/**\n * The adapter this profile's shim script runs: the one its {@link HarnessProfile.hooks}\n * schema describes, or the hand-written escape hatch where it carries one.\n */\nexport function shimAdapterOf(profile: ShimmedHarnessProfile): ShimAdapter {\n const { hooks, shim } = profile;\n if (shim !== undefined) return shim;\n /* v8 ignore next 5 -- unreachable: a ShimmedHarnessProfile carries one or the other */\n if (hooks === undefined) {\n throw new Error(\n `smelt: harness \"${profile.id}\" claims a shim but carries neither a hook schema nor an adapter.`,\n );\n }\n return shimFromSchema(hooks);\n}\n"]}
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/harness/profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAyGlD,yEAAyE;AACzE,MAAM,CAAC,MAAM,YAAY,GAAgC;IACvD,QAAQ,EAAE,6DAA6D;IACvE,YAAY,EACV,mGAAmG;IACrG,QAAQ,EAAE,+DAA+D;CAC1E,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAA2B,MAAM,CAAC,IAAI,CAAC,YAAY,CAAkB,CAAC;AAEhG;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,OAAuB;IAClD,OAAO,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;AAC3C,CAAC;AAiGD,0FAA0F;AAC1F,MAAM,UAAU,OAAO,CAAC,OAAuB;IAC7C,OAAO,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC;AACnE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,OAA8B;IAC1D,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IAChC,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACpC,uFAAuF;IACvF,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,mBAAmB,OAAO,CAAC,EAAE,mEAAmE,CACjG,CAAC;IACJ,CAAC;IACD,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC","sourcesContent":["import { shimFromSchema } from '../hooks/shim.ts';\nimport type { HarnessHookSchema, ShimAdapter } from '../hooks/shim.ts';\n\n/**\n * Everything smelt knows about one agent harness, in one place.\n *\n * Before this module existed, a harness's facts lived at seven edit sites: the\n * `HarnessId` union, the `HARNESSES` array, a shim file, a case in `planInstall`, a\n * case in `planRemove`, `MANAGED_EVENTS`, a test fixture — plus a hand-typed id list\n * in the `--harness` help text, which could not be derived because the registry lived\n * in `cli/hooks.ts` and `cli/hooks.ts` imports `CLI_NAME` from `cli/args.ts`. The\n * registry was in the wrong module, and the cycle was the proof.\n *\n * A `HarnessProfile` is the single adapter carrying every per-harness fact, and the\n * registry (`registry.ts`) is `Record<HarnessId, HarnessProfile>` so totality stays a\n * compile error: a new `HarnessId` without a profile does not build. It imports\n * nothing from `cli/`, so `cli/args.ts` derives the `--harness` list and every other\n * rendered view is derived too — the managed event names, the files a re-run reads its\n * toggles back from, the id list in an error message.\n *\n * Installing and removing are **data**, not switch statements: `install` is the list\n * of artefacts `smelt hooks install` writes for this harness, and each artefact's kind\n * is also how `smelt hooks remove` takes it back out — a JSON hook file is merged and\n * strip-merged, a marker block is upserted and stripped, a file that is entirely ours\n * is written and deleted. `planInstall`/`planRemove` fold over the list.\n *\n * The optional sections state capability, never invent it: a harness ships a shim\n * exactly when it carries a {@link hooks} schema (or, for a harness a table cannot\n * express, a hand-written {@link shim}), and only such a profile has a shim script\n * path. What a profile does not claim, the installer does not write.\n */\nexport interface HarnessProfile {\n readonly id: HarnessId;\n /** The harness's own name, as its makers spell it. Shown wherever a tier is. */\n readonly name: string;\n /**\n * The name a *list* of harnesses uses, where the maker's own spelling carries a\n * category suffix a list does not need — \"Codex CLI\" is \"Codex\" in a tier clause,\n * \"Hermes Agent\" is \"Hermes\". Optional, and defaulted by {@link harnessLabel} to\n * {@link name}: only a harness whose prose name differs from its full name carries\n * one, and it carries it here rather than in each of the four places a tier list is\n * rendered. \"Claude Code\" has no suffix to drop, and so has none.\n */\n readonly shortName?: string;\n readonly tier: HarnessTier;\n /** Paths (relative to the project) whose existence means \"this harness is in use here\". */\n readonly detect: readonly string[];\n /** Paths relative to the user's home directory — \"installed on this machine\". */\n readonly detectHome: readonly string[];\n /** The standing-instructions file this harness reads (capability matrix column d). */\n readonly instructionFile: string;\n /**\n * The standing-instructions layer — belt and braces under every shim, and the *only*\n * layer an advisory harness has:\n *\n * - `'snippet'`: upsert the shared marker-bracketed block into\n * {@link instructionFile} (a file several harnesses read, like `AGENTS.md`, is\n * planned once); `remove` strips the block and leaves every other byte.\n * - a renderer: a whole file this harness owns, snippet included — KiloCode's rules\n * file carries two manual enforcement legs the shared snippet has no room for;\n * `remove` deletes it.\n */\n readonly instructions: 'snippet' | HarnessFileContent;\n /** Caveats carried from the capability matrix, shown at install time. */\n readonly caveats: readonly string[];\n /**\n * Everything else `hooks install` writes for this harness, in the order it is\n * written and listed. Empty means instructions only: the advisory tier, where\n * nothing enforces anything and the output says so.\n */\n readonly install: readonly HarnessInstallStep[];\n /**\n * This harness's native pre-tool hook schema, as data. Present exactly when the\n * harness ships a shim script (`dist/hooks/shims/<id>.js`) — absent for the advisory\n * tier, and for opencode, whose hook API is a JavaScript plugin rather than a stdin\n * schema.\n */\n readonly hooks?: HarnessHookSchema;\n /**\n * The escape hatch: a hand-written adapter, for a harness whose schema a table\n * cannot express. Wins over {@link hooks} when both are present.\n */\n readonly shim?: ShimAdapter;\n}\n\n/** How much smelt is willing to claim about a harness. */\nexport type HarnessTier = 'verified' | 'experimental' | 'advisory';\n\n/**\n * Every harness the preset knows. The registry is keyed by this union, so adding an id\n * without writing its profile is a compile error — and the `--harness` help list, the\n * wizard's table and every error message read the registry, never a second list.\n */\nexport type HarnessId =\n | 'claude-code'\n | 'codex'\n | 'gemini'\n | 'grok'\n | 'hermes'\n | 'cursor'\n | 'opencode'\n | 'cline'\n | 'kilocode'\n | 'aider';\n\n/** One line of honesty per tier, shown wherever a tier label appears. */\nexport const TIER_HONESTY: Record<HarnessTier, string> = {\n verified: 'schema verified against primary docs and pinned by fixtures',\n experimental:\n 'schema mapped from the 2026-09-02 capability matrix, not yet smoke-tested against the real binary',\n advisory: 'no usable hook API — instructions only, nothing enforces them',\n};\n\n/**\n * The tiers, in the order every rendered tier list walks them — most claimed first.\n * {@link TIER_HONESTY}'s key order *is* that order, so a tier cannot exist without a\n * line saying what it means, and the order lives once.\n */\nexport const HARNESS_TIERS: readonly HarnessTier[] = Object.keys(TIER_HONESTY) as HarnessTier[];\n\n/**\n * How a list of harnesses spells this one: {@link HarnessProfile.shortName} where the\n * maker's own name carries a suffix a list does not need, else {@link\n * HarnessProfile.name}. The help's tier clause, the wizard's sentences and the site's\n * tier table all render through this, so they cannot spell a harness three ways.\n */\nexport function harnessLabel(profile: HarnessProfile): string {\n return profile.shortName ?? profile.name;\n}\n\n/**\n * What the wizard settled on, as the installer's renderers see it: the toggles, the\n * guard's settings, and the project directory every path is relative to. A renderer\n * takes this and returns bytes; nothing writes.\n */\nexport interface HarnessInstallContext {\n /** Project directory: every path a renderer emits is portable relative to it. */\n readonly cwd: string;\n readonly guard: boolean;\n readonly statsOnStop: boolean;\n readonly mapOnStart: boolean;\n /** Opt-in `smelt agents lint` at session start — shares SessionStart with the map. */\n readonly lintOnStart: boolean;\n readonly thresholdBytes: number;\n /** The `--budget` every suggested command and the snippet quote. */\n readonly budgetBytes: number;\n}\n\n/** A file's bytes, rendered from the wizard's choices. */\nexport type HarnessFileContent = (ctx: HarnessInstallContext) => string;\n\n/**\n * One artefact `hooks install` writes. The kind is also the un-write: `json-hooks` is\n * merged in and strip-merged out, `marker-block` is upserted and stripped,\n * `own-file` is written and deleted.\n */\nexport type HarnessInstallStep = HarnessJsonHooks | HarnessMarkerBlock | HarnessOwnFile;\n\n/**\n * A JSON settings/hooks file the harness reads. Our entries are merged in\n * byte-faithfully and, on `remove`, stripped back out with everything foreign left\n * exactly as it was.\n */\nexport interface HarnessJsonHooks {\n readonly kind: 'json-hooks';\n /** Project-relative path of the file. */\n readonly file: string;\n /** The pre-tool event, in this harness's spelling (`PreToolUse`, `BeforeTool`, …). */\n readonly event: string;\n /**\n * One hook entry per matcher — the tool names the guard wants to see. `undefined`\n * is a matcher-less entry, for a harness whose hook fires on every tool.\n */\n readonly matchers: readonly (string | undefined)[];\n /**\n * The entry shape: `'command-list'` is Claude Code's `{matcher, hooks:[{type,\n * command}]}`, which Codex, Gemini and Grok mirror; `'bare-command'` is Cursor's\n * `{command}`.\n */\n readonly entry: 'command-list' | 'bare-command';\n /**\n * True for a harness whose schema also carries the session-lifecycle events this\n * preset offers — `smelt stats` on Stop, `smelt map` and `smelt agents lint` on\n * SessionStart. The other\n * harnesses wire the guard only, and the wizard's toggles say so.\n */\n readonly lifecycle: boolean;\n /** Top-level keys a *fresh* file must carry (Cursor's `version: 1`). */\n readonly shape?: { readonly version: number };\n}\n\n/** A marker-bracketed block inside a file smelt shares with its owner. */\nexport interface HarnessMarkerBlock {\n readonly kind: 'marker-block';\n readonly file: string;\n readonly block: HarnessFileContent;\n /** The marker line opening the block — also how `remove` finds it. */\n readonly start: string;\n readonly end: string;\n /**\n * Refuse the file instead of editing it when it already carries `contains` and none\n * of ours: Codex's `config.toml` may have a hand-written `[features]` table, and an\n * installer that merged into it would be editing what it was never asked to.\n */\n readonly skipWhen?: { readonly contains: string; readonly why: string };\n}\n\n/** A file that is entirely smelt's: written whole, deleted whole. */\nexport interface HarnessOwnFile {\n readonly kind: 'own-file';\n readonly file: string;\n readonly content: HarnessFileContent;\n /** chmod after writing (Cline's hook must be executable). */\n readonly mode?: number;\n /** True when the file exists only to wire the guard — the guard toggle gates it. */\n readonly guardOnly: boolean;\n}\n\n/**\n * A profile that ships a shim script. Only these have a shim path: `shimScriptPath`\n * takes one, so a harness with no shim cannot name a script that was never built.\n */\nexport type ShimmedHarnessProfile = HarnessProfile &\n ({ readonly hooks: HarnessHookSchema } | { readonly shim: ShimAdapter });\n\n/** True for a profile that ships a shim script — the narrowing `shimScriptPath` needs. */\nexport function hasShim(profile: HarnessProfile): profile is ShimmedHarnessProfile {\n return profile.hooks !== undefined || profile.shim !== undefined;\n}\n\n/**\n * The adapter this profile's shim script runs: the one its {@link HarnessProfile.hooks}\n * schema describes, or the hand-written escape hatch where it carries one.\n */\nexport function shimAdapterOf(profile: ShimmedHarnessProfile): ShimAdapter {\n const { hooks, shim } = profile;\n if (shim !== undefined) return shim;\n /* v8 ignore next 5 -- unreachable: a ShimmedHarnessProfile carries one or the other */\n if (hooks === undefined) {\n throw new Error(\n `smelt: harness \"${profile.id}\" claims a shim but carries neither a hook schema nor an adapter.`,\n );\n }\n return shimFromSchema(hooks);\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { HarnessId, HarnessProfile } from './profile.ts';
1
+ import type { HarnessId, HarnessProfile, HarnessTier } from './profile.ts';
2
2
  /**
3
3
  * The registry — every harness smelt knows, one {@link HarnessProfile} each.
4
4
  *
@@ -24,16 +24,65 @@ export declare const HARNESSES: readonly HarnessProfile[];
24
24
  export declare const HARNESS_IDS: readonly HarnessId[];
25
25
  /** The profile for a harness id, or `undefined` for a string the user made up. */
26
26
  export declare function harnessById(id: string): HarnessProfile | undefined;
27
+ /** One tier and the harnesses that ship at it — the row every tier table renders. */
28
+ export interface HarnessTierGroup {
29
+ readonly tier: HarnessTier;
30
+ /** That tier's one line of honesty ({@link TIER_HONESTY}), carried with the group. */
31
+ readonly honesty: string;
32
+ /** The profiles at this tier, in registry order. */
33
+ readonly harnesses: readonly HarnessProfile[];
34
+ }
27
35
  /**
28
- * The two session-lifecycle hooks this preset offers, named in each harness's schema:
29
- * `smelt stats` when a turn ends, an opening `smelt map` when a session starts. Only
30
- * the harnesses whose schema carries these events wire them (`step.lifecycle`), and
31
- * `cli/hooks.ts` writes the entries under these exact keys one spelling, so the
32
- * managed-event list below cannot fall behind what the installer writes.
36
+ * The tier harnesses grouping, derived `HarnessProfile.tier` is data, and this is
37
+ * the one fold over it.
38
+ *
39
+ * It was hand-typed in five places (the `hooks` help body, two wizard sentences, the
40
+ * README's tier table and the site's), so a harness promoted from experimental to
41
+ * verified stayed listed under the old tier in four of them and nothing failed: the
42
+ * only check was that each harness's name appeared *somewhere* in the help. Every
43
+ * rendered grouping now walks this, and `test/guards/harness-registry.test.ts` asserts
44
+ * the rendered ones agree with it — each name under its own tier clause and no other.
45
+ *
46
+ * Tier order is {@link HARNESS_TIERS} (i.e. `TIER_HONESTY`'s key order); a tier no
47
+ * profile claims yields no group, because a rendered "advisory ()" is worse than a
48
+ * tier that has stopped existing.
49
+ */
50
+ export declare function harnessesByTier(): readonly HarnessTierGroup[];
51
+ /**
52
+ * Whether this harness's native schema carries the session-lifecycle events this preset
53
+ * offers — `smelt stats` on Stop, `smelt map` on SessionStart — i.e. whether it declares
54
+ * a JSON hook step with `lifecycle`.
55
+ *
56
+ * The wizard used to call these "verified-tier harnesses (Claude Code, Codex)", which
57
+ * is a tier standing in for a capability it merely correlates with: the tier is how much
58
+ * smelt is willing to *claim* about a harness, `lifecycle` is whether the harness has
59
+ * the events at all. The two sets coincide today, which is precisely why the sentence
60
+ * read correctly and why nothing could notice it was wrong — so the guard asks this
61
+ * predicate about a profile whose tier and capability *disagree*, and the mutation runner
62
+ * swaps one for the other and watches it go red.
63
+ */
64
+ export declare function wiresLifecycle(profile: HarnessProfile): boolean;
65
+ /** The harnesses the stats/map toggles actually wire, in registry order. */
66
+ export declare function lifecycleHarnesses(): readonly HarnessProfile[];
67
+ /** A list of harnesses as prose spells them — the one join, so the four lists agree. */
68
+ export declare function harnessNames(profiles: readonly HarnessProfile[]): string;
69
+ /**
70
+ * The session-lifecycle hooks this preset offers, named in each harness's schema:
71
+ * `smelt stats` when a turn ends, and — sharing one `SessionStart` event — an opening
72
+ * `smelt map` and a lint of the repository's own instruction files. Only the harnesses
73
+ * whose schema carries these events wire them (`step.lifecycle`), and `cli/hooks.ts`
74
+ * writes the entries under these exact keys — one spelling, so the managed-event list
75
+ * below cannot fall behind what the installer writes.
76
+ *
77
+ * `map` and `lint` deliberately name the **same** event. They are two independent
78
+ * toggles under one key, which is why `cli/hooks.ts` merges their entries into one
79
+ * array rather than spreading two objects (the second would silently replace the
80
+ * first) and why a re-run tells them apart by the command each entry runs.
33
81
  */
34
82
  export declare const LIFECYCLE_EVENTS: {
35
83
  readonly stats: 'Stop';
36
84
  readonly map: 'SessionStart';
85
+ readonly lint: 'SessionStart';
37
86
  };
38
87
  /**
39
88
  * The events this installer manages, across every harness's spelling of them —
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/harness/registry.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAoB,cAAc,EAAE,MAAM,cAAc,CAAC;AAEhF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,cAAc,CAAC,CAWxE,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,SAAS,EAAE,SAAS,cAAc,EAAoC,CAAC;AAEpF,gFAAgF;AAChF,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAA2C,CAAC;AAExF,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAElE;AASD;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB;aAAK,KAAK,EAAE,MAAM;aAAE,GAAG,EAAE,cAAc;CAAW,CAAC;AAEhF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAO3C,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAEzC,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAE5C,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAQ7C,CAAC","sourcesContent":["import { aider } from './aider.ts';\nimport { claudeCode } from './claude-code.ts';\nimport { cline } from './cline.ts';\nimport { codex } from './codex.ts';\nimport { cursor } from './cursor.ts';\nimport { gemini } from './gemini.ts';\nimport { grok } from './grok.ts';\nimport { hermes } from './hermes.ts';\nimport { kilocode } from './kilocode.ts';\nimport { opencode } from './opencode.ts';\nimport type { HarnessId, HarnessJsonHooks, HarnessProfile } from './profile.ts';\n\n/**\n * The registry — every harness smelt knows, one {@link HarnessProfile} each.\n *\n * `Record<HarnessId, HarnessProfile>` on purpose: adding a `HarnessId` in `profile.ts`\n * without writing its profile is a compile error, so the id list and the facts cannot\n * drift. Every derived view — the `--harness` help list, the wizard's table, the\n * managed event names, the files a re-run reads its toggles back from — is computed\n * from this object, never written twice.\n *\n * This module imports **nothing from `cli/`**. That is the point: the registry used to\n * live in `cli/hooks.ts`, which imports `CLI_NAME` from `cli/args.ts`, so `args.ts`\n * could not import it back and the `--harness` help list was hand-typed five lines\n * below two lists that were correctly derived.\n *\n * Key order is meaningful: it is the order every rendered harness list uses (the help\n * text, the wizard's table, the \"Known: …\" of an unknown-harness error), so keep it\n * stable and append new harnesses at the end.\n */\nexport const HARNESS_PROFILES: Readonly<Record<HarnessId, HarnessProfile>> = {\n 'claude-code': claudeCode,\n codex,\n gemini,\n grok,\n hermes,\n cursor,\n opencode,\n cline,\n kilocode,\n aider,\n};\n\n/** Every profile, in registry order. The list every rendered table walks. */\nexport const HARNESSES: readonly HarnessProfile[] = Object.values(HARNESS_PROFILES);\n\n/** Every harness id, in registry order — the `--harness` help list, derived. */\nexport const HARNESS_IDS: readonly HarnessId[] = HARNESSES.map((profile) => profile.id);\n\n/** The profile for a harness id, or `undefined` for a string the user made up. */\nexport function harnessById(id: string): HarnessProfile | undefined {\n return HARNESSES.find((profile) => profile.id === id);\n}\n\n/** Every JSON hook step any profile declares, in registry order. */\nfunction jsonHookSteps(): readonly HarnessJsonHooks[] {\n return HARNESSES.flatMap((profile) =>\n profile.install.filter((step): step is HarnessJsonHooks => step.kind === 'json-hooks'),\n );\n}\n\n/**\n * The two session-lifecycle hooks this preset offers, named in each harness's schema:\n * `smelt stats` when a turn ends, an opening `smelt map` when a session starts. Only\n * the harnesses whose schema carries these events wire them (`step.lifecycle`), and\n * `cli/hooks.ts` writes the entries under these exact keys — one spelling, so the\n * managed-event list below cannot fall behind what the installer writes.\n */\nexport const LIFECYCLE_EVENTS = { stats: 'Stop', map: 'SessionStart' } as const;\n\n/**\n * The events this installer manages, across every harness's spelling of them —\n * foreign entries under them are always preserved, and events nobody claims are never\n * touched. Derived, so a harness that spells its pre-tool event a new way is managed\n * by existing.\n */\nexport const MANAGED_EVENTS: readonly string[] = [\n ...new Set(\n jsonHookSteps().flatMap((step) => [\n step.event,\n ...(step.lifecycle ? Object.values(LIFECYCLE_EVENTS) : []),\n ]),\n ),\n];\n\n/** The managed events that wire the PreToolUse guard, across harness spellings. */\nexport const GUARD_EVENTS: readonly string[] = [\n ...new Set(jsonHookSteps().map((step) => step.event)),\n];\n\n/** The JSON hook files a re-run reads installed toggles back from, per harness. */\nexport const JSON_HOOK_FILES: readonly string[] = [\n ...new Set(jsonHookSteps().map((step) => step.file)),\n];\n\n/** Guard-only files whose presence means the guard toggle was installed. */\nexport const GUARD_ONLY_FILES: readonly string[] = [\n ...new Set(\n HARNESSES.flatMap((profile) =>\n profile.install\n .filter((step) => step.kind === 'own-file' && step.guardOnly)\n .map((step) => step.file),\n ),\n ),\n];\n"]}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/harness/registry.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAoB,cAAc,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE7F;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,cAAc,CAAC,CAWxE,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,SAAS,EAAE,SAAS,cAAc,EAAoC,CAAC;AAEpF,gFAAgF;AAChF,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAA2C,CAAC;AAExF,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAElE;AAED,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,sFAAsF;IACtF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oDAAoD;IACpD,QAAQ,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,CAAC;CAC/C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,IAAI,SAAS,gBAAgB,EAAE,CAM7D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAE/D;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,IAAI,SAAS,cAAc,EAAE,CAE9D;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAExE;AASD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB;aAC3B,KAAK,EAAE,MAAM;aACb,GAAG,EAAE,cAAc;aACnB,IAAI,EAAE,cAAc;CACZ,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAO3C,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAEzC,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAE5C,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAQ7C,CAAC","sourcesContent":["import { aider } from './aider.ts';\nimport { claudeCode } from './claude-code.ts';\nimport { cline } from './cline.ts';\nimport { codex } from './codex.ts';\nimport { cursor } from './cursor.ts';\nimport { gemini } from './gemini.ts';\nimport { grok } from './grok.ts';\nimport { hermes } from './hermes.ts';\nimport { kilocode } from './kilocode.ts';\nimport { opencode } from './opencode.ts';\nimport { harnessLabel, HARNESS_TIERS, TIER_HONESTY } from './profile.ts';\nimport type { HarnessId, HarnessJsonHooks, HarnessProfile, HarnessTier } from './profile.ts';\n\n/**\n * The registry — every harness smelt knows, one {@link HarnessProfile} each.\n *\n * `Record<HarnessId, HarnessProfile>` on purpose: adding a `HarnessId` in `profile.ts`\n * without writing its profile is a compile error, so the id list and the facts cannot\n * drift. Every derived view — the `--harness` help list, the wizard's table, the\n * managed event names, the files a re-run reads its toggles back from — is computed\n * from this object, never written twice.\n *\n * This module imports **nothing from `cli/`**. That is the point: the registry used to\n * live in `cli/hooks.ts`, which imports `CLI_NAME` from `cli/args.ts`, so `args.ts`\n * could not import it back and the `--harness` help list was hand-typed five lines\n * below two lists that were correctly derived.\n *\n * Key order is meaningful: it is the order every rendered harness list uses (the help\n * text, the wizard's table, the \"Known: …\" of an unknown-harness error), so keep it\n * stable and append new harnesses at the end.\n */\nexport const HARNESS_PROFILES: Readonly<Record<HarnessId, HarnessProfile>> = {\n 'claude-code': claudeCode,\n codex,\n gemini,\n grok,\n hermes,\n cursor,\n opencode,\n cline,\n kilocode,\n aider,\n};\n\n/** Every profile, in registry order. The list every rendered table walks. */\nexport const HARNESSES: readonly HarnessProfile[] = Object.values(HARNESS_PROFILES);\n\n/** Every harness id, in registry order — the `--harness` help list, derived. */\nexport const HARNESS_IDS: readonly HarnessId[] = HARNESSES.map((profile) => profile.id);\n\n/** The profile for a harness id, or `undefined` for a string the user made up. */\nexport function harnessById(id: string): HarnessProfile | undefined {\n return HARNESSES.find((profile) => profile.id === id);\n}\n\n/** One tier and the harnesses that ship at it — the row every tier table renders. */\nexport interface HarnessTierGroup {\n readonly tier: HarnessTier;\n /** That tier's one line of honesty ({@link TIER_HONESTY}), carried with the group. */\n readonly honesty: string;\n /** The profiles at this tier, in registry order. */\n readonly harnesses: readonly HarnessProfile[];\n}\n\n/**\n * The tier → harnesses grouping, derived — `HarnessProfile.tier` is data, and this is\n * the one fold over it.\n *\n * It was hand-typed in five places (the `hooks` help body, two wizard sentences, the\n * README's tier table and the site's), so a harness promoted from experimental to\n * verified stayed listed under the old tier in four of them and nothing failed: the\n * only check was that each harness's name appeared *somewhere* in the help. Every\n * rendered grouping now walks this, and `test/guards/harness-registry.test.ts` asserts\n * the rendered ones agree with it — each name under its own tier clause and no other.\n *\n * Tier order is {@link HARNESS_TIERS} (i.e. `TIER_HONESTY`'s key order); a tier no\n * profile claims yields no group, because a rendered \"advisory ()\" is worse than a\n * tier that has stopped existing.\n */\nexport function harnessesByTier(): readonly HarnessTierGroup[] {\n return HARNESS_TIERS.map((tier) => ({\n tier,\n honesty: TIER_HONESTY[tier],\n harnesses: HARNESSES.filter((profile) => profile.tier === tier),\n })).filter((group) => group.harnesses.length > 0);\n}\n\n/**\n * Whether this harness's native schema carries the session-lifecycle events this preset\n * offers — `smelt stats` on Stop, `smelt map` on SessionStart — i.e. whether it declares\n * a JSON hook step with `lifecycle`.\n *\n * The wizard used to call these \"verified-tier harnesses (Claude Code, Codex)\", which\n * is a tier standing in for a capability it merely correlates with: the tier is how much\n * smelt is willing to *claim* about a harness, `lifecycle` is whether the harness has\n * the events at all. The two sets coincide today, which is precisely why the sentence\n * read correctly and why nothing could notice it was wrong — so the guard asks this\n * predicate about a profile whose tier and capability *disagree*, and the mutation runner\n * swaps one for the other and watches it go red.\n */\nexport function wiresLifecycle(profile: HarnessProfile): boolean {\n return profile.install.some((step) => step.kind === 'json-hooks' && step.lifecycle);\n}\n\n/** The harnesses the stats/map toggles actually wire, in registry order. */\nexport function lifecycleHarnesses(): readonly HarnessProfile[] {\n return HARNESSES.filter(wiresLifecycle);\n}\n\n/** A list of harnesses as prose spells them — the one join, so the four lists agree. */\nexport function harnessNames(profiles: readonly HarnessProfile[]): string {\n return profiles.map(harnessLabel).join(', ');\n}\n\n/** Every JSON hook step any profile declares, in registry order. */\nfunction jsonHookSteps(): readonly HarnessJsonHooks[] {\n return HARNESSES.flatMap((profile) =>\n profile.install.filter((step): step is HarnessJsonHooks => step.kind === 'json-hooks'),\n );\n}\n\n/**\n * The session-lifecycle hooks this preset offers, named in each harness's schema:\n * `smelt stats` when a turn ends, and — sharing one `SessionStart` event — an opening\n * `smelt map` and a lint of the repository's own instruction files. Only the harnesses\n * whose schema carries these events wire them (`step.lifecycle`), and `cli/hooks.ts`\n * writes the entries under these exact keys — one spelling, so the managed-event list\n * below cannot fall behind what the installer writes.\n *\n * `map` and `lint` deliberately name the **same** event. They are two independent\n * toggles under one key, which is why `cli/hooks.ts` merges their entries into one\n * array rather than spreading two objects (the second would silently replace the\n * first) and why a re-run tells them apart by the command each entry runs.\n */\nexport const LIFECYCLE_EVENTS = {\n stats: 'Stop',\n map: 'SessionStart',\n lint: 'SessionStart',\n} as const;\n\n/**\n * The events this installer manages, across every harness's spelling of them —\n * foreign entries under them are always preserved, and events nobody claims are never\n * touched. Derived, so a harness that spells its pre-tool event a new way is managed\n * by existing.\n */\nexport const MANAGED_EVENTS: readonly string[] = [\n ...new Set(\n jsonHookSteps().flatMap((step) => [\n step.event,\n ...(step.lifecycle ? Object.values(LIFECYCLE_EVENTS) : []),\n ]),\n ),\n];\n\n/** The managed events that wire the PreToolUse guard, across harness spellings. */\nexport const GUARD_EVENTS: readonly string[] = [\n ...new Set(jsonHookSteps().map((step) => step.event)),\n];\n\n/** The JSON hook files a re-run reads installed toggles back from, per harness. */\nexport const JSON_HOOK_FILES: readonly string[] = [\n ...new Set(jsonHookSteps().map((step) => step.file)),\n];\n\n/** Guard-only files whose presence means the guard toggle was installed. */\nexport const GUARD_ONLY_FILES: readonly string[] = [\n ...new Set(\n HARNESSES.flatMap((profile) =>\n profile.install\n .filter((step) => step.kind === 'own-file' && step.guardOnly)\n .map((step) => step.file),\n ),\n ),\n];\n"]}
@@ -8,6 +8,7 @@ import { grok } from './grok.js';
8
8
  import { hermes } from './hermes.js';
9
9
  import { kilocode } from './kilocode.js';
10
10
  import { opencode } from './opencode.js';
11
+ import { harnessLabel, HARNESS_TIERS, TIER_HONESTY } from './profile.js';
11
12
  /**
12
13
  * The registry — every harness smelt knows, one {@link HarnessProfile} each.
13
14
  *
@@ -46,18 +47,74 @@ export const HARNESS_IDS = HARNESSES.map((profile) => profile.id);
46
47
  export function harnessById(id) {
47
48
  return HARNESSES.find((profile) => profile.id === id);
48
49
  }
50
+ /**
51
+ * The tier → harnesses grouping, derived — `HarnessProfile.tier` is data, and this is
52
+ * the one fold over it.
53
+ *
54
+ * It was hand-typed in five places (the `hooks` help body, two wizard sentences, the
55
+ * README's tier table and the site's), so a harness promoted from experimental to
56
+ * verified stayed listed under the old tier in four of them and nothing failed: the
57
+ * only check was that each harness's name appeared *somewhere* in the help. Every
58
+ * rendered grouping now walks this, and `test/guards/harness-registry.test.ts` asserts
59
+ * the rendered ones agree with it — each name under its own tier clause and no other.
60
+ *
61
+ * Tier order is {@link HARNESS_TIERS} (i.e. `TIER_HONESTY`'s key order); a tier no
62
+ * profile claims yields no group, because a rendered "advisory ()" is worse than a
63
+ * tier that has stopped existing.
64
+ */
65
+ export function harnessesByTier() {
66
+ return HARNESS_TIERS.map((tier) => ({
67
+ tier,
68
+ honesty: TIER_HONESTY[tier],
69
+ harnesses: HARNESSES.filter((profile) => profile.tier === tier),
70
+ })).filter((group) => group.harnesses.length > 0);
71
+ }
72
+ /**
73
+ * Whether this harness's native schema carries the session-lifecycle events this preset
74
+ * offers — `smelt stats` on Stop, `smelt map` on SessionStart — i.e. whether it declares
75
+ * a JSON hook step with `lifecycle`.
76
+ *
77
+ * The wizard used to call these "verified-tier harnesses (Claude Code, Codex)", which
78
+ * is a tier standing in for a capability it merely correlates with: the tier is how much
79
+ * smelt is willing to *claim* about a harness, `lifecycle` is whether the harness has
80
+ * the events at all. The two sets coincide today, which is precisely why the sentence
81
+ * read correctly and why nothing could notice it was wrong — so the guard asks this
82
+ * predicate about a profile whose tier and capability *disagree*, and the mutation runner
83
+ * swaps one for the other and watches it go red.
84
+ */
85
+ export function wiresLifecycle(profile) {
86
+ return profile.install.some((step) => step.kind === 'json-hooks' && step.lifecycle);
87
+ }
88
+ /** The harnesses the stats/map toggles actually wire, in registry order. */
89
+ export function lifecycleHarnesses() {
90
+ return HARNESSES.filter(wiresLifecycle);
91
+ }
92
+ /** A list of harnesses as prose spells them — the one join, so the four lists agree. */
93
+ export function harnessNames(profiles) {
94
+ return profiles.map(harnessLabel).join(', ');
95
+ }
49
96
  /** Every JSON hook step any profile declares, in registry order. */
50
97
  function jsonHookSteps() {
51
98
  return HARNESSES.flatMap((profile) => profile.install.filter((step) => step.kind === 'json-hooks'));
52
99
  }
53
100
  /**
54
- * The two session-lifecycle hooks this preset offers, named in each harness's schema:
55
- * `smelt stats` when a turn ends, an opening `smelt map` when a session starts. Only
56
- * the harnesses whose schema carries these events wire them (`step.lifecycle`), and
57
- * `cli/hooks.ts` writes the entries under these exact keys one spelling, so the
58
- * managed-event list below cannot fall behind what the installer writes.
101
+ * The session-lifecycle hooks this preset offers, named in each harness's schema:
102
+ * `smelt stats` when a turn ends, and sharing one `SessionStart` event an opening
103
+ * `smelt map` and a lint of the repository's own instruction files. Only the harnesses
104
+ * whose schema carries these events wire them (`step.lifecycle`), and `cli/hooks.ts`
105
+ * writes the entries under these exact keys — one spelling, so the managed-event list
106
+ * below cannot fall behind what the installer writes.
107
+ *
108
+ * `map` and `lint` deliberately name the **same** event. They are two independent
109
+ * toggles under one key, which is why `cli/hooks.ts` merges their entries into one
110
+ * array rather than spreading two objects (the second would silently replace the
111
+ * first) and why a re-run tells them apart by the command each entry runs.
59
112
  */
60
- export const LIFECYCLE_EVENTS = { stats: 'Stop', map: 'SessionStart' };
113
+ export const LIFECYCLE_EVENTS = {
114
+ stats: 'Stop',
115
+ map: 'SessionStart',
116
+ lint: 'SessionStart',
117
+ };
61
118
  /**
62
119
  * The events this installer manages, across every harness's spelling of them —
63
120
  * foreign entries under them are always preserved, and events nobody claims are never
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/harness/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAgD;IAC3E,aAAa,EAAE,UAAU;IACzB,KAAK;IACL,MAAM;IACN,IAAI;IACJ,MAAM;IACN,MAAM;IACN,QAAQ;IACR,KAAK;IACL,QAAQ;IACR,KAAK;CACN,CAAC;AAEF,6EAA6E;AAC7E,MAAM,CAAC,MAAM,SAAS,GAA8B,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAEpF,gFAAgF;AAChF,MAAM,CAAC,MAAM,WAAW,GAAyB,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AAExF,kFAAkF;AAClF,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,oEAAoE;AACpE,SAAS,aAAa;IACpB,OAAO,SAAS,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CACnC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAA4B,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,YAAY,CAAC,CACvF,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,EAAW,CAAC;AAEhF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAsB;IAC/C,GAAG,IAAI,GAAG,CACR,aAAa,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAChC,IAAI,CAAC,KAAK;QACV,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3D,CAAC,CACH;CACF,CAAC;AAEF,mFAAmF;AACnF,MAAM,CAAC,MAAM,YAAY,GAAsB;IAC7C,GAAG,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;CACtD,CAAC;AAEF,mFAAmF;AACnF,MAAM,CAAC,MAAM,eAAe,GAAsB;IAChD,GAAG,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;CACrD,CAAC;AAEF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,gBAAgB,GAAsB;IACjD,GAAG,IAAI,GAAG,CACR,SAAS,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAC5B,OAAO,CAAC,OAAO;SACZ,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,SAAS,CAAC;SAC5D,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAC5B,CACF;CACF,CAAC","sourcesContent":["import { aider } from './aider.ts';\nimport { claudeCode } from './claude-code.ts';\nimport { cline } from './cline.ts';\nimport { codex } from './codex.ts';\nimport { cursor } from './cursor.ts';\nimport { gemini } from './gemini.ts';\nimport { grok } from './grok.ts';\nimport { hermes } from './hermes.ts';\nimport { kilocode } from './kilocode.ts';\nimport { opencode } from './opencode.ts';\nimport type { HarnessId, HarnessJsonHooks, HarnessProfile } from './profile.ts';\n\n/**\n * The registry — every harness smelt knows, one {@link HarnessProfile} each.\n *\n * `Record<HarnessId, HarnessProfile>` on purpose: adding a `HarnessId` in `profile.ts`\n * without writing its profile is a compile error, so the id list and the facts cannot\n * drift. Every derived view — the `--harness` help list, the wizard's table, the\n * managed event names, the files a re-run reads its toggles back from — is computed\n * from this object, never written twice.\n *\n * This module imports **nothing from `cli/`**. That is the point: the registry used to\n * live in `cli/hooks.ts`, which imports `CLI_NAME` from `cli/args.ts`, so `args.ts`\n * could not import it back and the `--harness` help list was hand-typed five lines\n * below two lists that were correctly derived.\n *\n * Key order is meaningful: it is the order every rendered harness list uses (the help\n * text, the wizard's table, the \"Known: …\" of an unknown-harness error), so keep it\n * stable and append new harnesses at the end.\n */\nexport const HARNESS_PROFILES: Readonly<Record<HarnessId, HarnessProfile>> = {\n 'claude-code': claudeCode,\n codex,\n gemini,\n grok,\n hermes,\n cursor,\n opencode,\n cline,\n kilocode,\n aider,\n};\n\n/** Every profile, in registry order. The list every rendered table walks. */\nexport const HARNESSES: readonly HarnessProfile[] = Object.values(HARNESS_PROFILES);\n\n/** Every harness id, in registry order — the `--harness` help list, derived. */\nexport const HARNESS_IDS: readonly HarnessId[] = HARNESSES.map((profile) => profile.id);\n\n/** The profile for a harness id, or `undefined` for a string the user made up. */\nexport function harnessById(id: string): HarnessProfile | undefined {\n return HARNESSES.find((profile) => profile.id === id);\n}\n\n/** Every JSON hook step any profile declares, in registry order. */\nfunction jsonHookSteps(): readonly HarnessJsonHooks[] {\n return HARNESSES.flatMap((profile) =>\n profile.install.filter((step): step is HarnessJsonHooks => step.kind === 'json-hooks'),\n );\n}\n\n/**\n * The two session-lifecycle hooks this preset offers, named in each harness's schema:\n * `smelt stats` when a turn ends, an opening `smelt map` when a session starts. Only\n * the harnesses whose schema carries these events wire them (`step.lifecycle`), and\n * `cli/hooks.ts` writes the entries under these exact keys — one spelling, so the\n * managed-event list below cannot fall behind what the installer writes.\n */\nexport const LIFECYCLE_EVENTS = { stats: 'Stop', map: 'SessionStart' } as const;\n\n/**\n * The events this installer manages, across every harness's spelling of them —\n * foreign entries under them are always preserved, and events nobody claims are never\n * touched. Derived, so a harness that spells its pre-tool event a new way is managed\n * by existing.\n */\nexport const MANAGED_EVENTS: readonly string[] = [\n ...new Set(\n jsonHookSteps().flatMap((step) => [\n step.event,\n ...(step.lifecycle ? Object.values(LIFECYCLE_EVENTS) : []),\n ]),\n ),\n];\n\n/** The managed events that wire the PreToolUse guard, across harness spellings. */\nexport const GUARD_EVENTS: readonly string[] = [\n ...new Set(jsonHookSteps().map((step) => step.event)),\n];\n\n/** The JSON hook files a re-run reads installed toggles back from, per harness. */\nexport const JSON_HOOK_FILES: readonly string[] = [\n ...new Set(jsonHookSteps().map((step) => step.file)),\n];\n\n/** Guard-only files whose presence means the guard toggle was installed. */\nexport const GUARD_ONLY_FILES: readonly string[] = [\n ...new Set(\n HARNESSES.flatMap((profile) =>\n profile.install\n .filter((step) => step.kind === 'own-file' && step.guardOnly)\n .map((step) => step.file),\n ),\n ),\n];\n"]}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/harness/registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { aider } from './aider.ts';\nimport { claudeCode } from './claude-code.ts';\nimport { cline } from './cline.ts';\nimport { codex } from './codex.ts';\nimport { cursor } from './cursor.ts';\nimport { gemini } from './gemini.ts';\nimport { grok } from './grok.ts';\nimport { hermes } from './hermes.ts';\nimport { kilocode } from './kilocode.ts';\nimport { opencode } from './opencode.ts';\nimport { harnessLabel, HARNESS_TIERS, TIER_HONESTY } from './profile.ts';\nimport type { HarnessId, HarnessJsonHooks, HarnessProfile, HarnessTier } from './profile.ts';\n\n/**\n * The registry — every harness smelt knows, one {@link HarnessProfile} each.\n *\n * `Record<HarnessId, HarnessProfile>` on purpose: adding a `HarnessId` in `profile.ts`\n * without writing its profile is a compile error, so the id list and the facts cannot\n * drift. Every derived view — the `--harness` help list, the wizard's table, the\n * managed event names, the files a re-run reads its toggles back from — is computed\n * from this object, never written twice.\n *\n * This module imports **nothing from `cli/`**. That is the point: the registry used to\n * live in `cli/hooks.ts`, which imports `CLI_NAME` from `cli/args.ts`, so `args.ts`\n * could not import it back and the `--harness` help list was hand-typed five lines\n * below two lists that were correctly derived.\n *\n * Key order is meaningful: it is the order every rendered harness list uses (the help\n * text, the wizard's table, the \"Known: …\" of an unknown-harness error), so keep it\n * stable and append new harnesses at the end.\n */\nexport const HARNESS_PROFILES: Readonly<Record<HarnessId, HarnessProfile>> = {\n 'claude-code': claudeCode,\n codex,\n gemini,\n grok,\n hermes,\n cursor,\n opencode,\n cline,\n kilocode,\n aider,\n};\n\n/** Every profile, in registry order. The list every rendered table walks. */\nexport const HARNESSES: readonly HarnessProfile[] = Object.values(HARNESS_PROFILES);\n\n/** Every harness id, in registry order — the `--harness` help list, derived. */\nexport const HARNESS_IDS: readonly HarnessId[] = HARNESSES.map((profile) => profile.id);\n\n/** The profile for a harness id, or `undefined` for a string the user made up. */\nexport function harnessById(id: string): HarnessProfile | undefined {\n return HARNESSES.find((profile) => profile.id === id);\n}\n\n/** One tier and the harnesses that ship at it — the row every tier table renders. */\nexport interface HarnessTierGroup {\n readonly tier: HarnessTier;\n /** That tier's one line of honesty ({@link TIER_HONESTY}), carried with the group. */\n readonly honesty: string;\n /** The profiles at this tier, in registry order. */\n readonly harnesses: readonly HarnessProfile[];\n}\n\n/**\n * The tier → harnesses grouping, derived — `HarnessProfile.tier` is data, and this is\n * the one fold over it.\n *\n * It was hand-typed in five places (the `hooks` help body, two wizard sentences, the\n * README's tier table and the site's), so a harness promoted from experimental to\n * verified stayed listed under the old tier in four of them and nothing failed: the\n * only check was that each harness's name appeared *somewhere* in the help. Every\n * rendered grouping now walks this, and `test/guards/harness-registry.test.ts` asserts\n * the rendered ones agree with it — each name under its own tier clause and no other.\n *\n * Tier order is {@link HARNESS_TIERS} (i.e. `TIER_HONESTY`'s key order); a tier no\n * profile claims yields no group, because a rendered \"advisory ()\" is worse than a\n * tier that has stopped existing.\n */\nexport function harnessesByTier(): readonly HarnessTierGroup[] {\n return HARNESS_TIERS.map((tier) => ({\n tier,\n honesty: TIER_HONESTY[tier],\n harnesses: HARNESSES.filter((profile) => profile.tier === tier),\n })).filter((group) => group.harnesses.length > 0);\n}\n\n/**\n * Whether this harness's native schema carries the session-lifecycle events this preset\n * offers — `smelt stats` on Stop, `smelt map` on SessionStart — i.e. whether it declares\n * a JSON hook step with `lifecycle`.\n *\n * The wizard used to call these \"verified-tier harnesses (Claude Code, Codex)\", which\n * is a tier standing in for a capability it merely correlates with: the tier is how much\n * smelt is willing to *claim* about a harness, `lifecycle` is whether the harness has\n * the events at all. The two sets coincide today, which is precisely why the sentence\n * read correctly and why nothing could notice it was wrong — so the guard asks this\n * predicate about a profile whose tier and capability *disagree*, and the mutation runner\n * swaps one for the other and watches it go red.\n */\nexport function wiresLifecycle(profile: HarnessProfile): boolean {\n return profile.install.some((step) => step.kind === 'json-hooks' && step.lifecycle);\n}\n\n/** The harnesses the stats/map toggles actually wire, in registry order. */\nexport function lifecycleHarnesses(): readonly HarnessProfile[] {\n return HARNESSES.filter(wiresLifecycle);\n}\n\n/** A list of harnesses as prose spells them — the one join, so the four lists agree. */\nexport function harnessNames(profiles: readonly HarnessProfile[]): string {\n return profiles.map(harnessLabel).join(', ');\n}\n\n/** Every JSON hook step any profile declares, in registry order. */\nfunction jsonHookSteps(): readonly HarnessJsonHooks[] {\n return HARNESSES.flatMap((profile) =>\n profile.install.filter((step): step is HarnessJsonHooks => step.kind === 'json-hooks'),\n );\n}\n\n/**\n * The session-lifecycle hooks this preset offers, named in each harness's schema:\n * `smelt stats` when a turn ends, and — sharing one `SessionStart` event — an opening\n * `smelt map` and a lint of the repository's own instruction files. Only the harnesses\n * whose schema carries these events wire them (`step.lifecycle`), and `cli/hooks.ts`\n * writes the entries under these exact keys — one spelling, so the managed-event list\n * below cannot fall behind what the installer writes.\n *\n * `map` and `lint` deliberately name the **same** event. They are two independent\n * toggles under one key, which is why `cli/hooks.ts` merges their entries into one\n * array rather than spreading two objects (the second would silently replace the\n * first) and why a re-run tells them apart by the command each entry runs.\n */\nexport const LIFECYCLE_EVENTS = {\n stats: 'Stop',\n map: 'SessionStart',\n lint: 'SessionStart',\n} as const;\n\n/**\n * The events this installer manages, across every harness's spelling of them —\n * foreign entries under them are always preserved, and events nobody claims are never\n * touched. Derived, so a harness that spells its pre-tool event a new way is managed\n * by existing.\n */\nexport const MANAGED_EVENTS: readonly string[] = [\n ...new Set(\n jsonHookSteps().flatMap((step) => [\n step.event,\n ...(step.lifecycle ? Object.values(LIFECYCLE_EVENTS) : []),\n ]),\n ),\n];\n\n/** The managed events that wire the PreToolUse guard, across harness spellings. */\nexport const GUARD_EVENTS: readonly string[] = [\n ...new Set(jsonHookSteps().map((step) => step.event)),\n];\n\n/** The JSON hook files a re-run reads installed toggles back from, per harness. */\nexport const JSON_HOOK_FILES: readonly string[] = [\n ...new Set(jsonHookSteps().map((step) => step.file)),\n];\n\n/** Guard-only files whose presence means the guard toggle was installed. */\nexport const GUARD_ONLY_FILES: readonly string[] = [\n ...new Set(\n HARNESSES.flatMap((profile) =>\n profile.install\n .filter((step) => step.kind === 'own-file' && step.guardOnly)\n .map((step) => step.file),\n ),\n ),\n];\n"]}
package/dist/index.d.ts CHANGED
@@ -52,6 +52,16 @@ export { retrieveStats } from './stats.ts';
52
52
  export type { RawRetrieveCounters } from './stats.ts';
53
53
  export { LANGUAGE_PROFILES, profileFor, profileForPath, structuralLanguages, } from './lang/registry.ts';
54
54
  export type { LanguageProfile, LanguageStructure, RepoMapFacts } from './lang/profile.ts';
55
+ /**
56
+ * The harness registry's rendered views. Public for the same reason the ops seam is:
57
+ * something outside this package renders them — the site's `facts.json` generator —
58
+ * and the alternative is a second copy of the tier table typed into a React component,
59
+ * which is exactly the drift `harnessesByTier()` exists to end.
60
+ */
61
+ export { harnessesByTier, harnessNames, HARNESSES, HARNESS_IDS } from './harness/registry.ts';
62
+ export type { HarnessTierGroup } from './harness/registry.ts';
63
+ export { harnessLabel, HARNESS_TIERS, TIER_HONESTY } from './harness/profile.ts';
64
+ export type { HarnessId, HarnessTier } from './harness/profile.ts';
55
65
  export { resolveRun } from './cli/subcommands/smelt.ts';
56
66
  export type { ResolvedRun } from './cli/subcommands/smelt.ts';
57
67
  export { REPO_MAP_FOCUS_RULE } from './repomap/map.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ApplyOptions, MarkerBuilder, MarkerInfo } from './apply.ts';\n\n/**\n * The public surface: a barrel over the modules that hold the reasoning.\n *\n * `createSmelter()` itself lives in `./smelter.ts` so that nothing inside `src/` has\n * to import this file to build a smelter — the CLI's default verb does exactly that,\n * and a barrel that imports the CLI which imports the barrel is a cycle whose only\n * symptom is a registry evaluating to `undefined` in whichever module the loader\n * entered first. Every name is re-exported here, so consumers see no difference.\n */\n\nexport type { ApplyOptions, MarkerBuilder, MarkerInfo };\nexport {\n applyPlan,\n defaultMarker,\n MARKER_FORMAT_VERSION,\n markerPricing,\n reconstruct,\n} from './apply.ts';\nexport { detectLanguage, SUPPORTED_LANGUAGES } from './detect.ts';\nexport * from './errors.ts';\nexport { contentHash, HASH_LENGTH } from './hash.ts';\nexport {\n ALLOWED_NODE_BUILTINS,\n ALLOWED_PACKAGES,\n ALLOWED_URL_SCHEMES,\n assertLocalResource,\n FORBIDDEN_GLOBALS,\n FORBIDDEN_NODE_MODULES,\n FORBIDDEN_PACKAGES,\n} from './net/policy.ts';\nexport type { LocalResource } from './net/policy.ts';\nexport { clearGrammarCache, grammarPath, loadGrammar, WASM_BY_LANGUAGE } from './plan/grammar.ts';\nexport { AUTO_PLANNER_ID, AutoPlanner, planAuto } from './plan/auto.ts';\nexport type { AutoPlannerOptions } from './plan/auto.ts';\nexport { LEXICAL_PLANNER_ID, LexicalPlanner, planLexical } from './plan/lexical.ts';\nexport type { LexicalPlannerOptions } from './plan/lexical.ts';\nexport { DEFAULT_STRATEGY, isStrategy, PLANNERS, STRATEGIES } from './plan/planners.ts';\nexport type { PlannerFactoryOptions } from './plan/planners.ts';\nexport {\n isStructuralLanguage,\n planStructural,\n STRUCTURAL_LANGUAGES,\n STRUCTURAL_PLANNER_ID,\n StructuralPlanner,\n} from './plan/structural.ts';\nexport type { StructuralLanguage, StructuralPlannerOptions } from './plan/structural.ts';\nexport { createRetrieveTool, RETRIEVE_TOOL_NAME } from './retrieve.ts';\nexport { unconfiguredDistillStage, unconfiguredRerankStage } from './stages.ts';\nexport { MemoryElisionStore } from './store.ts';\nexport {\n DIRECTORY_STORE_FORMAT,\n DIRECTORY_STORE_VERSION,\n DirectoryElisionStore,\n} from './store-dir.ts';\nexport type { DirectoryElisionStoreOptions } from './store-dir.ts';\nexport * from './types.ts';\nexport {\n CLI_JSON_FORMAT,\n CLI_NAME,\n cliUsage,\n EXIT,\n formatReport,\n parseSmeltArgs,\n runCli,\n} from './cli/run.ts';\nexport type { AnswerStream, CliIo, CliJsonEnvelope, SmeltInvocation } from './cli/run.ts';\nexport {\n ANTHROPIC_PROMPT_CACHE_FACTS,\n CACHE_BREAKER_RULES,\n detectCacheBreakers,\n findPrefixDivergence,\n} from './cache/prefix.ts';\nexport type {\n CacheWarning,\n PrefixDivergence,\n PromptStructure,\n PromptTool,\n} from './cache/prefix.ts';\nexport { MARKER_LINE_COMMENT_LEADERS, markerForLanguage } from './apply.ts';\nexport {\n buildRepoMap,\n DEFAULT_REPO_IGNORE,\n REPO_MAP_CACHE_CORRUPT_RULE,\n REPO_MAP_ID,\n REPO_MAP_PATH_ONLY_RULE,\n REPO_MAP_RANKED_RULE,\n REPO_MAP_UNREFERENCED_RULE,\n} from './repomap/map.ts';\nexport type {\n RepoMap,\n RepoMapCacheCounts,\n RepoMapEntry,\n RepoMapOptions,\n RepoMapPathEntry,\n RepoMapReason,\n RepoMapWarning,\n} from './repomap/map.ts';\nexport { PAGERANK_DAMPING, PAGERANK_ITERATIONS, rankDefinitions } from './repomap/rank.ts';\nexport type { FileTagsEntry, RankedDefinition } from './repomap/rank.ts';\nexport { extractTags } from './repomap/tags.ts';\nexport type { DefinitionTag, FileTags, ReferenceTag } from './repomap/tags.ts';\nexport { TAGS_CACHE_FORMAT, TAGS_CACHE_VERSION, tagsCacheKey } from './repomap/cache.ts';\nexport { nodeFsReader } from './repomap/reader.ts';\nexport type { DirEntry, FileStat, RepoReader } from './repomap/reader.ts';\nexport {\n CONFIG_FILE_NAME,\n CONFIG_VERSION,\n configuredStore,\n findConfigFile,\n loadNearestConfig,\n parseConfig,\n renderConfig,\n resolveStorePath,\n} from './cli/config.ts';\nexport type { ConfiguredStore, LoadedConfig, SmeltConfig, SmeltConfigStore } from './cli/config.ts';\nexport {\n MEASURE_STUB_FILE,\n measureStubSource,\n RERANK_STUB_FILE,\n rerankStubSource,\n runInit,\n} from './cli/init.ts';\nexport type { InitIo } from './cli/init.ts';\nexport { retrieveStats } from './stats.ts';\nexport type { RawRetrieveCounters } from './stats.ts';\nexport {\n LANGUAGE_PROFILES,\n profileFor,\n profileForPath,\n structuralLanguages,\n} from './lang/registry.ts';\nexport type { LanguageProfile, LanguageStructure, RepoMapFacts } from './lang/profile.ts';\nexport { resolveRun } from './cli/subcommands/smelt.ts';\nexport type { ResolvedRun } from './cli/subcommands/smelt.ts';\nexport { REPO_MAP_FOCUS_RULE } from './repomap/map.ts';\nexport { CLI_MAP_JSON_FORMAT, formatMapReport, resolveMapRun } from './cli/run.ts';\nexport type { CliInvocation, CliMapJsonEnvelope, MapInvocation } from './cli/run.ts';\nexport type { MapReportInput } from './cli/report.ts';\nexport type { ResolvedMapRun } from './cli/subcommands/map.ts';\n\n/**\n * The operations seam — the four verbs and the laws their inputs must satisfy, below\n * every front door. `@smeltjs/mcp` consumes these as an ordinary dependency, so the\n * `smelt` CLI and the MCP tools run the same middle instead of two copies of it. See\n * `src/ops/index.ts` for what belongs here and what stays in an adapter.\n */\nexport {\n budgetFault,\n budgetMalformed,\n budgetRequired,\n mapTree,\n openStore,\n readBlob,\n readCounters,\n readTree,\n resolveStrategy,\n retrieveBytes,\n smeltBlob,\n} from './ops/index.ts';\nexport type {\n BudgetFault,\n BudgetNaming,\n MapTreeOp,\n ReadCountersOp,\n ResolvedStrategy,\n RetrieveBytesOp,\n Ruling,\n SmeltBlobOp,\n SmeltBlobOutcome,\n StrategySource,\n TreeNaming,\n} from './ops/index.ts';\n\n/**\n * Which planner a smelter uses, named by string. The names, their factories, and this\n * type all come from the one {@link PLANNERS} registry in `src/plan/planners.ts`, so\n * the CLI's validation and help text cannot drift from what `createSmelter` builds.\n */\nexport type { Strategy } from './plan/planners.ts';\n\nexport { createSmelter } from './smelter.ts';\nexport type { Smelter, SmelterConfig, SmeltCallOptions } from './smelter.ts';\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE1E;;;;;;;;GAQG;AAEH,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC;AACxD,OAAO,EACL,SAAS,EACT,aAAa,EACb,qBAAqB,EACrB,aAAa,EACb,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClE,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAClG,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxE,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACpF,YAAY,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACxF,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AACnE,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,eAAe,EACf,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,YAAY,EACZ,cAAc,EACd,MAAM,GACP,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,EACL,4BAA4B,EAC5B,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,UAAU,GACX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,2BAA2B,EAC3B,WAAW,EACX,uBAAuB,EACvB,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,OAAO,EACP,kBAAkB,EAClB,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC3F,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACpG,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,OAAO,GACR,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC1F;;;;;GAKG;AACH,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC9F,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACnF,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACrF,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,YAAY,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D;;;;;GAKG;AACH,OAAO,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,OAAO,EACP,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,eAAe,EACf,aAAa,EACb,SAAS,GACV,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,WAAW,EACX,YAAY,EACZ,SAAS,EACT,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,MAAM,EACN,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,UAAU,GACX,MAAM,gBAAgB,CAAC;AAExB;;;;GAIG;AACH,YAAY,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC","sourcesContent":["import type { ApplyOptions, MarkerBuilder, MarkerInfo } from './apply.ts';\n\n/**\n * The public surface: a barrel over the modules that hold the reasoning.\n *\n * `createSmelter()` itself lives in `./smelter.ts` so that nothing inside `src/` has\n * to import this file to build a smelter — the CLI's default verb does exactly that,\n * and a barrel that imports the CLI which imports the barrel is a cycle whose only\n * symptom is a registry evaluating to `undefined` in whichever module the loader\n * entered first. Every name is re-exported here, so consumers see no difference.\n */\n\nexport type { ApplyOptions, MarkerBuilder, MarkerInfo };\nexport {\n applyPlan,\n defaultMarker,\n MARKER_FORMAT_VERSION,\n markerPricing,\n reconstruct,\n} from './apply.ts';\nexport { detectLanguage, SUPPORTED_LANGUAGES } from './detect.ts';\nexport * from './errors.ts';\nexport { contentHash, HASH_LENGTH } from './hash.ts';\nexport {\n ALLOWED_NODE_BUILTINS,\n ALLOWED_PACKAGES,\n ALLOWED_URL_SCHEMES,\n assertLocalResource,\n FORBIDDEN_GLOBALS,\n FORBIDDEN_NODE_MODULES,\n FORBIDDEN_PACKAGES,\n} from './net/policy.ts';\nexport type { LocalResource } from './net/policy.ts';\nexport { clearGrammarCache, grammarPath, loadGrammar, WASM_BY_LANGUAGE } from './plan/grammar.ts';\nexport { AUTO_PLANNER_ID, AutoPlanner, planAuto } from './plan/auto.ts';\nexport type { AutoPlannerOptions } from './plan/auto.ts';\nexport { LEXICAL_PLANNER_ID, LexicalPlanner, planLexical } from './plan/lexical.ts';\nexport type { LexicalPlannerOptions } from './plan/lexical.ts';\nexport { DEFAULT_STRATEGY, isStrategy, PLANNERS, STRATEGIES } from './plan/planners.ts';\nexport type { PlannerFactoryOptions } from './plan/planners.ts';\nexport {\n isStructuralLanguage,\n planStructural,\n STRUCTURAL_LANGUAGES,\n STRUCTURAL_PLANNER_ID,\n StructuralPlanner,\n} from './plan/structural.ts';\nexport type { StructuralLanguage, StructuralPlannerOptions } from './plan/structural.ts';\nexport { createRetrieveTool, RETRIEVE_TOOL_NAME } from './retrieve.ts';\nexport { unconfiguredDistillStage, unconfiguredRerankStage } from './stages.ts';\nexport { MemoryElisionStore } from './store.ts';\nexport {\n DIRECTORY_STORE_FORMAT,\n DIRECTORY_STORE_VERSION,\n DirectoryElisionStore,\n} from './store-dir.ts';\nexport type { DirectoryElisionStoreOptions } from './store-dir.ts';\nexport * from './types.ts';\nexport {\n CLI_JSON_FORMAT,\n CLI_NAME,\n cliUsage,\n EXIT,\n formatReport,\n parseSmeltArgs,\n runCli,\n} from './cli/run.ts';\nexport type { AnswerStream, CliIo, CliJsonEnvelope, SmeltInvocation } from './cli/run.ts';\nexport {\n ANTHROPIC_PROMPT_CACHE_FACTS,\n CACHE_BREAKER_RULES,\n detectCacheBreakers,\n findPrefixDivergence,\n} from './cache/prefix.ts';\nexport type {\n CacheWarning,\n PrefixDivergence,\n PromptStructure,\n PromptTool,\n} from './cache/prefix.ts';\nexport { MARKER_LINE_COMMENT_LEADERS, markerForLanguage } from './apply.ts';\nexport {\n buildRepoMap,\n DEFAULT_REPO_IGNORE,\n REPO_MAP_CACHE_CORRUPT_RULE,\n REPO_MAP_ID,\n REPO_MAP_PATH_ONLY_RULE,\n REPO_MAP_RANKED_RULE,\n REPO_MAP_UNREFERENCED_RULE,\n} from './repomap/map.ts';\nexport type {\n RepoMap,\n RepoMapCacheCounts,\n RepoMapEntry,\n RepoMapOptions,\n RepoMapPathEntry,\n RepoMapReason,\n RepoMapWarning,\n} from './repomap/map.ts';\nexport { PAGERANK_DAMPING, PAGERANK_ITERATIONS, rankDefinitions } from './repomap/rank.ts';\nexport type { FileTagsEntry, RankedDefinition } from './repomap/rank.ts';\nexport { extractTags } from './repomap/tags.ts';\nexport type { DefinitionTag, FileTags, ReferenceTag } from './repomap/tags.ts';\nexport { TAGS_CACHE_FORMAT, TAGS_CACHE_VERSION, tagsCacheKey } from './repomap/cache.ts';\nexport { nodeFsReader } from './repomap/reader.ts';\nexport type { DirEntry, FileStat, RepoReader } from './repomap/reader.ts';\nexport {\n CONFIG_FILE_NAME,\n CONFIG_VERSION,\n configuredStore,\n findConfigFile,\n loadNearestConfig,\n parseConfig,\n renderConfig,\n resolveStorePath,\n} from './cli/config.ts';\nexport type { ConfiguredStore, LoadedConfig, SmeltConfig, SmeltConfigStore } from './cli/config.ts';\nexport {\n MEASURE_STUB_FILE,\n measureStubSource,\n RERANK_STUB_FILE,\n rerankStubSource,\n runInit,\n} from './cli/init.ts';\nexport type { InitIo } from './cli/init.ts';\nexport { retrieveStats } from './stats.ts';\nexport type { RawRetrieveCounters } from './stats.ts';\nexport {\n LANGUAGE_PROFILES,\n profileFor,\n profileForPath,\n structuralLanguages,\n} from './lang/registry.ts';\nexport type { LanguageProfile, LanguageStructure, RepoMapFacts } from './lang/profile.ts';\n/**\n * The harness registry's rendered views. Public for the same reason the ops seam is:\n * something outside this package renders them — the site's `facts.json` generator —\n * and the alternative is a second copy of the tier table typed into a React component,\n * which is exactly the drift `harnessesByTier()` exists to end.\n */\nexport { harnessesByTier, harnessNames, HARNESSES, HARNESS_IDS } from './harness/registry.ts';\nexport type { HarnessTierGroup } from './harness/registry.ts';\nexport { harnessLabel, HARNESS_TIERS, TIER_HONESTY } from './harness/profile.ts';\nexport type { HarnessId, HarnessTier } from './harness/profile.ts';\nexport { resolveRun } from './cli/subcommands/smelt.ts';\nexport type { ResolvedRun } from './cli/subcommands/smelt.ts';\nexport { REPO_MAP_FOCUS_RULE } from './repomap/map.ts';\nexport { CLI_MAP_JSON_FORMAT, formatMapReport, resolveMapRun } from './cli/run.ts';\nexport type { CliInvocation, CliMapJsonEnvelope, MapInvocation } from './cli/run.ts';\nexport type { MapReportInput } from './cli/report.ts';\nexport type { ResolvedMapRun } from './cli/subcommands/map.ts';\n\n/**\n * The operations seam — the four verbs and the laws their inputs must satisfy, below\n * every front door. `@smeltjs/mcp` consumes these as an ordinary dependency, so the\n * `smelt` CLI and the MCP tools run the same middle instead of two copies of it. See\n * `src/ops/index.ts` for what belongs here and what stays in an adapter.\n */\nexport {\n budgetFault,\n budgetMalformed,\n budgetRequired,\n mapTree,\n openStore,\n readBlob,\n readCounters,\n readTree,\n resolveStrategy,\n retrieveBytes,\n smeltBlob,\n} from './ops/index.ts';\nexport type {\n BudgetFault,\n BudgetNaming,\n MapTreeOp,\n ReadCountersOp,\n ResolvedStrategy,\n RetrieveBytesOp,\n Ruling,\n SmeltBlobOp,\n SmeltBlobOutcome,\n StrategySource,\n TreeNaming,\n} from './ops/index.ts';\n\n/**\n * Which planner a smelter uses, named by string. The names, their factories, and this\n * type all come from the one {@link PLANNERS} registry in `src/plan/planners.ts`, so\n * the CLI's validation and help text cannot drift from what `createSmelter` builds.\n */\nexport type { Strategy } from './plan/planners.ts';\n\nexport { createSmelter } from './smelter.ts';\nexport type { Smelter, SmelterConfig, SmeltCallOptions } from './smelter.ts';\n"]}