@smeltjs/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/apply.d.ts.map +1 -1
- package/dist/apply.js.map +1 -1
- package/dist/cache/prefix.d.ts.map +1 -1
- package/dist/cache/prefix.js.map +1 -1
- package/dist/cli/args.d.ts +16 -70
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +18 -338
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/bin.d.ts.map +1 -1
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/config.d.ts +40 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +55 -1
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/hooks.d.ts +33 -38
- package/dist/cli/hooks.d.ts.map +1 -1
- package/dist/cli/hooks.js +140 -485
- package/dist/cli/hooks.js.map +1 -1
- package/dist/cli/init.d.ts +30 -15
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +167 -38
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/report.d.ts.map +1 -1
- package/dist/cli/report.js +2 -2
- package/dist/cli/report.js.map +1 -1
- package/dist/cli/run.d.ts +25 -88
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +32 -341
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/shell.d.ts +106 -0
- package/dist/cli/shell.d.ts.map +1 -0
- package/dist/cli/shell.js +100 -0
- package/dist/cli/shell.js.map +1 -0
- package/dist/cli/subcommands/flags.d.ts +141 -0
- package/dist/cli/subcommands/flags.d.ts.map +1 -0
- package/dist/cli/subcommands/flags.js +208 -0
- package/dist/cli/subcommands/flags.js.map +1 -0
- package/dist/cli/subcommands/hooks.d.ts +18 -0
- package/dist/cli/subcommands/hooks.d.ts.map +1 -0
- package/dist/cli/subcommands/hooks.js +64 -0
- package/dist/cli/subcommands/hooks.js.map +1 -0
- package/dist/cli/subcommands/init.d.ts +19 -0
- package/dist/cli/subcommands/init.d.ts.map +1 -0
- package/dist/cli/subcommands/init.js +44 -0
- package/dist/cli/subcommands/init.js.map +1 -0
- package/dist/cli/subcommands/map.d.ts +75 -0
- package/dist/cli/subcommands/map.d.ts.map +1 -0
- package/dist/cli/subcommands/map.js +132 -0
- package/dist/cli/subcommands/map.js.map +1 -0
- package/dist/cli/subcommands/registry.d.ts +80 -0
- package/dist/cli/subcommands/registry.d.ts.map +1 -0
- package/dist/cli/subcommands/registry.js +138 -0
- package/dist/cli/subcommands/registry.js.map +1 -0
- package/dist/cli/subcommands/retrieve.d.ts +74 -0
- package/dist/cli/subcommands/retrieve.d.ts.map +1 -0
- package/dist/cli/subcommands/retrieve.js +84 -0
- package/dist/cli/subcommands/retrieve.js.map +1 -0
- package/dist/cli/subcommands/smelt.d.ts +107 -0
- package/dist/cli/subcommands/smelt.d.ts.map +1 -0
- package/dist/cli/subcommands/smelt.js +304 -0
- package/dist/cli/subcommands/smelt.js.map +1 -0
- package/dist/cli/subcommands/stats.d.ts +43 -0
- package/dist/cli/subcommands/stats.d.ts.map +1 -0
- package/dist/cli/subcommands/stats.js +62 -0
- package/dist/cli/subcommands/stats.js.map +1 -0
- package/dist/cli/subcommands/subcommand.d.ts +113 -0
- package/dist/cli/subcommands/subcommand.d.ts.map +1 -0
- package/dist/cli/subcommands/subcommand.js +3 -0
- package/dist/cli/subcommands/subcommand.js.map +1 -0
- package/dist/cli/usage.d.ts +3 -0
- package/dist/cli/usage.d.ts.map +1 -0
- package/dist/cli/usage.js +95 -0
- package/dist/cli/usage.js.map +1 -0
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js.map +1 -1
- package/dist/errors.d.ts +21 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +33 -1
- package/dist/errors.js.map +1 -1
- package/dist/harness/aider.d.ts +10 -0
- package/dist/harness/aider.d.ts.map +1 -0
- package/dist/harness/aider.js +21 -0
- package/dist/harness/aider.js.map +1 -0
- package/dist/harness/claude-code.d.ts +3 -0
- package/dist/harness/claude-code.d.ts.map +1 -0
- package/dist/harness/claude-code.js +68 -0
- package/dist/harness/claude-code.js.map +1 -0
- package/dist/harness/cline.d.ts +3 -0
- package/dist/harness/cline.d.ts.map +1 -0
- package/dist/harness/cline.js +54 -0
- package/dist/harness/cline.js.map +1 -0
- package/dist/harness/codex.d.ts +3 -0
- package/dist/harness/codex.d.ts.map +1 -0
- package/dist/harness/codex.js +91 -0
- package/dist/harness/codex.js.map +1 -0
- package/dist/harness/cursor.d.ts +3 -0
- package/dist/harness/cursor.d.ts.map +1 -0
- package/dist/harness/cursor.js +54 -0
- package/dist/harness/cursor.js.map +1 -0
- package/dist/harness/gemini.d.ts +3 -0
- package/dist/harness/gemini.d.ts.map +1 -0
- package/dist/harness/gemini.js +59 -0
- package/dist/harness/gemini.js.map +1 -0
- package/dist/harness/grok.d.ts +3 -0
- package/dist/harness/grok.d.ts.map +1 -0
- package/dist/harness/grok.js +46 -0
- package/dist/harness/grok.js.map +1 -0
- package/dist/harness/hermes.d.ts +3 -0
- package/dist/harness/hermes.d.ts.map +1 -0
- package/dist/harness/hermes.js +71 -0
- package/dist/harness/hermes.js.map +1 -0
- package/dist/harness/kilocode.d.ts +3 -0
- package/dist/harness/kilocode.d.ts.map +1 -0
- package/dist/harness/kilocode.js +35 -0
- package/dist/harness/kilocode.js.map +1 -0
- package/dist/harness/opencode.d.ts +3 -0
- package/dist/harness/opencode.d.ts.map +1 -0
- package/dist/harness/opencode.js +97 -0
- package/dist/harness/opencode.js.map +1 -0
- package/dist/harness/paths.d.ts +17 -0
- package/dist/harness/paths.d.ts.map +1 -0
- package/dist/harness/paths.js +45 -0
- package/dist/harness/paths.js.map +1 -0
- package/dist/harness/profile.d.ts +184 -0
- package/dist/harness/profile.d.ts.map +1 -0
- package/dist/harness/profile.js +26 -0
- package/dist/harness/profile.js.map +1 -0
- package/dist/harness/registry.d.ts +51 -0
- package/dist/harness/registry.d.ts.map +1 -0
- package/dist/harness/registry.js +87 -0
- package/dist/harness/registry.js.map +1 -0
- package/dist/harness/snippet.d.ts +19 -0
- package/dist/harness/snippet.d.ts.map +1 -0
- package/dist/harness/snippet.js +41 -0
- package/dist/harness/snippet.js.map +1 -0
- package/dist/hash.d.ts.map +1 -1
- package/dist/hash.js.map +1 -1
- package/dist/hooks/guard-core.d.ts +14 -14
- package/dist/hooks/guard-core.d.ts.map +1 -1
- package/dist/hooks/guard-core.js +15 -37
- package/dist/hooks/guard-core.js.map +1 -1
- package/dist/hooks/shim.d.ts +95 -4
- package/dist/hooks/shim.d.ts.map +1 -1
- package/dist/hooks/shim.js +87 -2
- package/dist/hooks/shim.js.map +1 -1
- package/dist/hooks/shims/claude-code.d.ts +5 -18
- package/dist/hooks/shims/claude-code.d.ts.map +1 -1
- package/dist/hooks/shims/claude-code.js +9 -56
- package/dist/hooks/shims/claude-code.js.map +1 -1
- package/dist/hooks/shims/cline.d.ts +5 -12
- package/dist/hooks/shims/cline.d.ts.map +1 -1
- package/dist/hooks/shims/cline.js +9 -34
- package/dist/hooks/shims/cline.js.map +1 -1
- package/dist/hooks/shims/codex.d.ts +5 -18
- package/dist/hooks/shims/codex.d.ts.map +1 -1
- package/dist/hooks/shims/codex.js +9 -51
- package/dist/hooks/shims/codex.js.map +1 -1
- package/dist/hooks/shims/cursor.d.ts +5 -14
- package/dist/hooks/shims/cursor.d.ts.map +1 -1
- package/dist/hooks/shims/cursor.js +9 -42
- package/dist/hooks/shims/cursor.js.map +1 -1
- package/dist/hooks/shims/gemini.d.ts +5 -18
- package/dist/hooks/shims/gemini.d.ts.map +1 -1
- package/dist/hooks/shims/gemini.js +9 -48
- package/dist/hooks/shims/gemini.js.map +1 -1
- package/dist/hooks/shims/grok.d.ts +5 -13
- package/dist/hooks/shims/grok.d.ts.map +1 -1
- package/dist/hooks/shims/grok.js +9 -32
- package/dist/hooks/shims/grok.js.map +1 -1
- package/dist/hooks/shims/hermes.d.ts +5 -17
- package/dist/hooks/shims/hermes.d.ts.map +1 -1
- package/dist/hooks/shims/hermes.js +9 -45
- package/dist/hooks/shims/hermes.js.map +1 -1
- package/dist/index.d.ts +33 -76
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -67
- package/dist/index.js.map +1 -1
- package/dist/lang/bash.d.ts.map +1 -1
- package/dist/lang/bash.js.map +1 -1
- package/dist/lang/c.d.ts.map +1 -1
- package/dist/lang/c.js.map +1 -1
- package/dist/lang/c_sharp.d.ts.map +1 -1
- package/dist/lang/c_sharp.js.map +1 -1
- package/dist/lang/cpp.d.ts.map +1 -1
- package/dist/lang/cpp.js.map +1 -1
- package/dist/lang/go.d.ts.map +1 -1
- package/dist/lang/go.js.map +1 -1
- package/dist/lang/java.d.ts.map +1 -1
- package/dist/lang/java.js.map +1 -1
- package/dist/lang/javascript.d.ts.map +1 -1
- package/dist/lang/javascript.js.map +1 -1
- package/dist/lang/kotlin.d.ts.map +1 -1
- package/dist/lang/kotlin.js.map +1 -1
- package/dist/lang/php.d.ts.map +1 -1
- package/dist/lang/php.js.map +1 -1
- package/dist/lang/profile.d.ts.map +1 -1
- package/dist/lang/profile.js.map +1 -1
- package/dist/lang/python.d.ts.map +1 -1
- package/dist/lang/python.js.map +1 -1
- package/dist/lang/registry.d.ts.map +1 -1
- package/dist/lang/registry.js.map +1 -1
- package/dist/lang/ruby.d.ts.map +1 -1
- package/dist/lang/ruby.js.map +1 -1
- package/dist/lang/rust.d.ts.map +1 -1
- package/dist/lang/rust.js.map +1 -1
- package/dist/lang/swift.d.ts.map +1 -1
- package/dist/lang/swift.js.map +1 -1
- package/dist/lang/tsx.d.ts.map +1 -1
- package/dist/lang/tsx.js.map +1 -1
- package/dist/lang/typescript.d.ts.map +1 -1
- package/dist/lang/typescript.js.map +1 -1
- package/dist/net/policy.d.ts +18 -1
- package/dist/net/policy.d.ts.map +1 -1
- package/dist/net/policy.js +6 -4
- package/dist/net/policy.js.map +1 -1
- package/dist/ops/index.d.ts +39 -0
- package/dist/ops/index.d.ts.map +1 -0
- package/dist/ops/index.js +37 -0
- package/dist/ops/index.js.map +1 -0
- package/dist/ops/inputs.d.ts +170 -0
- package/dist/ops/inputs.d.ts.map +1 -0
- package/dist/ops/inputs.js +142 -0
- package/dist/ops/inputs.js.map +1 -0
- package/dist/ops/verbs.d.ts +136 -0
- package/dist/ops/verbs.d.ts.map +1 -0
- package/dist/ops/verbs.js +76 -0
- package/dist/ops/verbs.js.map +1 -0
- package/dist/plan/auto.d.ts +69 -0
- package/dist/plan/auto.d.ts.map +1 -0
- package/dist/plan/auto.js +70 -0
- package/dist/plan/auto.js.map +1 -0
- package/dist/plan/budget.d.ts +21 -0
- package/dist/plan/budget.d.ts.map +1 -0
- package/dist/plan/budget.js +26 -0
- package/dist/plan/budget.js.map +1 -0
- package/dist/plan/grammar.d.ts +14 -0
- package/dist/plan/grammar.d.ts.map +1 -1
- package/dist/plan/grammar.js +47 -6
- package/dist/plan/grammar.js.map +1 -1
- package/dist/plan/lexical.d.ts.map +1 -1
- package/dist/plan/lexical.js +1 -7
- package/dist/plan/lexical.js.map +1 -1
- package/dist/plan/planners.d.ts +35 -2
- package/dist/plan/planners.d.ts.map +1 -1
- package/dist/plan/planners.js +35 -2
- package/dist/plan/planners.js.map +1 -1
- package/dist/plan/structural.d.ts +19 -5
- package/dist/plan/structural.d.ts.map +1 -1
- package/dist/plan/structural.js +180 -33
- package/dist/plan/structural.js.map +1 -1
- package/dist/repomap/cache.d.ts +48 -0
- package/dist/repomap/cache.d.ts.map +1 -1
- package/dist/repomap/cache.js +112 -13
- package/dist/repomap/cache.js.map +1 -1
- package/dist/repomap/io.d.ts +24 -0
- package/dist/repomap/io.d.ts.map +1 -0
- package/dist/repomap/io.js +34 -0
- package/dist/repomap/io.js.map +1 -0
- package/dist/repomap/map.d.ts +69 -6
- package/dist/repomap/map.d.ts.map +1 -1
- package/dist/repomap/map.js +104 -22
- package/dist/repomap/map.js.map +1 -1
- package/dist/repomap/rank.d.ts +26 -2
- package/dist/repomap/rank.d.ts.map +1 -1
- package/dist/repomap/rank.js +20 -0
- package/dist/repomap/rank.js.map +1 -1
- package/dist/repomap/reader.d.ts +73 -0
- package/dist/repomap/reader.d.ts.map +1 -0
- package/dist/repomap/reader.js +30 -0
- package/dist/repomap/reader.js.map +1 -0
- package/dist/repomap/tags.d.ts.map +1 -1
- package/dist/repomap/tags.js.map +1 -1
- package/dist/retrieve.d.ts +8 -0
- package/dist/retrieve.d.ts.map +1 -1
- package/dist/retrieve.js +12 -1
- package/dist/retrieve.js.map +1 -1
- package/dist/smelter.d.ts +79 -0
- package/dist/smelter.d.ts.map +1 -0
- package/dist/smelter.js +65 -0
- package/dist/smelter.js.map +1 -0
- package/dist/stages.d.ts.map +1 -1
- package/dist/stages.js +2 -2
- package/dist/stages.js.map +1 -1
- package/dist/stats.d.ts +2 -1
- package/dist/stats.d.ts.map +1 -1
- package/dist/stats.js +2 -1
- package/dist/stats.js.map +1 -1
- package/dist/store-dir.d.ts +43 -4
- package/dist/store-dir.d.ts.map +1 -1
- package/dist/store-dir.js +48 -5
- package/dist/store-dir.js.map +1 -1
- package/dist/store.d.ts +6 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +6 -0
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +35 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/grammars/tree-sitter-bash.wasm +0 -0
- package/grammars/tree-sitter-c.wasm +0 -0
- package/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/grammars/tree-sitter-cpp.wasm +0 -0
- package/grammars/tree-sitter-go.wasm +0 -0
- package/grammars/tree-sitter-java.wasm +0 -0
- package/grammars/tree-sitter-javascript.wasm +0 -0
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/grammars/tree-sitter-php.wasm +0 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-ruby.wasm +0 -0
- package/grammars/tree-sitter-rust.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- package/package.json +11 -11
- package/dist/cli/resolve.d.ts +0 -103
- package/dist/cli/resolve.d.ts.map +0 -1
- package/dist/cli/resolve.js +0 -106
- package/dist/cli/resolve.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/harness/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAIzC;;;GAGG;AAEH;;;;;;;GAOG;AACH,SAAS,cAAc;IACrB,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,2BAA2B;IACjF,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,OAA8B;IAC3D,OAAO,IAAI,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;AACtE,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,mBAAmB;IACjC,OAAO,IAAI,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC;AAC1D,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AACjD,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,QAAgB;IACxD,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACpC,OAAO,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACvF,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,MAAc,EAAE,IAAI,GAAG,EAAE;IAChE,OAAO,SAAS,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;AAC/E,CAAC","sourcesContent":["import { dirname, isAbsolute, join, relative, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { ShimmedHarnessProfile } from './profile.ts';\n\n/**\n * Where the scripts a harness config points at actually live, and how a profile names\n * one. Path facts only — nothing here reads or writes a file.\n */\n\n/**\n * The `dist` directory of this installed package — where the shipped guard core and\n * shim scripts live. Computed from this module's own location, which is\n * `<pkg>/dist/harness/` in every real run (the CLI executes from `dist`); under the\n * test runner it is `<pkg>/src/harness/`, and the substitution still points at `dist`,\n * which is where the scripts will exist once built — the paths are written into config\n * files for *node* to execute, never imported.\n */\nfunction packageDistDir(): string {\n const here = dirname(fileURLToPath(import.meta.url)); // <pkg>/(dist|src)/harness\n return join(dirname(dirname(here)), 'dist');\n}\n\n/**\n * The shim script a harness's hook command runs. Takes a profile rather than an id,\n * because only a profile that carries a hook schema (or a hand-written adapter) has a\n * shim script on disk: a path for a harness that ships none would name a file the\n * build never produced.\n */\nexport function shimScriptPath(profile: ShimmedHarnessProfile): string {\n return join(packageDistDir(), 'hooks', 'shims', `${profile.id}.js`);\n}\n\n/** The guard core as a module: what the opencode plugin imports at hook time. */\nexport function guardCoreScriptPath(): string {\n return join(packageDistDir(), 'hooks', 'guard-core.js');\n}\n\n/** The `smelt` binary — quoted into the stats and map hook commands. */\nexport function smeltBinPath(): string {\n return join(packageDistDir(), 'cli', 'bin.js');\n}\n\n/** Inside the project, a project-relative path travels with the repo; outside, absolute. */\nexport function portablePath(cwd: string, absolute: string): string {\n const rel = relative(cwd, absolute);\n return rel.startsWith('..') || isAbsolute(rel) ? absolute : rel.split(sep).join('/');\n}\n\n/** `node \"<script>\"` — how every harness config invokes something of smelt's. */\nexport function nodeCommand(cwd: string, script: string, args = ''): string {\n return `node \"${portablePath(cwd, script)}\"${args === '' ? '' : ` ${args}`}`;\n}\n"]}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import type { HarnessHookSchema, ShimAdapter } from '../hooks/shim.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Everything smelt knows about one agent harness, in one place.
|
|
4
|
+
*
|
|
5
|
+
* Before this module existed, a harness's facts lived at seven edit sites: the
|
|
6
|
+
* `HarnessId` union, the `HARNESSES` array, a shim file, a case in `planInstall`, a
|
|
7
|
+
* case in `planRemove`, `MANAGED_EVENTS`, a test fixture — plus a hand-typed id list
|
|
8
|
+
* in the `--harness` help text, which could not be derived because the registry lived
|
|
9
|
+
* in `cli/hooks.ts` and `cli/hooks.ts` imports `CLI_NAME` from `cli/args.ts`. The
|
|
10
|
+
* registry was in the wrong module, and the cycle was the proof.
|
|
11
|
+
*
|
|
12
|
+
* A `HarnessProfile` is the single adapter carrying every per-harness fact, and the
|
|
13
|
+
* registry (`registry.ts`) is `Record<HarnessId, HarnessProfile>` so totality stays a
|
|
14
|
+
* compile error: a new `HarnessId` without a profile does not build. It imports
|
|
15
|
+
* nothing from `cli/`, so `cli/args.ts` derives the `--harness` list and every other
|
|
16
|
+
* rendered view is derived too — the managed event names, the files a re-run reads its
|
|
17
|
+
* toggles back from, the id list in an error message.
|
|
18
|
+
*
|
|
19
|
+
* Installing and removing are **data**, not switch statements: `install` is the list
|
|
20
|
+
* of artefacts `smelt hooks install` writes for this harness, and each artefact's kind
|
|
21
|
+
* is also how `smelt hooks remove` takes it back out — a JSON hook file is merged and
|
|
22
|
+
* strip-merged, a marker block is upserted and stripped, a file that is entirely ours
|
|
23
|
+
* is written and deleted. `planInstall`/`planRemove` fold over the list.
|
|
24
|
+
*
|
|
25
|
+
* The optional sections state capability, never invent it: a harness ships a shim
|
|
26
|
+
* exactly when it carries a {@link hooks} schema (or, for a harness a table cannot
|
|
27
|
+
* express, a hand-written {@link shim}), and only such a profile has a shim script
|
|
28
|
+
* path. What a profile does not claim, the installer does not write.
|
|
29
|
+
*/
|
|
30
|
+
export interface HarnessProfile {
|
|
31
|
+
readonly id: HarnessId;
|
|
32
|
+
/** The harness's own name, as its makers spell it. Shown wherever a tier is. */
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly tier: HarnessTier;
|
|
35
|
+
/** Paths (relative to the project) whose existence means "this harness is in use here". */
|
|
36
|
+
readonly detect: readonly string[];
|
|
37
|
+
/** Paths relative to the user's home directory — "installed on this machine". */
|
|
38
|
+
readonly detectHome: readonly string[];
|
|
39
|
+
/** The standing-instructions file this harness reads (capability matrix column d). */
|
|
40
|
+
readonly instructionFile: string;
|
|
41
|
+
/**
|
|
42
|
+
* The standing-instructions layer — belt and braces under every shim, and the *only*
|
|
43
|
+
* layer an advisory harness has:
|
|
44
|
+
*
|
|
45
|
+
* - `'snippet'`: upsert the shared marker-bracketed block into
|
|
46
|
+
* {@link instructionFile} (a file several harnesses read, like `AGENTS.md`, is
|
|
47
|
+
* planned once); `remove` strips the block and leaves every other byte.
|
|
48
|
+
* - a renderer: a whole file this harness owns, snippet included — KiloCode's rules
|
|
49
|
+
* file carries two manual enforcement legs the shared snippet has no room for;
|
|
50
|
+
* `remove` deletes it.
|
|
51
|
+
*/
|
|
52
|
+
readonly instructions: 'snippet' | HarnessFileContent;
|
|
53
|
+
/** Caveats carried from the capability matrix, shown at install time. */
|
|
54
|
+
readonly caveats: readonly string[];
|
|
55
|
+
/**
|
|
56
|
+
* Everything else `hooks install` writes for this harness, in the order it is
|
|
57
|
+
* written and listed. Empty means instructions only: the advisory tier, where
|
|
58
|
+
* nothing enforces anything and the output says so.
|
|
59
|
+
*/
|
|
60
|
+
readonly install: readonly HarnessInstallStep[];
|
|
61
|
+
/**
|
|
62
|
+
* This harness's native pre-tool hook schema, as data. Present exactly when the
|
|
63
|
+
* harness ships a shim script (`dist/hooks/shims/<id>.js`) — absent for the advisory
|
|
64
|
+
* tier, and for opencode, whose hook API is a JavaScript plugin rather than a stdin
|
|
65
|
+
* schema.
|
|
66
|
+
*/
|
|
67
|
+
readonly hooks?: HarnessHookSchema;
|
|
68
|
+
/**
|
|
69
|
+
* The escape hatch: a hand-written adapter, for a harness whose schema a table
|
|
70
|
+
* cannot express. Wins over {@link hooks} when both are present.
|
|
71
|
+
*/
|
|
72
|
+
readonly shim?: ShimAdapter;
|
|
73
|
+
}
|
|
74
|
+
/** How much smelt is willing to claim about a harness. */
|
|
75
|
+
export type HarnessTier = 'verified' | 'experimental' | 'advisory';
|
|
76
|
+
/**
|
|
77
|
+
* Every harness the preset knows. The registry is keyed by this union, so adding an id
|
|
78
|
+
* without writing its profile is a compile error — and the `--harness` help list, the
|
|
79
|
+
* wizard's table and every error message read the registry, never a second list.
|
|
80
|
+
*/
|
|
81
|
+
export type HarnessId = 'claude-code' | 'codex' | 'gemini' | 'grok' | 'hermes' | 'cursor' | 'opencode' | 'cline' | 'kilocode' | 'aider';
|
|
82
|
+
/** One line of honesty per tier, shown wherever a tier label appears. */
|
|
83
|
+
export declare const TIER_HONESTY: Record<HarnessTier, string>;
|
|
84
|
+
/**
|
|
85
|
+
* What the wizard settled on, as the installer's renderers see it: the toggles, the
|
|
86
|
+
* guard's settings, and the project directory every path is relative to. A renderer
|
|
87
|
+
* takes this and returns bytes; nothing writes.
|
|
88
|
+
*/
|
|
89
|
+
export interface HarnessInstallContext {
|
|
90
|
+
/** Project directory: every path a renderer emits is portable relative to it. */
|
|
91
|
+
readonly cwd: string;
|
|
92
|
+
readonly guard: boolean;
|
|
93
|
+
readonly statsOnStop: boolean;
|
|
94
|
+
readonly mapOnStart: boolean;
|
|
95
|
+
readonly thresholdBytes: number;
|
|
96
|
+
/** The `--budget` every suggested command and the snippet quote. */
|
|
97
|
+
readonly budgetBytes: number;
|
|
98
|
+
}
|
|
99
|
+
/** A file's bytes, rendered from the wizard's choices. */
|
|
100
|
+
export type HarnessFileContent = (ctx: HarnessInstallContext) => string;
|
|
101
|
+
/**
|
|
102
|
+
* One artefact `hooks install` writes. The kind is also the un-write: `json-hooks` is
|
|
103
|
+
* merged in and strip-merged out, `marker-block` is upserted and stripped,
|
|
104
|
+
* `own-file` is written and deleted.
|
|
105
|
+
*/
|
|
106
|
+
export type HarnessInstallStep = HarnessJsonHooks | HarnessMarkerBlock | HarnessOwnFile;
|
|
107
|
+
/**
|
|
108
|
+
* A JSON settings/hooks file the harness reads. Our entries are merged in
|
|
109
|
+
* byte-faithfully and, on `remove`, stripped back out with everything foreign left
|
|
110
|
+
* exactly as it was.
|
|
111
|
+
*/
|
|
112
|
+
export interface HarnessJsonHooks {
|
|
113
|
+
readonly kind: 'json-hooks';
|
|
114
|
+
/** Project-relative path of the file. */
|
|
115
|
+
readonly file: string;
|
|
116
|
+
/** The pre-tool event, in this harness's spelling (`PreToolUse`, `BeforeTool`, …). */
|
|
117
|
+
readonly event: string;
|
|
118
|
+
/**
|
|
119
|
+
* One hook entry per matcher — the tool names the guard wants to see. `undefined`
|
|
120
|
+
* is a matcher-less entry, for a harness whose hook fires on every tool.
|
|
121
|
+
*/
|
|
122
|
+
readonly matchers: readonly (string | undefined)[];
|
|
123
|
+
/**
|
|
124
|
+
* The entry shape: `'command-list'` is Claude Code's `{matcher, hooks:[{type,
|
|
125
|
+
* command}]}`, which Codex, Gemini and Grok mirror; `'bare-command'` is Cursor's
|
|
126
|
+
* `{command}`.
|
|
127
|
+
*/
|
|
128
|
+
readonly entry: 'command-list' | 'bare-command';
|
|
129
|
+
/**
|
|
130
|
+
* 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
|
|
132
|
+
* harnesses wire the guard only, and the wizard's toggles say so.
|
|
133
|
+
*/
|
|
134
|
+
readonly lifecycle: boolean;
|
|
135
|
+
/** Top-level keys a *fresh* file must carry (Cursor's `version: 1`). */
|
|
136
|
+
readonly shape?: {
|
|
137
|
+
readonly version: number;
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** A marker-bracketed block inside a file smelt shares with its owner. */
|
|
141
|
+
export interface HarnessMarkerBlock {
|
|
142
|
+
readonly kind: 'marker-block';
|
|
143
|
+
readonly file: string;
|
|
144
|
+
readonly block: HarnessFileContent;
|
|
145
|
+
/** The marker line opening the block — also how `remove` finds it. */
|
|
146
|
+
readonly start: string;
|
|
147
|
+
readonly end: string;
|
|
148
|
+
/**
|
|
149
|
+
* Refuse the file instead of editing it when it already carries `contains` and none
|
|
150
|
+
* of ours: Codex's `config.toml` may have a hand-written `[features]` table, and an
|
|
151
|
+
* installer that merged into it would be editing what it was never asked to.
|
|
152
|
+
*/
|
|
153
|
+
readonly skipWhen?: {
|
|
154
|
+
readonly contains: string;
|
|
155
|
+
readonly why: string;
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** A file that is entirely smelt's: written whole, deleted whole. */
|
|
159
|
+
export interface HarnessOwnFile {
|
|
160
|
+
readonly kind: 'own-file';
|
|
161
|
+
readonly file: string;
|
|
162
|
+
readonly content: HarnessFileContent;
|
|
163
|
+
/** chmod after writing (Cline's hook must be executable). */
|
|
164
|
+
readonly mode?: number;
|
|
165
|
+
/** True when the file exists only to wire the guard — the guard toggle gates it. */
|
|
166
|
+
readonly guardOnly: boolean;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* A profile that ships a shim script. Only these have a shim path: `shimScriptPath`
|
|
170
|
+
* takes one, so a harness with no shim cannot name a script that was never built.
|
|
171
|
+
*/
|
|
172
|
+
export type ShimmedHarnessProfile = HarnessProfile & ({
|
|
173
|
+
readonly hooks: HarnessHookSchema;
|
|
174
|
+
} | {
|
|
175
|
+
readonly shim: ShimAdapter;
|
|
176
|
+
});
|
|
177
|
+
/** True for a profile that ships a shim script — the narrowing `shimScriptPath` needs. */
|
|
178
|
+
export declare function hasShim(profile: HarnessProfile): profile is ShimmedHarnessProfile;
|
|
179
|
+
/**
|
|
180
|
+
* The adapter this profile's shim script runs: the one its {@link HarnessProfile.hooks}
|
|
181
|
+
* schema describes, or the hand-written escape hatch where it carries one.
|
|
182
|
+
*/
|
|
183
|
+
export declare function shimAdapterOf(profile: ShimmedHarnessProfile): ShimAdapter;
|
|
184
|
+
//# sourceMappingURL=profile.d.ts.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { shimFromSchema } from '../hooks/shim.js';
|
|
2
|
+
/** One line of honesty per tier, shown wherever a tier label appears. */
|
|
3
|
+
export const TIER_HONESTY = {
|
|
4
|
+
verified: 'schema verified against primary docs and pinned by fixtures',
|
|
5
|
+
experimental: 'schema mapped from the 2026-09-02 capability matrix, not yet smoke-tested against the real binary',
|
|
6
|
+
advisory: 'no usable hook API — instructions only, nothing enforces them',
|
|
7
|
+
};
|
|
8
|
+
/** True for a profile that ships a shim script — the narrowing `shimScriptPath` needs. */
|
|
9
|
+
export function hasShim(profile) {
|
|
10
|
+
return profile.hooks !== undefined || profile.shim !== undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The adapter this profile's shim script runs: the one its {@link HarnessProfile.hooks}
|
|
14
|
+
* schema describes, or the hand-written escape hatch where it carries one.
|
|
15
|
+
*/
|
|
16
|
+
export function shimAdapterOf(profile) {
|
|
17
|
+
const { hooks, shim } = profile;
|
|
18
|
+
if (shim !== undefined)
|
|
19
|
+
return shim;
|
|
20
|
+
/* v8 ignore next 5 -- unreachable: a ShimmedHarnessProfile carries one or the other */
|
|
21
|
+
if (hooks === undefined) {
|
|
22
|
+
throw new Error(`smelt: harness "${profile.id}" claims a shim but carries neither a hook schema nor an adapter.`);
|
|
23
|
+
}
|
|
24
|
+
return shimFromSchema(hooks);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=profile.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { HarnessId, HarnessProfile } from './profile.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The registry — every harness smelt knows, one {@link HarnessProfile} each.
|
|
4
|
+
*
|
|
5
|
+
* `Record<HarnessId, HarnessProfile>` on purpose: adding a `HarnessId` in `profile.ts`
|
|
6
|
+
* without writing its profile is a compile error, so the id list and the facts cannot
|
|
7
|
+
* drift. Every derived view — the `--harness` help list, the wizard's table, the
|
|
8
|
+
* managed event names, the files a re-run reads its toggles back from — is computed
|
|
9
|
+
* from this object, never written twice.
|
|
10
|
+
*
|
|
11
|
+
* This module imports **nothing from `cli/`**. That is the point: the registry used to
|
|
12
|
+
* live in `cli/hooks.ts`, which imports `CLI_NAME` from `cli/args.ts`, so `args.ts`
|
|
13
|
+
* could not import it back and the `--harness` help list was hand-typed five lines
|
|
14
|
+
* below two lists that were correctly derived.
|
|
15
|
+
*
|
|
16
|
+
* Key order is meaningful: it is the order every rendered harness list uses (the help
|
|
17
|
+
* text, the wizard's table, the "Known: …" of an unknown-harness error), so keep it
|
|
18
|
+
* stable and append new harnesses at the end.
|
|
19
|
+
*/
|
|
20
|
+
export declare const HARNESS_PROFILES: Readonly<Record<HarnessId, HarnessProfile>>;
|
|
21
|
+
/** Every profile, in registry order. The list every rendered table walks. */
|
|
22
|
+
export declare const HARNESSES: readonly HarnessProfile[];
|
|
23
|
+
/** Every harness id, in registry order — the `--harness` help list, derived. */
|
|
24
|
+
export declare const HARNESS_IDS: readonly HarnessId[];
|
|
25
|
+
/** The profile for a harness id, or `undefined` for a string the user made up. */
|
|
26
|
+
export declare function harnessById(id: string): HarnessProfile | undefined;
|
|
27
|
+
/**
|
|
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.
|
|
33
|
+
*/
|
|
34
|
+
export declare const LIFECYCLE_EVENTS: {
|
|
35
|
+
readonly stats: 'Stop';
|
|
36
|
+
readonly map: 'SessionStart';
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The events this installer manages, across every harness's spelling of them —
|
|
40
|
+
* foreign entries under them are always preserved, and events nobody claims are never
|
|
41
|
+
* touched. Derived, so a harness that spells its pre-tool event a new way is managed
|
|
42
|
+
* by existing.
|
|
43
|
+
*/
|
|
44
|
+
export declare const MANAGED_EVENTS: readonly string[];
|
|
45
|
+
/** The managed events that wire the PreToolUse guard, across harness spellings. */
|
|
46
|
+
export declare const GUARD_EVENTS: readonly string[];
|
|
47
|
+
/** The JSON hook files a re-run reads installed toggles back from, per harness. */
|
|
48
|
+
export declare const JSON_HOOK_FILES: readonly string[];
|
|
49
|
+
/** Guard-only files whose presence means the guard toggle was installed. */
|
|
50
|
+
export declare const GUARD_ONLY_FILES: readonly string[];
|
|
51
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { aider } from './aider.js';
|
|
2
|
+
import { claudeCode } from './claude-code.js';
|
|
3
|
+
import { cline } from './cline.js';
|
|
4
|
+
import { codex } from './codex.js';
|
|
5
|
+
import { cursor } from './cursor.js';
|
|
6
|
+
import { gemini } from './gemini.js';
|
|
7
|
+
import { grok } from './grok.js';
|
|
8
|
+
import { hermes } from './hermes.js';
|
|
9
|
+
import { kilocode } from './kilocode.js';
|
|
10
|
+
import { opencode } from './opencode.js';
|
|
11
|
+
/**
|
|
12
|
+
* The registry — every harness smelt knows, one {@link HarnessProfile} each.
|
|
13
|
+
*
|
|
14
|
+
* `Record<HarnessId, HarnessProfile>` on purpose: adding a `HarnessId` in `profile.ts`
|
|
15
|
+
* without writing its profile is a compile error, so the id list and the facts cannot
|
|
16
|
+
* drift. Every derived view — the `--harness` help list, the wizard's table, the
|
|
17
|
+
* managed event names, the files a re-run reads its toggles back from — is computed
|
|
18
|
+
* from this object, never written twice.
|
|
19
|
+
*
|
|
20
|
+
* This module imports **nothing from `cli/`**. That is the point: the registry used to
|
|
21
|
+
* live in `cli/hooks.ts`, which imports `CLI_NAME` from `cli/args.ts`, so `args.ts`
|
|
22
|
+
* could not import it back and the `--harness` help list was hand-typed five lines
|
|
23
|
+
* below two lists that were correctly derived.
|
|
24
|
+
*
|
|
25
|
+
* Key order is meaningful: it is the order every rendered harness list uses (the help
|
|
26
|
+
* text, the wizard's table, the "Known: …" of an unknown-harness error), so keep it
|
|
27
|
+
* stable and append new harnesses at the end.
|
|
28
|
+
*/
|
|
29
|
+
export const HARNESS_PROFILES = {
|
|
30
|
+
'claude-code': claudeCode,
|
|
31
|
+
codex,
|
|
32
|
+
gemini,
|
|
33
|
+
grok,
|
|
34
|
+
hermes,
|
|
35
|
+
cursor,
|
|
36
|
+
opencode,
|
|
37
|
+
cline,
|
|
38
|
+
kilocode,
|
|
39
|
+
aider,
|
|
40
|
+
};
|
|
41
|
+
/** Every profile, in registry order. The list every rendered table walks. */
|
|
42
|
+
export const HARNESSES = Object.values(HARNESS_PROFILES);
|
|
43
|
+
/** Every harness id, in registry order — the `--harness` help list, derived. */
|
|
44
|
+
export const HARNESS_IDS = HARNESSES.map((profile) => profile.id);
|
|
45
|
+
/** The profile for a harness id, or `undefined` for a string the user made up. */
|
|
46
|
+
export function harnessById(id) {
|
|
47
|
+
return HARNESSES.find((profile) => profile.id === id);
|
|
48
|
+
}
|
|
49
|
+
/** Every JSON hook step any profile declares, in registry order. */
|
|
50
|
+
function jsonHookSteps() {
|
|
51
|
+
return HARNESSES.flatMap((profile) => profile.install.filter((step) => step.kind === 'json-hooks'));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
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.
|
|
59
|
+
*/
|
|
60
|
+
export const LIFECYCLE_EVENTS = { stats: 'Stop', map: 'SessionStart' };
|
|
61
|
+
/**
|
|
62
|
+
* The events this installer manages, across every harness's spelling of them —
|
|
63
|
+
* foreign entries under them are always preserved, and events nobody claims are never
|
|
64
|
+
* touched. Derived, so a harness that spells its pre-tool event a new way is managed
|
|
65
|
+
* by existing.
|
|
66
|
+
*/
|
|
67
|
+
export const MANAGED_EVENTS = [
|
|
68
|
+
...new Set(jsonHookSteps().flatMap((step) => [
|
|
69
|
+
step.event,
|
|
70
|
+
...(step.lifecycle ? Object.values(LIFECYCLE_EVENTS) : []),
|
|
71
|
+
])),
|
|
72
|
+
];
|
|
73
|
+
/** The managed events that wire the PreToolUse guard, across harness spellings. */
|
|
74
|
+
export const GUARD_EVENTS = [
|
|
75
|
+
...new Set(jsonHookSteps().map((step) => step.event)),
|
|
76
|
+
];
|
|
77
|
+
/** The JSON hook files a re-run reads installed toggles back from, per harness. */
|
|
78
|
+
export const JSON_HOOK_FILES = [
|
|
79
|
+
...new Set(jsonHookSteps().map((step) => step.file)),
|
|
80
|
+
];
|
|
81
|
+
/** Guard-only files whose presence means the guard toggle was installed. */
|
|
82
|
+
export const GUARD_ONLY_FILES = [
|
|
83
|
+
...new Set(HARNESSES.flatMap((profile) => profile.install
|
|
84
|
+
.filter((step) => step.kind === 'own-file' && step.guardOnly)
|
|
85
|
+
.map((step) => step.file))),
|
|
86
|
+
];
|
|
87
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 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"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The text every harness shares: the marker lines that bracket a block this installer
|
|
3
|
+
* owns inside somebody else's file, the token that identifies a hook entry as ours,
|
|
4
|
+
* and the instruction snippet itself.
|
|
5
|
+
*/
|
|
6
|
+
/** Marker lines bracketing every block this installer owns inside a shared file. */
|
|
7
|
+
export declare const SNIPPET_START_MD = "<!-- smelt:hooks v1 start -->";
|
|
8
|
+
export declare const SNIPPET_END_MD = "<!-- smelt:hooks v1 end -->";
|
|
9
|
+
export declare const SNIPPET_START_HASH = "# smelt:hooks v1 start";
|
|
10
|
+
export declare const SNIPPET_END_HASH = "# smelt:hooks v1 end";
|
|
11
|
+
/** Substring that identifies a file (or JSON hook entry) as written by this installer. */
|
|
12
|
+
export declare const OURS_TOKEN = "smelt:hooks";
|
|
13
|
+
/**
|
|
14
|
+
* The instruction snippet — belt and braces under every shim, and the *only* layer
|
|
15
|
+
* for advisory harnesses. It teaches the three commands, and in particular what to do
|
|
16
|
+
* after a guard deny: run the named replacement, then `smelt retrieve` per marker.
|
|
17
|
+
*/
|
|
18
|
+
export declare function instructionSnippet(thresholdBytes: number, budgetBytes: number): string;
|
|
19
|
+
//# sourceMappingURL=snippet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snippet.d.ts","sourceRoot":"","sources":["../../src/harness/snippet.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,kCAAkC,CAAC;AAChE,eAAO,MAAM,cAAc,gCAAgC,CAAC;AAC5D,eAAO,MAAM,kBAAkB,2BAA2B,CAAC;AAC3D,eAAO,MAAM,gBAAgB,yBAAyB,CAAC;AAEvD,0FAA0F;AAC1F,eAAO,MAAM,UAAU,gBAAgB,CAAC;AAExC;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAsBtF","sourcesContent":["/**\n * The text every harness shares: the marker lines that bracket a block this installer\n * owns inside somebody else's file, the token that identifies a hook entry as ours,\n * and the instruction snippet itself.\n */\n\n/** Marker lines bracketing every block this installer owns inside a shared file. */\nexport const SNIPPET_START_MD = '<!-- smelt:hooks v1 start -->';\nexport const SNIPPET_END_MD = '<!-- smelt:hooks v1 end -->';\nexport const SNIPPET_START_HASH = '# smelt:hooks v1 start';\nexport const SNIPPET_END_HASH = '# smelt:hooks v1 end';\n\n/** Substring that identifies a file (or JSON hook entry) as written by this installer. */\nexport const OURS_TOKEN = 'smelt:hooks';\n\n/**\n * The instruction snippet — belt and braces under every shim, and the *only* layer\n * for advisory harnesses. It teaches the three commands, and in particular what to do\n * after a guard deny: run the named replacement, then `smelt retrieve` per marker.\n */\nexport function instructionSnippet(thresholdBytes: number, budgetBytes: number): string {\n return `${SNIPPET_START_MD}\n\n## smelt — context discipline\n\nThis project uses [smelt](https://github.com/smeltjs/smelt) to keep large tool output\nout of the context window, reversibly.\n\n- Do not read files over ${String(thresholdBytes)} bytes raw. Run\n \\`smelt <file> --budget ${String(budgetBytes)} --focus <what you are looking for>\\`\n instead (repeat \\`--focus\\` per term). Focused regions survive verbatim; everything\n else collapses into a one-line marker stating what was removed.\n- Every marker ends in \\`retrieve(\"hash\")\\`. \\`smelt retrieve <hash>\\` prints the\n exact original bytes back. Retrieve what you actually need — retrievals are counted,\n and \\`smelt stats\\` reports the honest expansion rate.\n- For orientation, \\`smelt map . --budget ${String(budgetBytes)}\\` prints a ranked\n symbol map of the repository.\n- If a smelt guard hook denies a raw read, run the exact replacement command named in\n the denial, then \\`smelt retrieve\\` any marker you need expanded.\n\n${SNIPPET_END_MD}\n`;\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The text every harness shares: the marker lines that bracket a block this installer
|
|
3
|
+
* owns inside somebody else's file, the token that identifies a hook entry as ours,
|
|
4
|
+
* and the instruction snippet itself.
|
|
5
|
+
*/
|
|
6
|
+
/** Marker lines bracketing every block this installer owns inside a shared file. */
|
|
7
|
+
export const SNIPPET_START_MD = '<!-- smelt:hooks v1 start -->';
|
|
8
|
+
export const SNIPPET_END_MD = '<!-- smelt:hooks v1 end -->';
|
|
9
|
+
export const SNIPPET_START_HASH = '# smelt:hooks v1 start';
|
|
10
|
+
export const SNIPPET_END_HASH = '# smelt:hooks v1 end';
|
|
11
|
+
/** Substring that identifies a file (or JSON hook entry) as written by this installer. */
|
|
12
|
+
export const OURS_TOKEN = 'smelt:hooks';
|
|
13
|
+
/**
|
|
14
|
+
* The instruction snippet — belt and braces under every shim, and the *only* layer
|
|
15
|
+
* for advisory harnesses. It teaches the three commands, and in particular what to do
|
|
16
|
+
* after a guard deny: run the named replacement, then `smelt retrieve` per marker.
|
|
17
|
+
*/
|
|
18
|
+
export function instructionSnippet(thresholdBytes, budgetBytes) {
|
|
19
|
+
return `${SNIPPET_START_MD}
|
|
20
|
+
|
|
21
|
+
## smelt — context discipline
|
|
22
|
+
|
|
23
|
+
This project uses [smelt](https://github.com/smeltjs/smelt) to keep large tool output
|
|
24
|
+
out of the context window, reversibly.
|
|
25
|
+
|
|
26
|
+
- Do not read files over ${String(thresholdBytes)} bytes raw. Run
|
|
27
|
+
\`smelt <file> --budget ${String(budgetBytes)} --focus <what you are looking for>\`
|
|
28
|
+
instead (repeat \`--focus\` per term). Focused regions survive verbatim; everything
|
|
29
|
+
else collapses into a one-line marker stating what was removed.
|
|
30
|
+
- Every marker ends in \`retrieve("hash")\`. \`smelt retrieve <hash>\` prints the
|
|
31
|
+
exact original bytes back. Retrieve what you actually need — retrievals are counted,
|
|
32
|
+
and \`smelt stats\` reports the honest expansion rate.
|
|
33
|
+
- For orientation, \`smelt map . --budget ${String(budgetBytes)}\` prints a ranked
|
|
34
|
+
symbol map of the repository.
|
|
35
|
+
- If a smelt guard hook denies a raw read, run the exact replacement command named in
|
|
36
|
+
the denial, then \`smelt retrieve\` any marker you need expanded.
|
|
37
|
+
|
|
38
|
+
${SNIPPET_END_MD}
|
|
39
|
+
`;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=snippet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snippet.js","sourceRoot":"","sources":["../../src/harness/snippet.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oFAAoF;AACpF,MAAM,CAAC,MAAM,gBAAgB,GAAG,+BAA+B,CAAC;AAChE,MAAM,CAAC,MAAM,cAAc,GAAG,6BAA6B,CAAC;AAC5D,MAAM,CAAC,MAAM,kBAAkB,GAAG,wBAAwB,CAAC;AAC3D,MAAM,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AAEvD,0FAA0F;AAC1F,MAAM,CAAC,MAAM,UAAU,GAAG,aAAa,CAAC;AAExC;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,cAAsB,EAAE,WAAmB;IAC5E,OAAO,GAAG,gBAAgB;;;;;;;2BAOD,MAAM,CAAC,cAAc,CAAC;4BACrB,MAAM,CAAC,WAAW,CAAC;;;;;;4CAMH,MAAM,CAAC,WAAW,CAAC;;;;;EAK7D,cAAc;CACf,CAAC;AACF,CAAC","sourcesContent":["/**\n * The text every harness shares: the marker lines that bracket a block this installer\n * owns inside somebody else's file, the token that identifies a hook entry as ours,\n * and the instruction snippet itself.\n */\n\n/** Marker lines bracketing every block this installer owns inside a shared file. */\nexport const SNIPPET_START_MD = '<!-- smelt:hooks v1 start -->';\nexport const SNIPPET_END_MD = '<!-- smelt:hooks v1 end -->';\nexport const SNIPPET_START_HASH = '# smelt:hooks v1 start';\nexport const SNIPPET_END_HASH = '# smelt:hooks v1 end';\n\n/** Substring that identifies a file (or JSON hook entry) as written by this installer. */\nexport const OURS_TOKEN = 'smelt:hooks';\n\n/**\n * The instruction snippet — belt and braces under every shim, and the *only* layer\n * for advisory harnesses. It teaches the three commands, and in particular what to do\n * after a guard deny: run the named replacement, then `smelt retrieve` per marker.\n */\nexport function instructionSnippet(thresholdBytes: number, budgetBytes: number): string {\n return `${SNIPPET_START_MD}\n\n## smelt — context discipline\n\nThis project uses [smelt](https://github.com/smeltjs/smelt) to keep large tool output\nout of the context window, reversibly.\n\n- Do not read files over ${String(thresholdBytes)} bytes raw. Run\n \\`smelt <file> --budget ${String(budgetBytes)} --focus <what you are looking for>\\`\n instead (repeat \\`--focus\\` per term). Focused regions survive verbatim; everything\n else collapses into a one-line marker stating what was removed.\n- Every marker ends in \\`retrieve(\"hash\")\\`. \\`smelt retrieve <hash>\\` prints the\n exact original bytes back. Retrieve what you actually need — retrievals are counted,\n and \\`smelt stats\\` reports the honest expansion rate.\n- For orientation, \\`smelt map . --budget ${String(budgetBytes)}\\` prints a ranked\n symbol map of the repository.\n- If a smelt guard hook denies a raw read, run the exact replacement command named in\n the denial, then \\`smelt retrieve\\` any marker you need expanded.\n\n${SNIPPET_END_MD}\n`;\n}\n"]}
|
package/dist/hash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,KAAK,CAAC;AAE9B,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD"}
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,KAAK,CAAC;AAE9B,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD","sourcesContent":["import { createHash } from 'node:crypto';\n\n/**\n * Length of the hex digest smelt uses as a retrieval key.\n *\n * The hash goes into every marker, so the model pays for it in tokens on every\n * elision. 16 hex characters is 64 bits — plenty for the number of elisions one\n * session produces, and cheap enough to inline. The store still verifies content on\n * collision rather than trusting the digest, so shortening it is a token decision,\n * not a correctness one.\n */\nexport const HASH_LENGTH = 16;\n\n/** Content hash of a string, over its UTF-8 bytes. Stable across runs and platforms. */\nexport function contentHash(content: string): string {\n return createHash('sha256').update(content, 'utf8').digest('hex').slice(0, HASH_LENGTH);\n}\n"]}
|
package/dist/hash.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAE9B,wFAAwF;AACxF,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;AAC1F,CAAC"}
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAE9B,wFAAwF;AACxF,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;AAC1F,CAAC","sourcesContent":["import { createHash } from 'node:crypto';\n\n/**\n * Length of the hex digest smelt uses as a retrieval key.\n *\n * The hash goes into every marker, so the model pays for it in tokens on every\n * elision. 16 hex characters is 64 bits — plenty for the number of elisions one\n * session produces, and cheap enough to inline. The store still verifies content on\n * collision rather than trusting the digest, so shortening it is a token decision,\n * not a correctness one.\n */\nexport const HASH_LENGTH = 16;\n\n/** Content hash of a string, over its UTF-8 bytes. Stable across runs and platforms. */\nexport function contentHash(content: string): string {\n return createHash('sha256').update(content, 'utf8').digest('hex').slice(0, HASH_LENGTH);\n}\n"]}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The guard core — one zero-dependency node
|
|
2
|
+
* The guard core — one zero-dependency node module, shared by every harness shim.
|
|
3
3
|
*
|
|
4
|
-
* Contract
|
|
5
|
-
* `{ tool, input: { path?, command?, offsetLimited? } }
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Contract: a caller hands `decide` a {@link GuardRequest}
|
|
5
|
+
* (`{ tool, input: { path?, command?, offsetLimited? } }`) with the settings
|
|
6
|
+
* {@link readGuardSettings} read, and gets one {@link GuardDecision} back —
|
|
7
|
+
* `{ action: "allow" | "deny", reason?, suggestion? }`. Each shim translates that into
|
|
8
|
+
* its harness's own schema (exit 2, `permissionDecision`, `cancel:true`, …), and the
|
|
9
|
+
* opencode plugin — the one harness whose hook API is JavaScript — imports this module
|
|
10
|
+
* at hook time and calls the same two functions.
|
|
9
11
|
*
|
|
10
12
|
* Two properties are load-bearing and guarded:
|
|
11
13
|
*
|
|
@@ -112,7 +114,12 @@ export declare const DEFAULT_GUARD_SETTINGS: GuardSettings;
|
|
|
112
114
|
export declare function readGuardSettings(cwd: string, warn: (text: string) => void): GuardSettings;
|
|
113
115
|
/** The same upward walk `cli/config.ts` does, re-implemented to keep this module tiny. */
|
|
114
116
|
export declare function findGuardConfigFile(cwd: string): string | undefined;
|
|
115
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* Parse one {@link GuardRequest} *document* — the request shape as JSON, for an
|
|
119
|
+
* adapter that receives it over a pipe rather than building it in-process.
|
|
120
|
+
* `undefined` means malformed, and a caller that gets it allows and warns: the same
|
|
121
|
+
* fail-open rule the rest of this module lives under.
|
|
122
|
+
*/
|
|
116
123
|
export declare function parseGuardRequest(text: string): GuardRequest | undefined;
|
|
117
124
|
/**
|
|
118
125
|
* The decision, pure given a stat function — so tests exercise every branch without
|
|
@@ -154,11 +161,4 @@ export declare function shellQuote(value: string): string;
|
|
|
154
161
|
export declare function isMainModule(moduleUrl: string): boolean;
|
|
155
162
|
/** Every byte of fd 0 to EOF, retrying EAGAIN — the same shape `cli/bin.ts` uses. */
|
|
156
163
|
export declare function readAllOfStdin(): string;
|
|
157
|
-
/**
|
|
158
|
-
* Run the guard over stdin and print the decision. Exit code 0 in every case —
|
|
159
|
-
* including every failure, which becomes an allow with a stderr warning. A guard
|
|
160
|
-
* subprocess that exits non-zero on its own bug would look like a policy decision
|
|
161
|
-
* to some harnesses, and "my hook crashed" must never read as "denied".
|
|
162
|
-
*/
|
|
163
|
-
export declare function runGuardMain(): void;
|
|
164
164
|
//# sourceMappingURL=guard-core.d.ts.map
|