@smeltjs/core 0.2.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.
- package/README.md +3 -3
- package/dist/agents/guide.d.ts +68 -0
- package/dist/agents/guide.d.ts.map +1 -0
- package/dist/agents/guide.js +72 -0
- package/dist/agents/guide.js.map +1 -0
- package/dist/agents/instructions.d.ts +133 -0
- package/dist/agents/instructions.d.ts.map +1 -0
- package/dist/agents/instructions.js +212 -0
- package/dist/agents/instructions.js.map +1 -0
- package/dist/agents/lint.d.ts +154 -0
- package/dist/agents/lint.d.ts.map +1 -0
- package/dist/agents/lint.js +761 -0
- package/dist/agents/lint.js.map +1 -0
- package/dist/agents/split.d.ts +113 -0
- package/dist/agents/split.d.ts.map +1 -0
- package/dist/agents/split.js +234 -0
- package/dist/agents/split.js.map +1 -0
- 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/agents.d.ts +39 -0
- package/dist/cli/agents.d.ts.map +1 -0
- package/dist/cli/agents.js +121 -0
- package/dist/cli/agents.js.map +1 -0
- package/dist/cli/args.d.ts +17 -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 +60 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +89 -2
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/hooks.d.ts +55 -51
- package/dist/cli/hooks.d.ts.map +1 -1
- package/dist/cli/hooks.js +206 -658
- 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 +23 -0
- package/dist/cli/report.d.ts.map +1 -1
- package/dist/cli/report.js +142 -4
- package/dist/cli/report.js.map +1 -1
- package/dist/cli/run.d.ts +29 -90
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +34 -342
- 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/agents.d.ts +72 -0
- package/dist/cli/subcommands/agents.d.ts.map +1 -0
- package/dist/cli/subcommands/agents.js +162 -0
- package/dist/cli/subcommands/agents.js.map +1 -0
- package/dist/cli/subcommands/flags.d.ts +144 -0
- package/dist/cli/subcommands/flags.d.ts.map +1 -0
- package/dist/cli/subcommands/flags.js +220 -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 +80 -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 +81 -0
- package/dist/cli/subcommands/registry.d.ts.map +1 -0
- package/dist/cli/subcommands/registry.js +141 -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 +92 -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 +60 -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 +47 -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 +72 -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 +209 -0
- package/dist/harness/profile.d.ts.map +1 -0
- package/dist/harness/profile.js +41 -0
- package/dist/harness/profile.js.map +1 -0
- package/dist/harness/registry.d.ts +100 -0
- package/dist/harness/registry.d.ts.map +1 -0
- package/dist/harness/registry.js +144 -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 +43 -76
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -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/text/json-edit.d.ts +71 -0
- package/dist/text/json-edit.d.ts.map +1 -0
- package/dist/text/json-edit.js +219 -0
- package/dist/text/json-edit.js.map +1 -0
- package/dist/types.d.ts +35 -14
- 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":"lint.js","sourceRoot":"","sources":["../../src/agents/lint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAIpD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGrF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH;;wFAEwF;AAExF,yFAAyF;AACzF,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CAAC;AAC1C,gEAAgE;AAChE,MAAM,CAAC,MAAM,cAAc,GAAG,WAAW,CAAC;AAC1C,2CAA2C;AAC3C,MAAM,CAAC,MAAM,qBAAqB,GAAG,kBAAkB,CAAC;AACxD,qDAAqD;AACrD,MAAM,CAAC,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AACpD,qFAAqF;AACrF,MAAM,CAAC,MAAM,0BAA0B,GAAG,uBAAuB,CAAC;AAClE,oFAAoF;AACpF,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAe,CAAC;AAClD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAChD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAE1D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,cAAc;IACd,cAAc;IACd,qBAAqB;IACrB,mBAAmB;IACnB,0BAA0B;IAC1B,kBAAkB;IAClB,iBAAiB;IACjB,sBAAsB;CACd,CAAC;AAEX;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,iBAAiB,CAAC;AAkFtD;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,OAA0B;IACnD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,YAAY,EAAE,CAAC;IAChD,MAAM,GAAG,GAAG,kBAAkB,CAAC;QAC7B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,MAAM;QACN,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;KACpE,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,MAAM,WAAW,GAAoB,EAAE,CAAC;IAExC,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QAC/B,+EAA+E;QAC/E,6EAA6E;QAC7E,+EAA+E;QAC/E,uDAAuD;QACvD,WAAW,CAAC,IAAI,CAAC,GAAG,gBAAgB,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAEpF,+EAA+E;QAC/E,mFAAmF;QACnF,4EAA4E;QAC5E,iEAAiE;QACjE,MAAM,MAAM,GAAG;YACb,KAAK,CAAC,OAAO;YACb,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC;SACnF,CAAC;QACF,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;YAC1B,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnC,QAAQ,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;YACnE,QAAQ,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;YACnE,QAAQ,CAAC,IAAI,CAAC,GAAG,mBAAmB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;YACnD,QAAQ,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;YAClD,QAAQ,CAAC,IAAI,CAAC,GAAG,wBAAwB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;YACxD,QAAQ,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;QACnD,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IAClE,CAAC;IACD,QAAQ,CAAC,IAAI,CAAC,GAAG,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAC;IAEhD,OAAO;QACL,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACjC,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI;YACxB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK;YAC1B,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBACtC,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC;aAC5C,CAAC,CAAC;SACJ,CAAC,CAAC;QACH,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,eAAe,EAAE,GAAG,CAAC,eAAe;QACpC,GAAG,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;QAClF,WAAW;QACX,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC;KAC7C,CAAC;AACJ,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,eAAe,CAAC,MAAwB;IACtD,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACvD,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC;IACpD,OAAO,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AACrC,CAAC;AAED,wEAAwE;AACxE,SAAS,eAAe,CAAC,CAAgB,EAAE,CAAgB;IACzD,MAAM,IAAI,GACR,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;QACnD,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAChD,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;IAClB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,KAAK,GAAI,iBAAuC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;AACzD,CAAC;AAiBD;;;;;;;GAOG;AACH,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,GAAG,GAAkB,EAAE,CAAC;IAC9B,IAAI,KAAyB,CAAC;IAC9B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACvC,MAAM,MAAM,GAAG,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,MAAM,UAAU,GAAG,MAAM,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC;QAC1D,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,IAAI,KAAK,KAAK,SAAS;gBAAE,KAAK,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;iBACnD,IAAI,MAAM,CAAC,CAAC,CAAE,CAAC,UAAU,CAAC,KAAK,CAAC;gBAAE,KAAK,GAAG,SAAS,CAAC;YACzD,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;YACtE,OAAO;QACT,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,KAAK,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC;AAED,uFAAuF;AACvF,SAAS,QAAQ,CAAC,IAAY;IAC5B,OAAO,IAAI;SACR,OAAO,CAAC,6CAA6C,EAAE,EAAE,CAAC;SAC1D,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;SACpB,IAAI,EAAE,CAAC;AACZ,CAAC;AAED;;wFAEwF;AAExF;;;;;;;GAOG;AACH,MAAM,kBAAkB,GAAsB;IAC5C,KAAK;IACL,QAAQ;IACR,OAAO;IACP,OAAO;IACP,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,IAAI;IACJ,UAAU;IACV,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,WAAW;IACX,SAAS;IACT,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,QAAQ;IACR,KAAK;IACL,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,KAAK;IACL,QAAQ;IACR,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,QAAQ;IACR,KAAK;IACL,QAAQ;IACR,OAAO;CACR,CAAC;AAEF;;;;;;;;GAQG;AACH,SAAS,gBAAgB,CAAC,IAAqB,EAAE,KAA6B;IAC5E,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM;YAAE,SAAS;QAC1B,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,IAAI,KAAK,EAAE;YAAE,SAAS;QAC1B,MAAM,MAAM,GAAG,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACpE,IAAI,MAAM,KAAK,SAAS;YAAE,SAAS;QACnC,MAAM,OAAO,GAAG,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;QACvF,MAAM,OAAO,GACX,OAAO,KAAK,QAAQ;YACpB,OAAO,KAAK,OAAO;YACnB,kBAAkB,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,MAAM,EAAE;gBACN,IAAI,EAAE,oBAAoB;gBAC1B,WAAW,EACT,eAAe,OAAO,oDAAoD;oBAC1E,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC;aACnC;SACF,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;wFAEwF;AAExF,kDAAkD;AAClD,MAAM,aAAa,GAAG,yCAAyC,CAAC;AAEhE;;;;;GAKG;AACH,SAAS,WAAW,CAAC,MAAc;IACjC,OAAO,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AACvF,CAAC;AAED,SAAS,aAAa,CACpB,IAAqB,EACrB,KAA6B,EAC7B,IAAY,EACZ,MAAkB;IAElB,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM;YAAE,SAAS;QAC1B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YACtD,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC;YACrD,IAAI,MAAM,KAAK,EAAE,IAAI,UAAU,CAAC,MAAM,CAAC;gBAAE,SAAS;YAClD,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAClD,IAAI,QAAQ,KAAK,SAAS;gBAAE,SAAS;YACrC,IAAI,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC;gBAAE,SAAS;YACrD,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,MAAM,EAAE;oBACN,IAAI,EAAE,cAAc;oBACpB,WAAW,EACT,cAAc,MAAM,sDAAsD;wBAC1E,gCAAgC;wBAChC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC;iBAChC;aACF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;wFAEwF;AAExF,sFAAsF;AACtF,MAAM,OAAO,GAAG,wCAAwC,CAAC;AACzD,2EAA2E;AAC3E,MAAM,SAAS,GAAG,iFAAiF,CAAC;AACpG;;;;;;;;;GASG;AACH,MAAM,WAAW,GACf,kGAAkG,CAAC;AACrG;;;;;;;GAOG;AACH,MAAM,YAAY,GAAG,uCAAuC,CAAC;AAE7D,SAAS,aAAa,CACpB,IAAqB,EACrB,KAA6B,EAC7B,IAAY,EACZ,MAAkB;IAElB,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM;YAAE,SAAS;QAC1B,iFAAiF;QACjF,wCAAwC;QACxC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QAC9D,KAAK,MAAM,KAAK,IAAI,cAAc,CAAC,YAAY,CAAC,EAAE,CAAC;YACjD,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YAC9C,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAS;YAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACjD,6EAA6E;YAC7E,0EAA0E;YAC1E,+DAA+D;YAC/D,IAAI,QAAQ,KAAK,SAAS;gBAAE,SAAS;YACrC,IAAI,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC;gBAAE,SAAS;YACrD,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,MAAM,EAAE;oBACN,IAAI,EAAE,cAAc;oBACpB,WAAW,EACT,WAAW,KAAK,wDAAwD;wBACxE,oDAAoD;wBACpD,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC;iBACjC;aACF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,SAAS,cAAc,CAAC,IAAY;IAClC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,YAAqB,EAAQ,EAAE;QACvD,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAClD,IAAI,KAAK,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,YAAY,CAAC;YAAE,OAAO;QAC7D,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC,CAAC;IACF,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,KAAa,EAAE,EAAE;QACvE,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACjB,OAAO,GAAG,CAAC;IACb,CAAC,CAAC,CAAC;IACH,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC9D,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CAAC,KAAa,EAAE,YAAqB;IACtD,IAAI,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACpC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,yEAAyE;QACzE,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,YAAY,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC5E,CAAC;AAED,2DAA2D;AAC3D,SAAS,UAAU,CAAC,MAAc;IAChC,OAAO,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;AAClG,CAAC;AAED,SAAS,aAAa,CAAC,MAAc;IACnC,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACpD,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,cAAc,CAAC,GAAW,EAAE,KAAa;IAChD,MAAM,IAAI,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IACxB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,GAAG;YAAE,SAAS;QAC1C,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,SAAS,CAAC;YACzC,KAAK,CAAC,GAAG,EAAE,CAAC;QACd,CAAC;;YAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED;;wFAEwF;AAExF,2EAA2E;AAC3E,MAAM,OAAO,GAAG,4EAA4E,CAAC;AAC7F,+EAA+E;AAC/E,MAAM,aAAa,GAAG,qBAAqB,CAAC;AAE5C,SAAS,mBAAmB,CAC1B,IAAqB,EACrB,KAA6B;IAE7B,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM;YAAE,SAAS;QAC1B,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,OAAO,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9D,IAAI,OAAO,KAAK,SAAS;YAAE,SAAS;QACpC,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,MAAM,EAAE;gBACN,IAAI,EAAE,qBAAqB;gBAC3B,WAAW,EACT,gBAAgB,OAAO,iDAAiD;oBACxE,4BAA4B;oBAC5B,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC;aAC3B;SACF,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;wFAEwF;AAExF;;;;;;;;GAQG;AACH,MAAM,YAAY,GAAG,sCAAsC,CAAC;AAC5D,iFAAiF;AACjF,MAAM,QAAQ,GAAG,CAAC,CAAC;AAEnB,SAAS,kBAAkB,CAAC,IAAqB,EAAE,KAA6B;IAC9E,MAAM,GAAG,GAAoB,EAAE,CAAC;IAEhC,+CAA+C;IAC/C,IAAI,UAAmC,CAAC;IACxC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,UAAU,GAAG,IAAI,CAAC;YAClB,KAAK,GAAG,CAAC,CAAC;YACV,SAAS;QACX,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,UAAU,GAAG,SAAS,CAAC;YACvB,SAAS;QACX,CAAC;QACD,IAAI,UAAU,KAAK,SAAS;YAAE,SAAS;QACvC,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,KAAK,IAAI,CAAC,CAAC;QAC7C,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAChB,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;YACxE,UAAU,GAAG,SAAS,CAAC;QACzB,CAAC;IACH,CAAC;IAED,gFAAgF;IAChF,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,GAAG,GAAG,CAAC,CAAC;YACR,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,6EAA6E;QAC7E,mFAAmF;QACnF,MAAM,UAAU,GACd,IAAI,KAAK,EAAE,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;QAChF,IAAI,UAAU,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YAChD,IAAI,GAAG,KAAK,CAAC;gBAAE,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC;YACtC,GAAG,IAAI,CAAC,CAAC;YACT,IAAI,GAAG,KAAK,QAAQ;gBAAE,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CAAC,CAAC;QAC1F,CAAC;aAAM,CAAC;YACN,GAAG,GAAG,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAqB,EAAE,IAAY,EAAE,IAAY;IACzE,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI;QACJ,MAAM,EAAE;YACN,IAAI,EAAE,mBAAmB;YACzB,WAAW,EACT,sCAAsC,IAAI,6BAA6B;gBACvE,4EAA4E;gBAC5E,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC;SACrC;KACF,CAAC;AACJ,CAAC;AAED;;wFAEwF;AAExF,4EAA4E;AAC5E,MAAM,sBAAsB,GAA2C;IACrE,CAAC,sBAAsB,EAAE,4BAA4B,CAAC;IACtD,CAAC,mBAAmB,EAAE,yBAAyB,CAAC;IAChD,CAAC,mBAAmB,EAAE,2BAA2B,CAAC;IAClD,CAAC,oDAAoD,EAAE,wBAAwB,CAAC;IAChF,CAAC,0CAA0C,EAAE,kCAAkC,CAAC;IAChF,CAAC,kBAAkB,EAAE,wBAAwB,CAAC;CAC/C,CAAC;AAEF,SAAS,wBAAwB,CAC/B,IAAqB,EACrB,KAA6B;IAE7B,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM;YAAE,SAAS;QAC1B,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,sBAAsB,EAAE,CAAC;YACrD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,SAAS;YACvC,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,MAAM,EAAE;oBACN,IAAI,EAAE,0BAA0B;oBAChC,WAAW,EACT,WAAW,IAAI,uDAAuD;wBACtE,yEAAyE;wBACzE,yEAAyE;wBACzE,6BAA6B;wBAC7B,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC;iBAC9B;aACF,CAAC,CAAC;YACH,MAAM;QACR,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;wFAEwF;AAExF,qFAAqF;AACrF,MAAM,wBAAwB,GAA2C;IACvE,CAAC,+CAA+C,EAAE,kBAAkB,CAAC;IACrE,CAAC,yDAAyD,EAAE,2BAA2B,CAAC;IACxF,CAAC,iCAAiC,EAAE,oBAAoB,CAAC;IACzD,CAAC,oBAAoB,EAAE,kBAAkB,CAAC;IAC1C,CAAC,+BAA+B,EAAE,oBAAoB,CAAC;IACvD,CAAC,uBAAuB,EAAE,wBAAwB,CAAC;IACnD,CAAC,0CAA0C,EAAE,sBAAsB,CAAC;IACpE;QACE,kFAAkF;QAClF,qBAAqB;KACtB;CACF,CAAC;AAEF,SAAS,iBAAiB,CAAC,IAAqB,EAAE,KAA6B;IAC7E,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM;YAAE,SAAS;QAC1B,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,wBAAwB,EAAE,CAAC;YACvD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,SAAS;YACvC,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,MAAM,EAAE;oBACN,IAAI,EAAE,kBAAkB;oBACxB,WAAW,EACT,UAAU,IAAI,0DAA0D;wBACxE,+EAA+E;wBAC/E,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC;iBACjC;aACF,CAAC,CAAC;YACH,MAAM;QACR,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;wFAEwF;AAExF,SAAS,UAAU,CACjB,OAAwB,EACxB,MAAuB;IAEvB,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IACrC,OAAO,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;AACzD,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,eAAe,CACtB,OAAwB,EACxB,OAAmC;IAEnC,OAAO,OAAO;SACX,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC;SAC3D,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,CAAC;QACP,MAAM,EAAE;YACN,IAAI,EAAE,iBAAiB;YACvB,WAAW,EACT,uBAAuB,OAAO,CAAC,IAAI,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB;gBAC/E,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,uDAAuD;gBAC/E,qCAAqC;gBACrC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC;SAC9B;KACF,CAAC,CAAC,CAAC;AACR,CAAC;AAED;;wFAEwF;AAExF,uFAAuF;AACvF,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAEjC;;;;;;;;;;;;;GAaG;AACH,SAAS,wBAAwB,CAAC,GAAmB;IACnD,8EAA8E;IAC9E,MAAM,MAAM,GAAG,IAAI,GAAG,EAA+B,CAAC;IACtD,MAAM,GAAG,GAAoB,EAAE,CAAC;IAEhC,oFAAoF;IACpF,sEAAsE;IACtE,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC;QAC3B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;QACxC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC7B,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAE1C,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YACpC,IAAI,IAAI,CAAC,MAAM;gBAAE,OAAO;YACxB,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC1E,IAAI,UAAU,CAAC,MAAM,GAAG,qBAAqB;gBAAE,OAAO;YACtD,IAAI,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC;gBAAE,OAAO;YAClC,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACjC,MAAM,OAAO,GAAG,SAAS;iBACtB,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;iBAC9C,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;YACtC,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAO;YAClC,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,MAAM,EAAE;oBACN,IAAI,EAAE,sBAAsB;oBAC5B,WAAW,EACT,+BAA+B,OAAO,qCAAqC;wBAC3E,0EAA0E;wBAC1E,YAAY;wBACZ,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC;iBAC5B;aACF,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["import { nodeFsReader } from '../repomap/reader.ts';\nimport type { RepoReader } from '../repomap/reader.ts';\nimport type { ElisionReason } from '../types.ts';\n\nimport { citing, GUIDE } from './guide.ts';\nimport { ancestorDirs, readInstructionSet, resolvesInTree } from './instructions.ts';\nimport type { InstructionFile, InstructionSet } from './instructions.ts';\n\n/**\n * `smelt agents lint` — the audit of the blob an agent loads on **every** request.\n *\n * smelt's whole subject is what a context window is spent on, and an instruction file\n * is the one blob every request pays for whether or not it is relevant. So the lint is\n * the same three moves smelt makes everywhere else, aimed at a file nobody measures:\n *\n * 1. **Measure, never threshold** (ruling R2). Bytes per level, the most any single\n * request loads, and the repository-wide surface — three numbers, each labelled\n * with the question it answers, because a monorepo makes the last two differ and\n * printing one under the other's heading is a lie about a cost. Plus an imperative\n * count that says out loud it is a heuristic. The only number that can fail a run\n * is `agents.budgetBytes` in `smelt.config.json` — *the user's own*. There is no\n * built-in budget, for exactly the reason `--budget` has none.\n * 2. **Explain every finding** (Law 2, in the {@link ElisionReason} discipline). A\n * finding is a stable `rule` id plus a sentence, and the sentence ends in a phrase\n * from the guide it is applying, attributed — see `./guide.ts`.\n * 3. **Resolve against the real tree** (ruling R3). `dead-path` and `dead-link` are\n * the checks nobody else makes, because everyone else is linting Markdown while\n * the thing that has rotted is the *repository the Markdown describes*. A renamed\n * `src/auth/handlers.ts` does not make the file invalid; it makes it a lie that\n * the agent believes on every request.\n *\n * **Advisory by default.** Findings exit 0. `--strict` turns any finding into exit 1\n * for CI, because a check that cannot be enforced is a check nobody runs, and a check\n * that is enforced by default is smelt deciding somebody's house style for them.\n *\n * The heuristics here are heuristics, and the report says so where it matters: the\n * imperative count carries \"(heuristic)\" in its own label and in every receipt,\n * `generated-boilerplate` calls itself the softest rule in its own explanation, and the\n * closing line of every run states that findings are advisory and exit 0 unless\n * `--strict` was asked for. A rule that fires on a file the guide would call minimal is\n * not automatically a bug in the file — it may be a bug in the rule, or in the guide,\n * and the answer is worth writing down either way (ruling R9; see this repository's own\n * `AGENTS.md`).\n */\n\n/* ------------------------------------------------------------------------------------\n * Rule ids — stable, and the whole machine-readable surface of a finding\n * ---------------------------------------------------------------------------------- */\n\n/** A path-like token in the prose that resolves to nothing in the tree. The flagship. */\nexport const DEAD_PATH_RULE = 'dead-path';\n/** A Markdown link whose relative target is not in the tree. */\nexport const DEAD_LINK_RULE = 'dead-link';\n/** \"always\", \"never\", ALL-CAPS forcing. */\nexport const FORCING_LANGUAGE_RULE = 'forcing-language';\n/** A directory tree, or a run of bare path lines. */\nexport const STRUCTURE_DUMP_RULE = 'structure-dump';\n/** The fingerprints an init script leaves. The softest rule here, and it says so. */\nexport const GENERATED_BOILERPLATE_RULE = 'generated-boilerplate';\n/** A code-style rule that loads on every request to be relevant on some of them. */\nexport const LANGUAGE_RULE_RULE = 'language-rule';\n/** A mirror (CLAUDE.md / GEMINI.md) that has diverged from its AGENTS.md. */\nexport const MIRROR_DRIFT_RULE = 'mirror-drift';\n/** The same instruction present at a level and at one of its ancestors. */\nexport const RESTATED_AT_LEVEL_RULE = 'restated-at-level';\n\n/**\n * The rules a finding can carry, in report order.\n *\n * `Object.freeze`-flat on purpose: the ids are a wire surface. They go into `--json`,\n * into CI greps and into whatever a user filters on, so they are declared once and\n * never spelled again in prose.\n */\nexport const AGENTS_LINT_RULES = [\n DEAD_PATH_RULE,\n DEAD_LINK_RULE,\n FORCING_LANGUAGE_RULE,\n STRUCTURE_DUMP_RULE,\n GENERATED_BOILERPLATE_RULE,\n LANGUAGE_RULE_RULE,\n MIRROR_DRIFT_RULE,\n RESTATED_AT_LEVEL_RULE,\n] as const;\n\n/**\n * The imperative counter's rule id (ruling R6).\n *\n * **Deliberately not in {@link AGENTS_LINT_RULES}.** An imperative is not a defect —\n * an instruction file is *made* of imperatives — so counting them is a measurement,\n * like `outputBytes`, and putting them among the findings would make `--strict` red on\n * every real AGENTS.md and therefore useless. Each counted line still carries a\n * receipt naming the verb that matched, because a heuristic whose matches you cannot\n * inspect is a number nobody can check.\n */\nexport const IMPERATIVE_LINE_RULE = 'imperative-line';\n\n/* ------------------------------------------------------------------------------------\n * The report\n * ---------------------------------------------------------------------------------- */\n\n/** One thing the lint noticed, at one place, with its reason. */\nexport interface AgentsFinding {\n /** Root-relative path of the instruction file. */\n readonly file: string;\n /** 1-based line within that file. */\n readonly line: number;\n /** Stable `rule` id plus the sentence explaining it — Law 2's shape. */\n readonly reason: ElisionReason;\n}\n\n/** What one level of the merged set costs, and what stands beside it. */\nexport interface AgentsLevelReport {\n /** Root-relative directory; `''` is the repository root. */\n readonly dir: string;\n /** The file this level contributes — see {@link InstructionLevel}. */\n readonly path: string;\n readonly bytes: number;\n /** The mirrors at this level, and how each one stands. */\n readonly mirrors: readonly AgentsMirrorReport[];\n}\n\n/** A `CLAUDE.md`/`GEMINI.md` beside an `AGENTS.md`, and whether it can drift. */\nexport interface AgentsMirrorReport {\n readonly path: string;\n readonly bytes: number;\n /**\n * `'symlink'` — the arrangement the guide recommends, and the only one in which\n * drift is impossible. `'copy'` — byte-identical today. `'drift'` — diverged, and a\n * {@link MIRROR_DRIFT_RULE} finding.\n */\n readonly standing: 'symlink' | 'copy' | 'drift';\n}\n\n/** Everything one `smelt agents lint` run measured and found. */\nexport interface AgentsLintReport {\n /** The directory that was linted, as the caller spelled it. */\n readonly root: string;\n /** Root level first. Empty when the tree holds no instruction file at all. */\n readonly levels: readonly AgentsLevelReport[];\n /**\n * The repository-wide instruction surface: every level's primary, summed.\n *\n * **Not a per-request cost** — see {@link perRequestBytes}, and the note in\n * `./instructions.ts` on why the two differ in any monorepo. This is the number the\n * user's `agents.budgetBytes` is compared against, deliberately: a ceiling on the\n * whole surface is one that cannot be met by moving bytes into a second package.\n */\n readonly totalBytes: number;\n /**\n * What the most expensive single request loads: the heaviest level plus its\n * ancestors. Siblings never merge, so they are never summed into this.\n */\n readonly perRequestBytes: number;\n /** Present only when `smelt.config.json` set one. There is no default (R2). */\n readonly budgetBytes?: number;\n /**\n * Lines counted as instructions, each with the verb that matched. The *count* is the\n * headline (`imperatives (heuristic)`); the receipts make it checkable.\n */\n readonly imperatives: readonly AgentsFinding[];\n /** Every advisory finding, grouped by rule in {@link AGENTS_LINT_RULES} order. */\n readonly findings: readonly AgentsFinding[];\n}\n\n/** What `lintAgents` needs. Everything but the root has a default. */\nexport interface AgentsLintOptions {\n /** The repository root to lint. */\n readonly root: string;\n /** The tree seam. Defaults to {@link nodeFsReader}. */\n readonly reader?: RepoReader;\n /** Replaces the built-in ignore list when given. */\n readonly ignore?: readonly string[];\n /** The user's budget, from `smelt.config.json`. Absent means unbudgeted (R2). */\n readonly budgetBytes?: number;\n}\n\n/**\n * Lint the merged set under `root`.\n *\n * Pure over its inputs and its reader: nothing is written, and every filesystem touch\n * goes through {@link RepoReader}, which has no writer on it.\n */\nexport function lintAgents(options: AgentsLintOptions): AgentsLintReport {\n const reader = options.reader ?? nodeFsReader();\n const set = readInstructionSet({\n root: options.root,\n reader,\n ...(options.ignore === undefined ? {} : { ignore: options.ignore }),\n });\n\n const findings: AgentsFinding[] = [];\n const imperatives: AgentsFinding[] = [];\n\n for (const level of set.levels) {\n // The imperative count is a companion to the byte total, so it is counted over\n // exactly what the byte total is counted over: the primaries. A mirror is an\n // alternative spelling of a level, not a second level — counting it would make\n // the headline number describe a request nobody makes.\n imperatives.push(...countImperatives(level.primary, scanLines(level.primary.text)));\n\n // The rules, though, run over the primary **and every mirror that has actually\n // diverged**: a drifted CLAUDE.md is what Claude Code loads, so its own dead paths\n // are real. A symlink or a byte-identical copy is skipped — it would mint a\n // duplicate of every finding on the primary and say nothing new.\n const linted = [\n level.primary,\n ...level.mirrors.filter((mirror) => standingOf(level.primary, mirror) === 'drift'),\n ];\n for (const file of linted) {\n const lines = scanLines(file.text);\n findings.push(...findDeadLinks(file, lines, options.root, reader));\n findings.push(...findDeadPaths(file, lines, options.root, reader));\n findings.push(...findForcingLanguage(file, lines));\n findings.push(...findStructureDumps(file, lines));\n findings.push(...findGeneratedBoilerplate(file, lines));\n findings.push(...findLanguageRules(file, lines));\n }\n findings.push(...findMirrorDrift(level.primary, level.mirrors));\n }\n findings.push(...findRestatedAcrossLevels(set));\n\n return {\n root: options.root,\n levels: set.levels.map((level) => ({\n dir: level.dir,\n path: level.primary.path,\n bytes: level.primary.bytes,\n mirrors: level.mirrors.map((mirror) => ({\n path: mirror.path,\n bytes: mirror.bytes,\n standing: standingOf(level.primary, mirror),\n })),\n })),\n totalBytes: set.totalBytes,\n perRequestBytes: set.perRequestBytes,\n ...(options.budgetBytes === undefined ? {} : { budgetBytes: options.budgetBytes }),\n imperatives,\n findings: findings.toSorted(byRuleThenPlace),\n };\n}\n\n/** How far over the user's budget the merged set is, or `undefined` when it fits. */\nexport function overBudgetBytes(report: AgentsLintReport): number | undefined {\n if (report.budgetBytes === undefined) return undefined;\n const over = report.totalBytes - report.budgetBytes;\n return over > 0 ? over : undefined;\n}\n\n/** Findings in report order: rule first, then where they were found. */\nfunction byRuleThenPlace(a: AgentsFinding, b: AgentsFinding): number {\n const rank =\n ruleOrder(a.reason.rule) - ruleOrder(b.reason.rule) ||\n (a.file < b.file ? -1 : a.file > b.file ? 1 : 0) ||\n a.line - b.line;\n return rank;\n}\n\nfunction ruleOrder(rule: string): number {\n const index = (AGENTS_LINT_RULES as readonly string[]).indexOf(rule);\n return index === -1 ? AGENTS_LINT_RULES.length : index;\n}\n\n/* ------------------------------------------------------------------------------------\n * Scanning: prose versus fences\n * ---------------------------------------------------------------------------------- */\n\n/** One line of an instruction file, with the one fact every rule branches on. */\ninterface ScannedLine {\n /** 1-based. */\n readonly number: number;\n readonly text: string;\n /** True inside a fenced code block, including the fence lines themselves. */\n readonly fenced: boolean;\n /** True for the ``` or ~~~ line that opens a block. */\n readonly opensFence: boolean;\n}\n\n/**\n * Split a file into lines, marking fenced code.\n *\n * The distinction matters in both directions: a `const x = 1` inside a fence is an\n * *example*, not a `language-rule`, and a tree drawing is only a `structure-dump`\n * because it is a fence full of paths. Rules that read prose skip fences; the one\n * rule that reads fences skips prose.\n */\nfunction scanLines(text: string): readonly ScannedLine[] {\n const out: ScannedLine[] = [];\n let fence: string | undefined;\n text.split('\\n').forEach((line, index) => {\n const marker = /^\\s{0,3}(`{3,}|~{3,})/.exec(line);\n const opensFence = marker !== null && fence === undefined;\n if (marker !== null) {\n if (fence === undefined) fence = marker[1]!.slice(0, 1);\n else if (marker[1]!.startsWith(fence)) fence = undefined;\n out.push({ number: index + 1, text: line, fenced: true, opensFence });\n return;\n }\n out.push({ number: index + 1, text: line, fenced: fence !== undefined, opensFence: false });\n });\n return out;\n}\n\n/** A line stripped of list bullets, heading hashes, blockquote marks and bold runs. */\nfunction bareText(text: string): string {\n return text\n .replace(/^\\s*(?:[-*+]\\s+|\\d+[.)]\\s+|#{1,6}\\s+|>\\s*)+/, '')\n .replace(/\\*\\*/g, '')\n .trim();\n}\n\n/* ------------------------------------------------------------------------------------\n * imperative-line — a labelled heuristic (R6)\n * ---------------------------------------------------------------------------------- */\n\n/**\n * The openers counted as an instruction.\n *\n * A closed list, deliberately: an open-ended part-of-speech guess would be a number\n * nobody could reproduce, and this figure is reported beside a byte count that *is*\n * exact. The modal openers the guide itself names — always / never / do not / must /\n * should — are here alongside the verbs an instruction file actually opens with.\n */\nconst IMPERATIVE_OPENERS: readonly string[] = [\n 'add',\n 'always',\n 'avoid',\n 'build',\n 'check',\n 'commit',\n 'create',\n 'do',\n 'document',\n 'ensure',\n 'follow',\n 'format',\n 'ignore',\n 'implement',\n 'install',\n 'keep',\n 'lint',\n 'make',\n 'must',\n 'name',\n 'never',\n 'place',\n 'prefer',\n 'put',\n 'read',\n 'refuse',\n 'remove',\n 'return',\n 'run',\n 'should',\n 'skip',\n 'test',\n 'throw',\n 'treat',\n 'update',\n 'use',\n 'verify',\n 'write',\n];\n\n/**\n * Count the lines that read as instructions, one receipt each.\n *\n * Reported as `imperatives (heuristic)` and never as a precise figure, because it is\n * not one: \"Run `pnpm verify`\" counts and \"The gate is `pnpm verify`\" does not, and\n * both are the same instruction. The number is useful as a *scale* — the guide cites\n * ~150-200 as what a frontier thinking model follows consistently — and useless as a\n * threshold, which is why nothing here compares it to anything.\n */\nfunction countImperatives(file: InstructionFile, lines: readonly ScannedLine[]): AgentsFinding[] {\n const out: AgentsFinding[] = [];\n for (const line of lines) {\n if (line.fenced) continue;\n const bare = bareText(line.text);\n if (bare === '') continue;\n const opener = /^(do not|[A-Za-z']+)/.exec(bare.toLowerCase())?.[1];\n if (opener === undefined) continue;\n const matched = opener === 'do not' ? 'do not' : opener === \"don't\" ? \"don't\" : opener;\n const counted =\n matched === 'do not' ||\n matched === \"don't\" ||\n IMPERATIVE_OPENERS.includes(matched.replace(/'.*$/, ''));\n if (!counted) continue;\n out.push({\n file: file.path,\n line: line.number,\n reason: {\n rule: IMPERATIVE_LINE_RULE,\n explanation:\n `opens with \"${matched}\", so it is counted as one instruction (heuristic)` +\n citing(GUIDE.instructionCeiling),\n },\n });\n }\n return out;\n}\n\n/* ------------------------------------------------------------------------------------\n * dead-link — a Markdown link whose target left the tree\n * ---------------------------------------------------------------------------------- */\n\n/** `[text](target)`, with the target captured. */\nconst MARKDOWN_LINK = /\\[[^\\]]*\\]\\(([^)\\s]+)(?:\\s+\"[^\"]*\")?\\)/g;\n\n/**\n * CommonMark lets a link destination be wrapped in angle brackets — `[x](<a b.md>)`,\n * the only way to write one containing a space. The brackets are delimiters, not part\n * of the path, and statting `<src/kept.ts>` never resolves: the rule would report a\n * live file as dead, which is the exact failure this rule exists to avoid making.\n */\nfunction stripAngles(target: string): string {\n return target.startsWith('<') && target.endsWith('>') ? target.slice(1, -1) : target;\n}\n\nfunction findDeadLinks(\n file: InstructionFile,\n lines: readonly ScannedLine[],\n root: string,\n reader: RepoReader,\n): AgentsFinding[] {\n const out: AgentsFinding[] = [];\n for (const line of lines) {\n if (line.fenced) continue;\n for (const match of line.text.matchAll(MARKDOWN_LINK)) {\n const target = stripFragment(stripAngles(match[1]!));\n if (target === '' || isExternal(target)) continue;\n const resolved = resolveAgainst(file.dir, target);\n if (resolved === undefined) continue;\n if (resolvesInTree(root, reader, resolved)) continue;\n out.push({\n file: file.path,\n line: line.number,\n reason: {\n rule: DEAD_LINK_RULE,\n explanation:\n `links to \\`${target}\\`, which is not in the tree — the pointer the root ` +\n `file exists to be goes nowhere` +\n citing(GUIDE.pointsElsewhere),\n },\n });\n }\n }\n return out;\n}\n\n/* ------------------------------------------------------------------------------------\n * dead-path — the flagship (R3)\n * ---------------------------------------------------------------------------------- */\n\n/** A token with a directory separator: `src/auth`, `./scripts/build.mjs`, `docs/`. */\nconst SLASHED = /^\\.{0,2}\\/?[\\w.+-]+(?:\\/[\\w.+-]+)*\\/?$/;\n/** A bare file name whose extension says \"this is a file in this repo\". */\nconst CODE_FILE = /^[\\w.-]+\\.(?:[cm]?[jt]sx?|json|md|ya?ml|toml|py|rs|go|rb|java|sh|sql|css|html)$/;\n/**\n * A host name: what `example.com/guide` is, and what a repository path never is.\n *\n * A scheme-less URL is the one thing that is shaped exactly like a relative path —\n * dotted word, slash, more words — and the guide this very tool cites is written\n * `aihero.dev/a-complete-guide-to-agents-md` in smelt's own help text. Only the\n * segment before the first slash is tested, so `scripts/build.sh` is untouched: `sh`\n * is a TLD *and* an extension, and which one it is depends entirely on where the dot\n * sits relative to the separator.\n */\nconst DOMAIN_HOST =\n /^[\\w-]+(?:\\.[\\w-]+)*\\.(?:com|org|net|io|dev|ai|app|co|me|sh|so|to|xyz|gg|cloud|page|info|blog)$/i;\n/**\n * `Node.js`, `Vue.js`, `Bun.sh`, `Three.js` — a product, not a file in this tree.\n *\n * Narrow on purpose: one capitalised word, then one of the five suffixes products are\n * actually named with. It costs a bare mention of a PascalCase `Button.js`, which is\n * a real filename — but that is a finding not made, and this is a false accusation not\n * made, and on the flagship rule those two are not worth the same.\n */\nconst PRODUCT_NAME = /^[A-Z][A-Za-z]*\\.(?:js|sh|ai|dev|io)$/;\n\nfunction findDeadPaths(\n file: InstructionFile,\n lines: readonly ScannedLine[],\n root: string,\n reader: RepoReader,\n): AgentsFinding[] {\n const out: AgentsFinding[] = [];\n const seen = new Set<string>();\n for (const line of lines) {\n if (line.fenced) continue;\n // Markdown link targets belong to `dead-link`; blank them so one dead pointer is\n // never reported twice under two rules.\n const withoutLinks = line.text.replace(MARKDOWN_LINK, '[]()');\n for (const token of pathCandidates(withoutLinks)) {\n const key = `${String(line.number)}\u0000${token}`;\n if (seen.has(key)) continue;\n seen.add(key);\n const resolved = resolveAgainst(file.dir, token);\n // A token that climbed out of the tree with `..` is not a path this lint can\n // rule on: it names something outside the repository, and both \"dead\" and\n // \"alive\" would be guesses about a directory nobody handed us.\n if (resolved === undefined) continue;\n if (resolvesInTree(root, reader, resolved)) continue;\n out.push({\n file: file.path,\n line: line.number,\n reason: {\n rule: DEAD_PATH_RULE,\n explanation:\n `names \\`${token}\\`, which resolves to nothing in this tree — an agent ` +\n `reads this on every request and looks there anyway` +\n citing(GUIDE.stalenessPoisons),\n },\n });\n }\n }\n return out;\n}\n\n/**\n * The path-like tokens on one line.\n *\n * Inline code spans first, because a path in an instruction file is nearly always in\n * backticks; then bare words, filtered hard. The filters are the interesting part —\n * every one of them is a false positive this rule made before it had them:\n *\n * - `https://…`, `mailto:` — not tree paths.\n * - `@smeltjs/core`, `@types/node` — package names, which look exactly like paths.\n * - `src/**\\/*.ts` — a glob describes a set, and a set does not resolve.\n * - `pnpm run build`, `and/or` — anything with whitespace, and anything whose\n * segments carry no extension and no separator worth trusting.\n * - `v1.2/v2` style version prose, caught by requiring a real segment shape.\n * - `example.com/guide`, `aihero.dev/…` — a URL somebody wrote without its scheme.\n * - `Node.js`, `Vue.js`, `Bun.sh` — products whose names end in an extension.\n *\n * The last two are why a token *without* a separator is a candidate only when it came\n * out of a code span. In running prose a dotted bare word is far more often a product\n * than a file, and this rule's whole value is that a reader believes it: one confident\n * sentence accusing `Node.js` of having left the tree costs more trust than a dozen\n * real findings earn. In backticks the author has said \"this is a thing in my\n * repository\", and the rule takes them at their word.\n */\nfunction pathCandidates(text: string): readonly string[] {\n const tokens: string[] = [];\n const add = (raw: string, fromCodeSpan: boolean): void => {\n const token = raw.replace(/[),.:;]+$/, '').trim();\n if (token === '' || !isPathLike(token, fromCodeSpan)) return;\n tokens.push(token);\n };\n const withoutCode = text.replace(/`([^`]+)`/g, (_whole, inner: string) => {\n add(inner, true);\n return ' ';\n });\n for (const word of withoutCode.split(/\\s+/)) add(word, false);\n return tokens;\n}\n\n/**\n * Is `token` a path into this tree?\n *\n * `fromCodeSpan` is the author's own signal, and it decides the one ambiguous case: a\n * dotted word with no separator. See {@link pathCandidates}.\n */\nfunction isPathLike(token: string, fromCodeSpan: boolean): boolean {\n if (isExternal(token)) return false;\n if (token.startsWith('@')) return false;\n if (/[*?[\\]{}<>|\"'`\\\\]/.test(token)) return false;\n if (token.startsWith('#')) return false;\n if (!SLASHED.test(token)) return false;\n if (token.includes('/')) {\n // `example.com/guide` — a URL with its scheme left off, not a directory.\n return !DOMAIN_HOST.test(token.split('/')[0] ?? '');\n }\n return fromCodeSpan && !PRODUCT_NAME.test(token) && CODE_FILE.test(token);\n}\n\n/** True for anything that is not a path into this tree. */\nfunction isExternal(target: string): boolean {\n return /^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith('//') || target.startsWith('#');\n}\n\nfunction stripFragment(target: string): string {\n const cut = target.indexOf('#');\n return cut === -1 ? target : target.slice(0, cut);\n}\n\n/**\n * A token in a nested instruction file is relative to *that* file's directory, which\n * is the whole reason a nested file can hold a link the root one cannot. `../` is\n * resolved rather than refused, so a nested file may point back up the tree.\n *\n * `undefined` when the token climbs past the repository root. It used to clamp there —\n * `stack.pop()` on an empty stack is a no-op — which silently turned `../sibling/x.ts`\n * into `sibling/x.ts` and then answered a question about the wrong file, in whichever\n * direction happened to be wrong. Outside the tree, this lint has nothing to say.\n */\nfunction resolveAgainst(dir: string, token: string): string | undefined {\n const base = dir === '' ? [] : dir.split('/');\n const parts = token.replace(/\\/+$/, '').split('/');\n const stack = [...base];\n for (const part of parts) {\n if (part === '' || part === '.') continue;\n if (part === '..') {\n if (stack.length === 0) return undefined;\n stack.pop();\n } else stack.push(part);\n }\n return stack.join('/');\n}\n\n/* ------------------------------------------------------------------------------------\n * forcing-language\n * ---------------------------------------------------------------------------------- */\n\n/** ALL-CAPS words a reader meets as shouting rather than as an acronym. */\nconst SHOUTED = /\\b(ALWAYS|NEVER|MUST|DO NOT|DON'T|REQUIRED|MANDATORY|CRITICAL|IMPORTANT)\\b/;\n/** The two words the guide names in its own example of what *not* to write. */\nconst FORCING_WORDS = /\\b(always|never)\\b/i;\n\nfunction findForcingLanguage(\n file: InstructionFile,\n lines: readonly ScannedLine[],\n): AgentsFinding[] {\n const out: AgentsFinding[] = [];\n for (const line of lines) {\n if (line.fenced) continue;\n const shouted = SHOUTED.exec(line.text)?.[1];\n const forcing = shouted ?? FORCING_WORDS.exec(line.text)?.[1];\n if (forcing === undefined) continue;\n out.push({\n file: file.path,\n line: line.number,\n reason: {\n rule: FORCING_LANGUAGE_RULE,\n explanation:\n `forces with \"${forcing}\" — forcing language spends tokens on emphasis ` +\n `rather than on information` +\n citing(GUIDE.lightTouch),\n },\n });\n }\n return out;\n}\n\n/* ------------------------------------------------------------------------------------\n * structure-dump\n * ---------------------------------------------------------------------------------- */\n\n/**\n * The characters a generated tree listing is drawn with.\n *\n * Both charsets: `tree` draws box-drawing by default and `|--` / `` `-- `` under\n * `--charset=ascii`, and the ASCII form is what lands in a file written on a machine\n * whose terminal was not UTF-8. Missing it made the whole rule silent on a fenced\n * ASCII tree — and because fenced lines skip `dead-path` too, that block produced no\n * finding of any kind.\n */\nconst TREE_DRAWING = /[├└│─]|^\\s*(?:\\|--|`--|\\|\\s{3}|\\+--)/;\n/** How many path-ish lines in a row read as a dump rather than as an example. */\nconst DUMP_RUN = 3;\n\nfunction findStructureDumps(file: InstructionFile, lines: readonly ScannedLine[]): AgentsFinding[] {\n const out: AgentsFinding[] = [];\n\n // A fenced block whose body is a tree drawing.\n let fenceStart: ScannedLine | undefined;\n let drawn = 0;\n for (const line of lines) {\n if (line.opensFence) {\n fenceStart = line;\n drawn = 0;\n continue;\n }\n if (!line.fenced) {\n fenceStart = undefined;\n continue;\n }\n if (fenceStart === undefined) continue;\n if (TREE_DRAWING.test(line.text)) drawn += 1;\n if (drawn === 2) {\n out.push(structureFinding(file, fenceStart.number, 'a directory tree'));\n fenceStart = undefined;\n }\n }\n\n // A run of bare path lines in prose — the same dump without the box characters.\n let run = 0;\n let runStart = 0;\n for (const line of lines) {\n if (line.fenced) {\n run = 0;\n continue;\n }\n const bare = bareText(line.text);\n // A line whose whole content is one token is a path line, not prose — so the\n // code-span rule `dead-path` needs against running text has nothing to guard here.\n const isPathLine =\n bare !== '' && isPathLike(bare.replace(/`/g, '').split(/\\s+/)[0] ?? '', true);\n if (isPathLine && bare.split(/\\s+/).length <= 2) {\n if (run === 0) runStart = line.number;\n run += 1;\n if (run === DUMP_RUN) out.push(structureFinding(file, runStart, 'a run of path lines'));\n } else {\n run = 0;\n }\n }\n\n return out;\n}\n\nfunction structureFinding(file: InstructionFile, line: number, what: string): AgentsFinding {\n return {\n file: file.path,\n line,\n reason: {\n rule: STRUCTURE_DUMP_RULE,\n explanation:\n `spends the every-request budget on ${what} — layout is the fact in a ` +\n `repository that changes most often, so it is also the fact that rots first` +\n citing(GUIDE.describeCapabilities),\n },\n };\n}\n\n/* ------------------------------------------------------------------------------------\n * generated-boilerplate — the softest rule here, and it says so\n * ---------------------------------------------------------------------------------- */\n\n/** The fingerprints an init script leaves behind, with what each one is. */\nconst BOILERPLATE_SIGNATURES: readonly (readonly [RegExp, string])[] = [\n [/\\bauto-?generated\\b/i, 'an \"auto-generated\" marker'],\n [/\\bgenerated by\\b/i, 'a \"generated by\" credit'],\n [/<!--\\s*generated/i, 'a generated-block comment'],\n [/\\b(?:claude|codex|gemini|cursor|agents?)\\s+init\\b/i, 'an init-command credit'],\n [/\\bthis file was (?:created|generated)\\b/i, 'a \"this file was generated\" line'],\n [/\\bdo not edit\\b/i, 'a \"do not edit\" banner'],\n];\n\nfunction findGeneratedBoilerplate(\n file: InstructionFile,\n lines: readonly ScannedLine[],\n): AgentsFinding[] {\n const out: AgentsFinding[] = [];\n for (const line of lines) {\n if (line.fenced) continue;\n for (const [pattern, what] of BOILERPLATE_SIGNATURES) {\n if (!pattern.test(line.text)) continue;\n out.push({\n file: file.path,\n line: line.number,\n reason: {\n rule: GENERATED_BOILERPLATE_RULE,\n explanation:\n `carries ${what}, which suggests this file was generated rather than ` +\n `written. This is the softest rule here: a signature is circumstantial, ` +\n `and a hand-written file may honestly carry one, so it never means more ` +\n `than \"read this file again\"` +\n citing(GUIDE.neverGenerate),\n },\n });\n break;\n }\n }\n return out;\n}\n\n/* ------------------------------------------------------------------------------------\n * language-rule\n * ---------------------------------------------------------------------------------- */\n\n/** Style rules that pay their every-request cost only when the agent writes code. */\nconst LANGUAGE_RULE_SIGNATURES: readonly (readonly [RegExp, string])[] = [\n [/\\bconst\\b[^\\n]*\\blet\\b|\\blet\\b[^\\n]*\\bconst\\b/, 'a const/let rule'],\n [/\\binterface\\b[^\\n]*\\btype\\b|\\btype\\b[^\\n]*\\binterface\\b/, 'an interface-vs-type rule'],\n [/\\bstrict[- ]?null(?:checks)?\\b/i, 'a strict-null rule'],\n [/\\bsemi-?colons?\\b/i, 'a semicolon rule'],\n [/\\b(?:single|double) quotes\\b/i, 'a quote-style rule'],\n [/\\barrow functions?\\b/i, 'an arrow-function rule'],\n [/\\bnamed exports?\\b|\\bdefault exports?\\b/i, 'an export-style rule'],\n [\n /\\btabs? (?:over|versus|vs\\.?) spaces?\\b|\\bspaces? (?:over|versus|vs\\.?) tabs?\\b/i,\n 'an indentation rule',\n ],\n];\n\nfunction findLanguageRules(file: InstructionFile, lines: readonly ScannedLine[]): AgentsFinding[] {\n const out: AgentsFinding[] = [];\n for (const line of lines) {\n if (line.fenced) continue;\n for (const [pattern, what] of LANGUAGE_RULE_SIGNATURES) {\n if (!pattern.test(line.text)) continue;\n out.push({\n file: file.path,\n line: line.number,\n reason: {\n rule: LANGUAGE_RULE_RULE,\n explanation:\n `states ${what}, which is paid for on every request and is relevant on ` +\n `few of them — move it behind a link and it costs only the tasks it applies to` +\n citing(GUIDE.loadWhenRelevant),\n },\n });\n break;\n }\n }\n return out;\n}\n\n/* ------------------------------------------------------------------------------------\n * mirror-drift (R4)\n * ---------------------------------------------------------------------------------- */\n\nfunction standingOf(\n primary: InstructionFile,\n mirror: InstructionFile,\n): AgentsMirrorReport['standing'] {\n if (mirror.symlink) return 'symlink';\n return mirror.text === primary.text ? 'copy' : 'drift';\n}\n\n/**\n * A mirror that has diverged from its `AGENTS.md`.\n *\n * A byte-identical copy is **not** a finding: it is not drift, and calling it one\n * would be smelt enforcing the guide's suggestion rather than reporting a fact. What\n * the report does say, beside every copy, is that a symlink cannot drift — which is\n * the guide's suggestion offered, exactly as `smelt hooks` offers rather than does.\n */\nfunction findMirrorDrift(\n primary: InstructionFile,\n mirrors: readonly InstructionFile[],\n): AgentsFinding[] {\n return mirrors\n .filter((mirror) => standingOf(primary, mirror) === 'drift')\n .map((mirror) => ({\n file: mirror.path,\n line: 1,\n reason: {\n rule: MIRROR_DRIFT_RULE,\n explanation:\n `has diverged from \\`${primary.path}\\` (${String(mirror.bytes)} bytes against ` +\n `${String(primary.bytes)}) — two harnesses are now reading two different sets ` +\n `of instructions from one repository` +\n citing(GUIDE.symlinkMirror),\n },\n }));\n}\n\n/* ------------------------------------------------------------------------------------\n * restated-at-level (R8)\n * ---------------------------------------------------------------------------------- */\n\n/** Below this, a repeated line is a heading or a bullet marker, not an instruction. */\nconst RESTATEMENT_MIN_CHARS = 40;\n\n/**\n * The same instruction present at a level **and at one of its ancestors**.\n *\n * The guide's rule is that a nested file *merges with* the root, so a line written in\n * both is a line the agent is handed twice — paid for twice, and the second copy\n * carrying the risk that only one of them is ever updated. Reported on the deeper file,\n * because that is the copy the ancestor already covers.\n *\n * **Only ancestors.** A merge runs up the tree, never across it: an agent working in\n * `pkg/a` loads the root file and `pkg/a`'s, and never `pkg/b`'s. So a line two\n * siblings happen to share is not a line anybody is handed twice, and reporting it as\n * one would print an explanation about a merge that does not happen — a finding whose\n * sentence is false, which is worse than no finding at all (Law 2).\n */\nfunction findRestatedAcrossLevels(set: InstructionSet): AgentsFinding[] {\n /** dir → the lines that level states, each with the file that states them. */\n const stated = new Map<string, Map<string, string>>();\n const out: AgentsFinding[] = [];\n\n // Levels arrive root-first (`readInstructionSet` sorts by depth), so every ancestor\n // of a level has already been recorded by the time the level is read.\n for (const level of set.levels) {\n const file = level.primary;\n const local = new Map<string, string>();\n stated.set(level.dir, local);\n const ancestors = ancestorDirs(level.dir);\n\n scanLines(file.text).forEach((line) => {\n if (line.fenced) return;\n const normalized = bareText(line.text).toLowerCase().replace(/\\s+/g, ' ');\n if (normalized.length < RESTATEMENT_MIN_CHARS) return;\n if (local.has(normalized)) return;\n local.set(normalized, file.path);\n const earlier = ancestors\n .map((dir) => stated.get(dir)?.get(normalized))\n .find((path) => path !== undefined);\n if (earlier === undefined) return;\n out.push({\n file: file.path,\n line: line.number,\n reason: {\n rule: RESTATED_AT_LEVEL_RULE,\n explanation:\n `repeats a line already in \\`${earlier}\\`, which is above it — the levels ` +\n `merge, so the agent is handed this twice and only one copy will be kept ` +\n `up to date` +\n citing(GUIDE.nestedMerge),\n },\n });\n });\n }\n return out;\n}\n"]}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { InstructionFile } from './instructions.ts';
|
|
2
|
+
/**
|
|
3
|
+
* `smelt agents split` — the **mechanical half** of the guide's refactor, and a plain
|
|
4
|
+
* statement of where the other half is.
|
|
5
|
+
*
|
|
6
|
+
* The guide's advice for a bloated instruction file is to cut it down to a small root
|
|
7
|
+
* that points elsewhere, moving the rest into `docs/`. That advice has two halves, and
|
|
8
|
+
* they are not the same kind of work:
|
|
9
|
+
*
|
|
10
|
+
* - **Mechanical.** Find the sections. Decide what each one's file is called. Move
|
|
11
|
+
* the bytes. Fix every relative link that just moved a directory deeper. Leave a
|
|
12
|
+
* link to each moved file behind. All of this is text manipulation with a right
|
|
13
|
+
* answer, and it is what this module does.
|
|
14
|
+
* - **Judgment.** *Which sections are essential enough to stay in the root?* Which
|
|
15
|
+
* two paragraphs contradict each other? Which rule is too vague to be worth a
|
|
16
|
+
* token? That is a reading of a specific project by someone who knows it, which
|
|
17
|
+
* means a model, which Law 1 forbids smelt from being (ruling R1).
|
|
18
|
+
*
|
|
19
|
+
* So the seam is stated instead of straddled: {@link planSplit} returns a partition
|
|
20
|
+
* anyone can check, and {@link refactorPrompt} returns the guide's own refactor
|
|
21
|
+
* prompt **filled in with this file's actual section headings**, for the user to hand
|
|
22
|
+
* to their own agent. smelt does the part with a right answer and hands over the part
|
|
23
|
+
* that needs a reader — the same shape as the unconfigured rerank stage, and the same
|
|
24
|
+
* reason.
|
|
25
|
+
*
|
|
26
|
+
* Nothing here writes. `cli/agents.ts` owns the confirm-listing discipline
|
|
27
|
+
* (`smelt init`'s, verbatim: every file listed, one final confirm, an existing file
|
|
28
|
+
* never overwritten without a per-file `yes`).
|
|
29
|
+
*/
|
|
30
|
+
/** Where the moved sections go. The guide's own suggested layout. */
|
|
31
|
+
export declare const SPLIT_DIR = "docs";
|
|
32
|
+
/** One `##` section of an instruction file. */
|
|
33
|
+
export interface InstructionSection {
|
|
34
|
+
/** The heading text, without its `##`. */
|
|
35
|
+
readonly title: string;
|
|
36
|
+
/** 1-based line of the heading. */
|
|
37
|
+
readonly line: number;
|
|
38
|
+
/** The section's body, heading excluded, trailing blank lines trimmed. */
|
|
39
|
+
readonly body: string;
|
|
40
|
+
/** UTF-8 bytes of heading + body — what moving this section saves the root file. */
|
|
41
|
+
readonly bytes: number;
|
|
42
|
+
}
|
|
43
|
+
/** One file the split would write, already rendered. */
|
|
44
|
+
export interface SplitFile {
|
|
45
|
+
/** Root-relative, `/`-separated. */
|
|
46
|
+
readonly path: string;
|
|
47
|
+
readonly content: string;
|
|
48
|
+
/** What this file is, for the confirm listing. */
|
|
49
|
+
readonly role: 'root' | 'section';
|
|
50
|
+
}
|
|
51
|
+
/** The whole mechanical proposal. Pure data: nothing on disk has moved. */
|
|
52
|
+
export interface SplitPlan {
|
|
53
|
+
/** The file that was read. */
|
|
54
|
+
readonly source: string;
|
|
55
|
+
/** Its size before. */
|
|
56
|
+
readonly beforeBytes: number;
|
|
57
|
+
/** What the root file would become. */
|
|
58
|
+
readonly afterBytes: number;
|
|
59
|
+
/** The sections found, in document order. */
|
|
60
|
+
readonly sections: readonly InstructionSection[];
|
|
61
|
+
/** Root file first, then one file per moved section. */
|
|
62
|
+
readonly files: readonly SplitFile[];
|
|
63
|
+
/**
|
|
64
|
+
* Why the plan is empty, when it is: a file with no `##` sections has nothing to
|
|
65
|
+
* partition **by**, and inventing a partition would be the judgment half.
|
|
66
|
+
*/
|
|
67
|
+
readonly refusal?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Partition one instruction file into a root and one file per `##` section.
|
|
71
|
+
*
|
|
72
|
+
* The partition is **by heading and nothing else**. That is the honest mechanical
|
|
73
|
+
* rule: a split by heading is reproducible, reversible by hand, and obviously not a
|
|
74
|
+
* judgment about what matters — which is exactly what it must not pretend to be.
|
|
75
|
+
*
|
|
76
|
+
* The preamble (everything above the first `##`) stays in the root, because in a
|
|
77
|
+
* file written to the guide's shape the preamble *is* the essentials: the title and
|
|
78
|
+
* the one-sentence description.
|
|
79
|
+
*/
|
|
80
|
+
export declare function planSplit(file: InstructionFile): SplitPlan;
|
|
81
|
+
/**
|
|
82
|
+
* Every `##` section, fences respected.
|
|
83
|
+
*
|
|
84
|
+
* A `## ` inside a fenced block is a Markdown example or a shell comment, not a
|
|
85
|
+
* heading — splitting on one would cut a code block in half, which is the kind of
|
|
86
|
+
* damage that makes a tool untrustworthy the first time it happens.
|
|
87
|
+
*/
|
|
88
|
+
export declare function readSections(text: string): readonly InstructionSection[];
|
|
89
|
+
/**
|
|
90
|
+
* Fix the relative links in a section that is moving one directory down.
|
|
91
|
+
*
|
|
92
|
+
* A section that said `[the gate](CONTRIBUTING.md)` at the root must say
|
|
93
|
+
* `../CONTRIBUTING.md` from `docs/`, or the split turns a working pointer into a
|
|
94
|
+
* `dead-link` — the lint's own flagship finding, minted by the tool that fixes it.
|
|
95
|
+
* External links, anchors and paths that already climb out are left alone.
|
|
96
|
+
*/
|
|
97
|
+
export declare function rewriteLinks(body: string): string;
|
|
98
|
+
/**
|
|
99
|
+
* The guide's refactor prompt, filled in with this file's real sections.
|
|
100
|
+
*
|
|
101
|
+
* This is the seam, printed. smelt will not decide which of somebody's sections are
|
|
102
|
+
* essential — that is a reading of their project, and Law 1 keeps smelt offline and
|
|
103
|
+
* modelless — so it hands over the guide's own five-step prompt with the section list
|
|
104
|
+
* already substituted in, ready to paste into whichever agent the user is running.
|
|
105
|
+
*
|
|
106
|
+
* The five steps are the guide's; the wording is smelt's summary of them, with the
|
|
107
|
+
* article cited so the reader can check it. The list of sections is measured, not
|
|
108
|
+
* invented: every heading and every byte count comes from the file on disk.
|
|
109
|
+
*/
|
|
110
|
+
export declare function refactorPrompt(plan: SplitPlan): string;
|
|
111
|
+
/** The one sentence that says where smelt stops. Printed by the CLI, and in its help. */
|
|
112
|
+
export declare function splitSeamNotice(): string;
|
|
113
|
+
//# sourceMappingURL=split.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split.d.ts","sourceRoot":"","sources":["../../src/agents/split.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,qEAAqE;AACrE,eAAO,MAAM,SAAS,SAAS,CAAC;AAEhC,+CAA+C;AAC/C,MAAM,WAAW,kBAAkB;IACjC,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mCAAmC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oFAAoF;IACpF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,wDAAwD;AACxD,MAAM,WAAW,SAAS;IACxB,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,2EAA2E;AAC3E,MAAM,WAAW,SAAS;IACxB,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uBAAuB;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,uCAAuC;IACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACjD,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,eAAe,GAAG,SAAS,CAyC1D;AAMD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,kBAAkB,EAAE,CA+BxE;AAgCD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYjD;AAwBD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CA8BtD;AAED,yFAAyF;AACzF,wBAAgB,eAAe,IAAI,MAAM,CASxC","sourcesContent":["import { citing, GUIDE, GUIDE_URL } from './guide.ts';\nimport type { InstructionFile } from './instructions.ts';\n\n/**\n * `smelt agents split` — the **mechanical half** of the guide's refactor, and a plain\n * statement of where the other half is.\n *\n * The guide's advice for a bloated instruction file is to cut it down to a small root\n * that points elsewhere, moving the rest into `docs/`. That advice has two halves, and\n * they are not the same kind of work:\n *\n * - **Mechanical.** Find the sections. Decide what each one's file is called. Move\n * the bytes. Fix every relative link that just moved a directory deeper. Leave a\n * link to each moved file behind. All of this is text manipulation with a right\n * answer, and it is what this module does.\n * - **Judgment.** *Which sections are essential enough to stay in the root?* Which\n * two paragraphs contradict each other? Which rule is too vague to be worth a\n * token? That is a reading of a specific project by someone who knows it, which\n * means a model, which Law 1 forbids smelt from being (ruling R1).\n *\n * So the seam is stated instead of straddled: {@link planSplit} returns a partition\n * anyone can check, and {@link refactorPrompt} returns the guide's own refactor\n * prompt **filled in with this file's actual section headings**, for the user to hand\n * to their own agent. smelt does the part with a right answer and hands over the part\n * that needs a reader — the same shape as the unconfigured rerank stage, and the same\n * reason.\n *\n * Nothing here writes. `cli/agents.ts` owns the confirm-listing discipline\n * (`smelt init`'s, verbatim: every file listed, one final confirm, an existing file\n * never overwritten without a per-file `yes`).\n */\n\n/** Where the moved sections go. The guide's own suggested layout. */\nexport const SPLIT_DIR = 'docs';\n\n/** One `##` section of an instruction file. */\nexport interface InstructionSection {\n /** The heading text, without its `##`. */\n readonly title: string;\n /** 1-based line of the heading. */\n readonly line: number;\n /** The section's body, heading excluded, trailing blank lines trimmed. */\n readonly body: string;\n /** UTF-8 bytes of heading + body — what moving this section saves the root file. */\n readonly bytes: number;\n}\n\n/** One file the split would write, already rendered. */\nexport interface SplitFile {\n /** Root-relative, `/`-separated. */\n readonly path: string;\n readonly content: string;\n /** What this file is, for the confirm listing. */\n readonly role: 'root' | 'section';\n}\n\n/** The whole mechanical proposal. Pure data: nothing on disk has moved. */\nexport interface SplitPlan {\n /** The file that was read. */\n readonly source: string;\n /** Its size before. */\n readonly beforeBytes: number;\n /** What the root file would become. */\n readonly afterBytes: number;\n /** The sections found, in document order. */\n readonly sections: readonly InstructionSection[];\n /** Root file first, then one file per moved section. */\n readonly files: readonly SplitFile[];\n /**\n * Why the plan is empty, when it is: a file with no `##` sections has nothing to\n * partition **by**, and inventing a partition would be the judgment half.\n */\n readonly refusal?: string;\n}\n\n/**\n * Partition one instruction file into a root and one file per `##` section.\n *\n * The partition is **by heading and nothing else**. That is the honest mechanical\n * rule: a split by heading is reproducible, reversible by hand, and obviously not a\n * judgment about what matters — which is exactly what it must not pretend to be.\n *\n * The preamble (everything above the first `##`) stays in the root, because in a\n * file written to the guide's shape the preamble *is* the essentials: the title and\n * the one-sentence description.\n */\nexport function planSplit(file: InstructionFile): SplitPlan {\n const sections = readSections(file.text);\n const before = file.bytes;\n\n if (sections.length === 0) {\n return {\n source: file.path,\n beforeBytes: before,\n afterBytes: before,\n sections: [],\n files: [],\n refusal:\n `${file.path} has no \\`##\\` sections, so there is nothing to partition by. ` +\n `A split invented without headings would be smelt deciding what matters, ` +\n `which is the half of the refactor it deliberately does not do.`,\n };\n }\n\n const used = new Set<string>();\n const moved = sections.map((section) => {\n const path = `${SPLIT_DIR}/${uniqueSlug(section.title, used)}.md`;\n return { section, path };\n });\n\n const rootContent = renderRoot(file.text, sections, moved);\n const files: SplitFile[] = [\n { path: file.path, content: rootContent, role: 'root' },\n ...moved.map(({ section, path }): SplitFile => ({\n path,\n content: renderSection(section),\n role: 'section',\n })),\n ];\n\n return {\n source: file.path,\n beforeBytes: before,\n afterBytes: Buffer.byteLength(rootContent, 'utf8'),\n sections,\n files,\n };\n}\n\n/* ------------------------------------------------------------------------------------\n * Reading sections\n * ---------------------------------------------------------------------------------- */\n\n/**\n * Every `##` section, fences respected.\n *\n * A `## ` inside a fenced block is a Markdown example or a shell comment, not a\n * heading — splitting on one would cut a code block in half, which is the kind of\n * damage that makes a tool untrustworthy the first time it happens.\n */\nexport function readSections(text: string): readonly InstructionSection[] {\n const lines = text.split('\\n');\n const starts: { readonly index: number; readonly title: string }[] = [];\n let fence: string | undefined;\n\n lines.forEach((line, index) => {\n const marker = /^\\s{0,3}(`{3,}|~{3,})/.exec(line);\n if (marker !== null) {\n if (fence === undefined) fence = marker[1]!.slice(0, 1);\n else if (marker[1]!.startsWith(fence)) fence = undefined;\n return;\n }\n if (fence !== undefined) return;\n const heading = /^##\\s+(.+?)\\s*$/.exec(line);\n if (heading !== null) starts.push({ index, title: heading[1]! });\n });\n\n return starts.map(({ index, title }, position) => {\n const end = starts[position + 1]?.index ?? lines.length;\n const body = lines\n .slice(index + 1, end)\n .join('\\n')\n .replace(/^\\n+/, '')\n .replace(/\\n+$/, '');\n return {\n title,\n line: index + 1,\n body,\n bytes: Buffer.byteLength(`${lines[index]!}\\n${body}\\n`, 'utf8'),\n };\n });\n}\n\n/* ------------------------------------------------------------------------------------\n * Rendering\n * ---------------------------------------------------------------------------------- */\n\n/** The moved section as its own file: the heading promoted to `#`, links rewritten. */\nfunction renderSection(section: InstructionSection): string {\n return `# ${section.title}\\n\\n${rewriteLinks(section.body)}\\n`;\n}\n\n/**\n * The root file: everything above the first `##`, then one link per moved section.\n *\n * The link list is the point of the whole exercise — the guide's ideal root file is\n * small and points elsewhere — so it is written plainly, one line per section, with\n * the section's own title as the link text.\n */\nfunction renderRoot(\n text: string,\n sections: readonly InstructionSection[],\n moved: readonly { readonly section: InstructionSection; readonly path: string }[],\n): string {\n const lines = text.split('\\n');\n const preamble = lines\n .slice(0, (sections[0]?.line ?? lines.length + 1) - 1)\n .join('\\n')\n .replace(/\\n+$/, '');\n const links = moved.map(({ section, path }) => `- [${section.title}](${path})`).join('\\n');\n return `${preamble}\\n\\n## More\\n\\n${links}\\n`;\n}\n\n/**\n * Fix the relative links in a section that is moving one directory down.\n *\n * A section that said `[the gate](CONTRIBUTING.md)` at the root must say\n * `../CONTRIBUTING.md` from `docs/`, or the split turns a working pointer into a\n * `dead-link` — the lint's own flagship finding, minted by the tool that fixes it.\n * External links, anchors and paths that already climb out are left alone.\n */\nexport function rewriteLinks(body: string): string {\n return body.replace(\n /(\\[[^\\]]*\\]\\()([^)\\s]+)(\\))/g,\n (whole, open: string, target: string, close: string) => {\n if (/^[a-z][a-z0-9+.-]*:/i.test(target)) return whole;\n if (target.startsWith('#') || target.startsWith('/') || target.startsWith('..')) return whole;\n const cleaned = target.startsWith('./') ? target.slice(2) : target;\n if (cleaned.startsWith(`${SPLIT_DIR}/`))\n return `${open}${cleaned.slice(SPLIT_DIR.length + 1)}${close}`;\n return `${open}../${cleaned}${close}`;\n },\n );\n}\n\n/** `Build & test` → `build-test`, made unique within one plan. */\nfunction uniqueSlug(title: string, used: Set<string>): string {\n const base =\n title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 40) || 'section';\n let slug = base;\n let counter = 2;\n while (used.has(slug)) {\n slug = `${base}-${String(counter)}`;\n counter += 1;\n }\n used.add(slug);\n return slug;\n}\n\n/* ------------------------------------------------------------------------------------\n * The judgment half, handed over\n * ---------------------------------------------------------------------------------- */\n\n/**\n * The guide's refactor prompt, filled in with this file's real sections.\n *\n * This is the seam, printed. smelt will not decide which of somebody's sections are\n * essential — that is a reading of their project, and Law 1 keeps smelt offline and\n * modelless — so it hands over the guide's own five-step prompt with the section list\n * already substituted in, ready to paste into whichever agent the user is running.\n *\n * The five steps are the guide's; the wording is smelt's summary of them, with the\n * article cited so the reader can check it. The list of sections is measured, not\n * invented: every heading and every byte count comes from the file on disk.\n */\nexport function refactorPrompt(plan: SplitPlan): string {\n const inventory = plan.sections\n .map(\n (section) =>\n ` - \"${section.title}\" (${String(section.bytes)} bytes, line ${String(section.line)})`,\n )\n .join('\\n');\n\n return [\n `Here is my ${plan.source} (${String(plan.beforeBytes)} bytes). Its \\`##\\` sections are:`,\n '',\n inventory,\n '',\n `Refactor it, following the five steps from ${GUIDE_URL}:`,\n '',\n ' 1. Find contradictions. Where two instructions conflict, show me both and ask',\n ' which one to keep — do not pick for me.',\n ' 2. Identify the essentials for the root file: a one-sentence project description,',\n ' the package manager if it is not npm, the build and typecheck commands if they',\n ' are non-standard, and anything genuinely relevant to every task.',\n ' 3. Group the rest into categories (TypeScript, testing, API design, git workflow),',\n ' one file per category.',\n ' 4. Create the file structure: a minimal root that links to each file, and the',\n ' files themselves under docs/.',\n ' 5. Flag for deletion anything redundant, too vague to be actionable, or so obvious',\n ' it is not worth a token on every request.',\n '',\n `Why this matters: ${GUIDE.loadsEveryRequest}.`,\n '',\n ].join('\\n');\n}\n\n/** The one sentence that says where smelt stops. Printed by the CLI, and in its help. */\nexport function splitSeamNotice(): string {\n return (\n `smelt did the mechanical half: it partitioned by heading, rewrote the links that ` +\n `moved a directory, and wrote nothing without asking. It did NOT decide which ` +\n `sections are essential — that is a reading of your project, so it needs a model, ` +\n `and smelt has none by law. The prompt above is the judgment half, filled in with ` +\n `your real sections; hand it to your own agent` +\n citing(GUIDE.pointsElsewhere)\n );\n}\n"]}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { citing, GUIDE, GUIDE_URL } from './guide.js';
|
|
2
|
+
/**
|
|
3
|
+
* `smelt agents split` — the **mechanical half** of the guide's refactor, and a plain
|
|
4
|
+
* statement of where the other half is.
|
|
5
|
+
*
|
|
6
|
+
* The guide's advice for a bloated instruction file is to cut it down to a small root
|
|
7
|
+
* that points elsewhere, moving the rest into `docs/`. That advice has two halves, and
|
|
8
|
+
* they are not the same kind of work:
|
|
9
|
+
*
|
|
10
|
+
* - **Mechanical.** Find the sections. Decide what each one's file is called. Move
|
|
11
|
+
* the bytes. Fix every relative link that just moved a directory deeper. Leave a
|
|
12
|
+
* link to each moved file behind. All of this is text manipulation with a right
|
|
13
|
+
* answer, and it is what this module does.
|
|
14
|
+
* - **Judgment.** *Which sections are essential enough to stay in the root?* Which
|
|
15
|
+
* two paragraphs contradict each other? Which rule is too vague to be worth a
|
|
16
|
+
* token? That is a reading of a specific project by someone who knows it, which
|
|
17
|
+
* means a model, which Law 1 forbids smelt from being (ruling R1).
|
|
18
|
+
*
|
|
19
|
+
* So the seam is stated instead of straddled: {@link planSplit} returns a partition
|
|
20
|
+
* anyone can check, and {@link refactorPrompt} returns the guide's own refactor
|
|
21
|
+
* prompt **filled in with this file's actual section headings**, for the user to hand
|
|
22
|
+
* to their own agent. smelt does the part with a right answer and hands over the part
|
|
23
|
+
* that needs a reader — the same shape as the unconfigured rerank stage, and the same
|
|
24
|
+
* reason.
|
|
25
|
+
*
|
|
26
|
+
* Nothing here writes. `cli/agents.ts` owns the confirm-listing discipline
|
|
27
|
+
* (`smelt init`'s, verbatim: every file listed, one final confirm, an existing file
|
|
28
|
+
* never overwritten without a per-file `yes`).
|
|
29
|
+
*/
|
|
30
|
+
/** Where the moved sections go. The guide's own suggested layout. */
|
|
31
|
+
export const SPLIT_DIR = 'docs';
|
|
32
|
+
/**
|
|
33
|
+
* Partition one instruction file into a root and one file per `##` section.
|
|
34
|
+
*
|
|
35
|
+
* The partition is **by heading and nothing else**. That is the honest mechanical
|
|
36
|
+
* rule: a split by heading is reproducible, reversible by hand, and obviously not a
|
|
37
|
+
* judgment about what matters — which is exactly what it must not pretend to be.
|
|
38
|
+
*
|
|
39
|
+
* The preamble (everything above the first `##`) stays in the root, because in a
|
|
40
|
+
* file written to the guide's shape the preamble *is* the essentials: the title and
|
|
41
|
+
* the one-sentence description.
|
|
42
|
+
*/
|
|
43
|
+
export function planSplit(file) {
|
|
44
|
+
const sections = readSections(file.text);
|
|
45
|
+
const before = file.bytes;
|
|
46
|
+
if (sections.length === 0) {
|
|
47
|
+
return {
|
|
48
|
+
source: file.path,
|
|
49
|
+
beforeBytes: before,
|
|
50
|
+
afterBytes: before,
|
|
51
|
+
sections: [],
|
|
52
|
+
files: [],
|
|
53
|
+
refusal: `${file.path} has no \`##\` sections, so there is nothing to partition by. ` +
|
|
54
|
+
`A split invented without headings would be smelt deciding what matters, ` +
|
|
55
|
+
`which is the half of the refactor it deliberately does not do.`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const used = new Set();
|
|
59
|
+
const moved = sections.map((section) => {
|
|
60
|
+
const path = `${SPLIT_DIR}/${uniqueSlug(section.title, used)}.md`;
|
|
61
|
+
return { section, path };
|
|
62
|
+
});
|
|
63
|
+
const rootContent = renderRoot(file.text, sections, moved);
|
|
64
|
+
const files = [
|
|
65
|
+
{ path: file.path, content: rootContent, role: 'root' },
|
|
66
|
+
...moved.map(({ section, path }) => ({
|
|
67
|
+
path,
|
|
68
|
+
content: renderSection(section),
|
|
69
|
+
role: 'section',
|
|
70
|
+
})),
|
|
71
|
+
];
|
|
72
|
+
return {
|
|
73
|
+
source: file.path,
|
|
74
|
+
beforeBytes: before,
|
|
75
|
+
afterBytes: Buffer.byteLength(rootContent, 'utf8'),
|
|
76
|
+
sections,
|
|
77
|
+
files,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/* ------------------------------------------------------------------------------------
|
|
81
|
+
* Reading sections
|
|
82
|
+
* ---------------------------------------------------------------------------------- */
|
|
83
|
+
/**
|
|
84
|
+
* Every `##` section, fences respected.
|
|
85
|
+
*
|
|
86
|
+
* A `## ` inside a fenced block is a Markdown example or a shell comment, not a
|
|
87
|
+
* heading — splitting on one would cut a code block in half, which is the kind of
|
|
88
|
+
* damage that makes a tool untrustworthy the first time it happens.
|
|
89
|
+
*/
|
|
90
|
+
export function readSections(text) {
|
|
91
|
+
const lines = text.split('\n');
|
|
92
|
+
const starts = [];
|
|
93
|
+
let fence;
|
|
94
|
+
lines.forEach((line, index) => {
|
|
95
|
+
const marker = /^\s{0,3}(`{3,}|~{3,})/.exec(line);
|
|
96
|
+
if (marker !== null) {
|
|
97
|
+
if (fence === undefined)
|
|
98
|
+
fence = marker[1].slice(0, 1);
|
|
99
|
+
else if (marker[1].startsWith(fence))
|
|
100
|
+
fence = undefined;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (fence !== undefined)
|
|
104
|
+
return;
|
|
105
|
+
const heading = /^##\s+(.+?)\s*$/.exec(line);
|
|
106
|
+
if (heading !== null)
|
|
107
|
+
starts.push({ index, title: heading[1] });
|
|
108
|
+
});
|
|
109
|
+
return starts.map(({ index, title }, position) => {
|
|
110
|
+
const end = starts[position + 1]?.index ?? lines.length;
|
|
111
|
+
const body = lines
|
|
112
|
+
.slice(index + 1, end)
|
|
113
|
+
.join('\n')
|
|
114
|
+
.replace(/^\n+/, '')
|
|
115
|
+
.replace(/\n+$/, '');
|
|
116
|
+
return {
|
|
117
|
+
title,
|
|
118
|
+
line: index + 1,
|
|
119
|
+
body,
|
|
120
|
+
bytes: Buffer.byteLength(`${lines[index]}\n${body}\n`, 'utf8'),
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/* ------------------------------------------------------------------------------------
|
|
125
|
+
* Rendering
|
|
126
|
+
* ---------------------------------------------------------------------------------- */
|
|
127
|
+
/** The moved section as its own file: the heading promoted to `#`, links rewritten. */
|
|
128
|
+
function renderSection(section) {
|
|
129
|
+
return `# ${section.title}\n\n${rewriteLinks(section.body)}\n`;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The root file: everything above the first `##`, then one link per moved section.
|
|
133
|
+
*
|
|
134
|
+
* The link list is the point of the whole exercise — the guide's ideal root file is
|
|
135
|
+
* small and points elsewhere — so it is written plainly, one line per section, with
|
|
136
|
+
* the section's own title as the link text.
|
|
137
|
+
*/
|
|
138
|
+
function renderRoot(text, sections, moved) {
|
|
139
|
+
const lines = text.split('\n');
|
|
140
|
+
const preamble = lines
|
|
141
|
+
.slice(0, (sections[0]?.line ?? lines.length + 1) - 1)
|
|
142
|
+
.join('\n')
|
|
143
|
+
.replace(/\n+$/, '');
|
|
144
|
+
const links = moved.map(({ section, path }) => `- [${section.title}](${path})`).join('\n');
|
|
145
|
+
return `${preamble}\n\n## More\n\n${links}\n`;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Fix the relative links in a section that is moving one directory down.
|
|
149
|
+
*
|
|
150
|
+
* A section that said `[the gate](CONTRIBUTING.md)` at the root must say
|
|
151
|
+
* `../CONTRIBUTING.md` from `docs/`, or the split turns a working pointer into a
|
|
152
|
+
* `dead-link` — the lint's own flagship finding, minted by the tool that fixes it.
|
|
153
|
+
* External links, anchors and paths that already climb out are left alone.
|
|
154
|
+
*/
|
|
155
|
+
export function rewriteLinks(body) {
|
|
156
|
+
return body.replace(/(\[[^\]]*\]\()([^)\s]+)(\))/g, (whole, open, target, close) => {
|
|
157
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(target))
|
|
158
|
+
return whole;
|
|
159
|
+
if (target.startsWith('#') || target.startsWith('/') || target.startsWith('..'))
|
|
160
|
+
return whole;
|
|
161
|
+
const cleaned = target.startsWith('./') ? target.slice(2) : target;
|
|
162
|
+
if (cleaned.startsWith(`${SPLIT_DIR}/`))
|
|
163
|
+
return `${open}${cleaned.slice(SPLIT_DIR.length + 1)}${close}`;
|
|
164
|
+
return `${open}../${cleaned}${close}`;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/** `Build & test` → `build-test`, made unique within one plan. */
|
|
168
|
+
function uniqueSlug(title, used) {
|
|
169
|
+
const base = title
|
|
170
|
+
.toLowerCase()
|
|
171
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
172
|
+
.replace(/^-+|-+$/g, '')
|
|
173
|
+
.slice(0, 40) || 'section';
|
|
174
|
+
let slug = base;
|
|
175
|
+
let counter = 2;
|
|
176
|
+
while (used.has(slug)) {
|
|
177
|
+
slug = `${base}-${String(counter)}`;
|
|
178
|
+
counter += 1;
|
|
179
|
+
}
|
|
180
|
+
used.add(slug);
|
|
181
|
+
return slug;
|
|
182
|
+
}
|
|
183
|
+
/* ------------------------------------------------------------------------------------
|
|
184
|
+
* The judgment half, handed over
|
|
185
|
+
* ---------------------------------------------------------------------------------- */
|
|
186
|
+
/**
|
|
187
|
+
* The guide's refactor prompt, filled in with this file's real sections.
|
|
188
|
+
*
|
|
189
|
+
* This is the seam, printed. smelt will not decide which of somebody's sections are
|
|
190
|
+
* essential — that is a reading of their project, and Law 1 keeps smelt offline and
|
|
191
|
+
* modelless — so it hands over the guide's own five-step prompt with the section list
|
|
192
|
+
* already substituted in, ready to paste into whichever agent the user is running.
|
|
193
|
+
*
|
|
194
|
+
* The five steps are the guide's; the wording is smelt's summary of them, with the
|
|
195
|
+
* article cited so the reader can check it. The list of sections is measured, not
|
|
196
|
+
* invented: every heading and every byte count comes from the file on disk.
|
|
197
|
+
*/
|
|
198
|
+
export function refactorPrompt(plan) {
|
|
199
|
+
const inventory = plan.sections
|
|
200
|
+
.map((section) => ` - "${section.title}" (${String(section.bytes)} bytes, line ${String(section.line)})`)
|
|
201
|
+
.join('\n');
|
|
202
|
+
return [
|
|
203
|
+
`Here is my ${plan.source} (${String(plan.beforeBytes)} bytes). Its \`##\` sections are:`,
|
|
204
|
+
'',
|
|
205
|
+
inventory,
|
|
206
|
+
'',
|
|
207
|
+
`Refactor it, following the five steps from ${GUIDE_URL}:`,
|
|
208
|
+
'',
|
|
209
|
+
' 1. Find contradictions. Where two instructions conflict, show me both and ask',
|
|
210
|
+
' which one to keep — do not pick for me.',
|
|
211
|
+
' 2. Identify the essentials for the root file: a one-sentence project description,',
|
|
212
|
+
' the package manager if it is not npm, the build and typecheck commands if they',
|
|
213
|
+
' are non-standard, and anything genuinely relevant to every task.',
|
|
214
|
+
' 3. Group the rest into categories (TypeScript, testing, API design, git workflow),',
|
|
215
|
+
' one file per category.',
|
|
216
|
+
' 4. Create the file structure: a minimal root that links to each file, and the',
|
|
217
|
+
' files themselves under docs/.',
|
|
218
|
+
' 5. Flag for deletion anything redundant, too vague to be actionable, or so obvious',
|
|
219
|
+
' it is not worth a token on every request.',
|
|
220
|
+
'',
|
|
221
|
+
`Why this matters: ${GUIDE.loadsEveryRequest}.`,
|
|
222
|
+
'',
|
|
223
|
+
].join('\n');
|
|
224
|
+
}
|
|
225
|
+
/** The one sentence that says where smelt stops. Printed by the CLI, and in its help. */
|
|
226
|
+
export function splitSeamNotice() {
|
|
227
|
+
return (`smelt did the mechanical half: it partitioned by heading, rewrote the links that ` +
|
|
228
|
+
`moved a directory, and wrote nothing without asking. It did NOT decide which ` +
|
|
229
|
+
`sections are essential — that is a reading of your project, so it needs a model, ` +
|
|
230
|
+
`and smelt has none by law. The prompt above is the judgment half, filled in with ` +
|
|
231
|
+
`your real sections; hand it to your own agent` +
|
|
232
|
+
citing(GUIDE.pointsElsewhere));
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=split.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split.js","sourceRoot":"","sources":["../../src/agents/split.ts"],"names":[],"mappings":"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","sourcesContent":["import { citing, GUIDE, GUIDE_URL } from './guide.ts';\nimport type { InstructionFile } from './instructions.ts';\n\n/**\n * `smelt agents split` — the **mechanical half** of the guide's refactor, and a plain\n * statement of where the other half is.\n *\n * The guide's advice for a bloated instruction file is to cut it down to a small root\n * that points elsewhere, moving the rest into `docs/`. That advice has two halves, and\n * they are not the same kind of work:\n *\n * - **Mechanical.** Find the sections. Decide what each one's file is called. Move\n * the bytes. Fix every relative link that just moved a directory deeper. Leave a\n * link to each moved file behind. All of this is text manipulation with a right\n * answer, and it is what this module does.\n * - **Judgment.** *Which sections are essential enough to stay in the root?* Which\n * two paragraphs contradict each other? Which rule is too vague to be worth a\n * token? That is a reading of a specific project by someone who knows it, which\n * means a model, which Law 1 forbids smelt from being (ruling R1).\n *\n * So the seam is stated instead of straddled: {@link planSplit} returns a partition\n * anyone can check, and {@link refactorPrompt} returns the guide's own refactor\n * prompt **filled in with this file's actual section headings**, for the user to hand\n * to their own agent. smelt does the part with a right answer and hands over the part\n * that needs a reader — the same shape as the unconfigured rerank stage, and the same\n * reason.\n *\n * Nothing here writes. `cli/agents.ts` owns the confirm-listing discipline\n * (`smelt init`'s, verbatim: every file listed, one final confirm, an existing file\n * never overwritten without a per-file `yes`).\n */\n\n/** Where the moved sections go. The guide's own suggested layout. */\nexport const SPLIT_DIR = 'docs';\n\n/** One `##` section of an instruction file. */\nexport interface InstructionSection {\n /** The heading text, without its `##`. */\n readonly title: string;\n /** 1-based line of the heading. */\n readonly line: number;\n /** The section's body, heading excluded, trailing blank lines trimmed. */\n readonly body: string;\n /** UTF-8 bytes of heading + body — what moving this section saves the root file. */\n readonly bytes: number;\n}\n\n/** One file the split would write, already rendered. */\nexport interface SplitFile {\n /** Root-relative, `/`-separated. */\n readonly path: string;\n readonly content: string;\n /** What this file is, for the confirm listing. */\n readonly role: 'root' | 'section';\n}\n\n/** The whole mechanical proposal. Pure data: nothing on disk has moved. */\nexport interface SplitPlan {\n /** The file that was read. */\n readonly source: string;\n /** Its size before. */\n readonly beforeBytes: number;\n /** What the root file would become. */\n readonly afterBytes: number;\n /** The sections found, in document order. */\n readonly sections: readonly InstructionSection[];\n /** Root file first, then one file per moved section. */\n readonly files: readonly SplitFile[];\n /**\n * Why the plan is empty, when it is: a file with no `##` sections has nothing to\n * partition **by**, and inventing a partition would be the judgment half.\n */\n readonly refusal?: string;\n}\n\n/**\n * Partition one instruction file into a root and one file per `##` section.\n *\n * The partition is **by heading and nothing else**. That is the honest mechanical\n * rule: a split by heading is reproducible, reversible by hand, and obviously not a\n * judgment about what matters — which is exactly what it must not pretend to be.\n *\n * The preamble (everything above the first `##`) stays in the root, because in a\n * file written to the guide's shape the preamble *is* the essentials: the title and\n * the one-sentence description.\n */\nexport function planSplit(file: InstructionFile): SplitPlan {\n const sections = readSections(file.text);\n const before = file.bytes;\n\n if (sections.length === 0) {\n return {\n source: file.path,\n beforeBytes: before,\n afterBytes: before,\n sections: [],\n files: [],\n refusal:\n `${file.path} has no \\`##\\` sections, so there is nothing to partition by. ` +\n `A split invented without headings would be smelt deciding what matters, ` +\n `which is the half of the refactor it deliberately does not do.`,\n };\n }\n\n const used = new Set<string>();\n const moved = sections.map((section) => {\n const path = `${SPLIT_DIR}/${uniqueSlug(section.title, used)}.md`;\n return { section, path };\n });\n\n const rootContent = renderRoot(file.text, sections, moved);\n const files: SplitFile[] = [\n { path: file.path, content: rootContent, role: 'root' },\n ...moved.map(({ section, path }): SplitFile => ({\n path,\n content: renderSection(section),\n role: 'section',\n })),\n ];\n\n return {\n source: file.path,\n beforeBytes: before,\n afterBytes: Buffer.byteLength(rootContent, 'utf8'),\n sections,\n files,\n };\n}\n\n/* ------------------------------------------------------------------------------------\n * Reading sections\n * ---------------------------------------------------------------------------------- */\n\n/**\n * Every `##` section, fences respected.\n *\n * A `## ` inside a fenced block is a Markdown example or a shell comment, not a\n * heading — splitting on one would cut a code block in half, which is the kind of\n * damage that makes a tool untrustworthy the first time it happens.\n */\nexport function readSections(text: string): readonly InstructionSection[] {\n const lines = text.split('\\n');\n const starts: { readonly index: number; readonly title: string }[] = [];\n let fence: string | undefined;\n\n lines.forEach((line, index) => {\n const marker = /^\\s{0,3}(`{3,}|~{3,})/.exec(line);\n if (marker !== null) {\n if (fence === undefined) fence = marker[1]!.slice(0, 1);\n else if (marker[1]!.startsWith(fence)) fence = undefined;\n return;\n }\n if (fence !== undefined) return;\n const heading = /^##\\s+(.+?)\\s*$/.exec(line);\n if (heading !== null) starts.push({ index, title: heading[1]! });\n });\n\n return starts.map(({ index, title }, position) => {\n const end = starts[position + 1]?.index ?? lines.length;\n const body = lines\n .slice(index + 1, end)\n .join('\\n')\n .replace(/^\\n+/, '')\n .replace(/\\n+$/, '');\n return {\n title,\n line: index + 1,\n body,\n bytes: Buffer.byteLength(`${lines[index]!}\\n${body}\\n`, 'utf8'),\n };\n });\n}\n\n/* ------------------------------------------------------------------------------------\n * Rendering\n * ---------------------------------------------------------------------------------- */\n\n/** The moved section as its own file: the heading promoted to `#`, links rewritten. */\nfunction renderSection(section: InstructionSection): string {\n return `# ${section.title}\\n\\n${rewriteLinks(section.body)}\\n`;\n}\n\n/**\n * The root file: everything above the first `##`, then one link per moved section.\n *\n * The link list is the point of the whole exercise — the guide's ideal root file is\n * small and points elsewhere — so it is written plainly, one line per section, with\n * the section's own title as the link text.\n */\nfunction renderRoot(\n text: string,\n sections: readonly InstructionSection[],\n moved: readonly { readonly section: InstructionSection; readonly path: string }[],\n): string {\n const lines = text.split('\\n');\n const preamble = lines\n .slice(0, (sections[0]?.line ?? lines.length + 1) - 1)\n .join('\\n')\n .replace(/\\n+$/, '');\n const links = moved.map(({ section, path }) => `- [${section.title}](${path})`).join('\\n');\n return `${preamble}\\n\\n## More\\n\\n${links}\\n`;\n}\n\n/**\n * Fix the relative links in a section that is moving one directory down.\n *\n * A section that said `[the gate](CONTRIBUTING.md)` at the root must say\n * `../CONTRIBUTING.md` from `docs/`, or the split turns a working pointer into a\n * `dead-link` — the lint's own flagship finding, minted by the tool that fixes it.\n * External links, anchors and paths that already climb out are left alone.\n */\nexport function rewriteLinks(body: string): string {\n return body.replace(\n /(\\[[^\\]]*\\]\\()([^)\\s]+)(\\))/g,\n (whole, open: string, target: string, close: string) => {\n if (/^[a-z][a-z0-9+.-]*:/i.test(target)) return whole;\n if (target.startsWith('#') || target.startsWith('/') || target.startsWith('..')) return whole;\n const cleaned = target.startsWith('./') ? target.slice(2) : target;\n if (cleaned.startsWith(`${SPLIT_DIR}/`))\n return `${open}${cleaned.slice(SPLIT_DIR.length + 1)}${close}`;\n return `${open}../${cleaned}${close}`;\n },\n );\n}\n\n/** `Build & test` → `build-test`, made unique within one plan. */\nfunction uniqueSlug(title: string, used: Set<string>): string {\n const base =\n title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 40) || 'section';\n let slug = base;\n let counter = 2;\n while (used.has(slug)) {\n slug = `${base}-${String(counter)}`;\n counter += 1;\n }\n used.add(slug);\n return slug;\n}\n\n/* ------------------------------------------------------------------------------------\n * The judgment half, handed over\n * ---------------------------------------------------------------------------------- */\n\n/**\n * The guide's refactor prompt, filled in with this file's real sections.\n *\n * This is the seam, printed. smelt will not decide which of somebody's sections are\n * essential — that is a reading of their project, and Law 1 keeps smelt offline and\n * modelless — so it hands over the guide's own five-step prompt with the section list\n * already substituted in, ready to paste into whichever agent the user is running.\n *\n * The five steps are the guide's; the wording is smelt's summary of them, with the\n * article cited so the reader can check it. The list of sections is measured, not\n * invented: every heading and every byte count comes from the file on disk.\n */\nexport function refactorPrompt(plan: SplitPlan): string {\n const inventory = plan.sections\n .map(\n (section) =>\n ` - \"${section.title}\" (${String(section.bytes)} bytes, line ${String(section.line)})`,\n )\n .join('\\n');\n\n return [\n `Here is my ${plan.source} (${String(plan.beforeBytes)} bytes). Its \\`##\\` sections are:`,\n '',\n inventory,\n '',\n `Refactor it, following the five steps from ${GUIDE_URL}:`,\n '',\n ' 1. Find contradictions. Where two instructions conflict, show me both and ask',\n ' which one to keep — do not pick for me.',\n ' 2. Identify the essentials for the root file: a one-sentence project description,',\n ' the package manager if it is not npm, the build and typecheck commands if they',\n ' are non-standard, and anything genuinely relevant to every task.',\n ' 3. Group the rest into categories (TypeScript, testing, API design, git workflow),',\n ' one file per category.',\n ' 4. Create the file structure: a minimal root that links to each file, and the',\n ' files themselves under docs/.',\n ' 5. Flag for deletion anything redundant, too vague to be actionable, or so obvious',\n ' it is not worth a token on every request.',\n '',\n `Why this matters: ${GUIDE.loadsEveryRequest}.`,\n '',\n ].join('\\n');\n}\n\n/** The one sentence that says where smelt stops. Printed by the CLI, and in its help. */\nexport function splitSeamNotice(): string {\n return (\n `smelt did the mechanical half: it partitioned by heading, rewrote the links that ` +\n `moved a directory, and wrote nothing without asking. It did NOT decide which ` +\n `sections are essential — that is a reading of your project, so it needs a model, ` +\n `and smelt has none by law. The prompt above is the judgment half, filled in with ` +\n `your real sections; hand it to your own agent` +\n citing(GUIDE.pointsElsewhere)\n );\n}\n"]}
|
package/dist/apply.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../src/apply.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAGV,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,aAAa,EACb,OAAO,EACP,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,0DAA0D;AAC1D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,UAAU,KAAK,MAAM,CAAC;AAEzD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,EAAE,aACoE,CAAC;AAEjG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,2BAA2B,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAKzF,CAAC;AAEJ;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,GAAE,aAA6B,GAClC,aAAa,CAIf;AAQD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,GAAE,gBAA4B,EACtC,aAAa,CAAC,EAAE,aAAa,GAC5B,aAAa,CAef;AAED,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC,mFAAmF;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,YAAY,EACnB,OAAO,GAAE,YAAiB,GACzB,WAAW,CA6Eb;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,CAiB5E"}
|
|
1
|
+
{"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../src/apply.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAGV,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,aAAa,EACb,OAAO,EACP,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,0DAA0D;AAC1D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,UAAU,KAAK,MAAM,CAAC;AAEzD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,EAAE,aACoE,CAAC;AAEjG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,2BAA2B,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAKzF,CAAC;AAEJ;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,GAAE,aAA6B,GAClC,aAAa,CAIf;AAQD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,GAAE,gBAA4B,EACtC,aAAa,CAAC,EAAE,aAAa,GAC5B,aAAa,CAef;AAED,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC,mFAAmF;IACnF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,YAAY,EACnB,OAAO,GAAE,YAAiB,GACzB,WAAW,CA6Eb;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,CAiB5E","sourcesContent":["import { OverlappingElisionError, RangeOutOfBoundsError, UnknownHashError } from './errors.ts';\nimport { HASH_LENGTH } from './hash.ts';\nimport { LANGUAGE_PROFILES } from './lang/registry.ts';\nimport type {\n AppliedElision,\n ByteRange,\n DetectedLanguage,\n ElisionPlan,\n ElisionStore,\n MarkerPricing,\n Measure,\n SmeltResult,\n} from './types.ts';\n\n/** Everything the marker text is allowed to depend on. */\nexport interface MarkerInfo {\n readonly hash: string;\n readonly bytes: number;\n readonly rule: string;\n readonly explanation: string;\n}\n\nexport type MarkerBuilder = (info: MarkerInfo) => string;\n\n/**\n * The version of the marker format itself, carried **in band** in every marker.\n *\n * The marker is the one part of smelt a *model* sees, and it goes into prompts.\n * Changing its shape changes model behaviour downstream and shows up as worse output\n * with no error anywhere — this project's signature failure mode, shipped as a patch\n * release. So the wire surface is frozen from 0.1 and treated as 1.0\n * (`CONTRIBUTING.md` § \"Two promises, not one\"), and a future format is *additive and\n * identifiable*: `smelt/v2` markers can coexist with `smelt/v1` ones, and a consumer\n * parsing markers can tell which it is holding. A format that changed silently would\n * be a substitution; this makes it a declaration.\n *\n * `test/guards/marker-format.test.ts` pins the rendered marker per version and fails if\n * the format moves without the version moving.\n */\nexport const MARKER_FORMAT_VERSION = 'v1';\n\n/**\n * The default marker.\n *\n * Its shape is the user-facing form of Laws 2 and 3, in one line the model reads:\n * *which format this is* (the version), *what was removed* (the explanation), *how\n * much* (the byte count), and *how to get it back* (the hash). Anything that cannot\n * fill in all of those is not allowed to be an elision.\n *\n * `<<…>>` rather than a Unicode bracket because it survives every tokenizer, terminal,\n * and diff tool without becoming three tokens of nothing.\n */\nexport const defaultMarker: MarkerBuilder = ({ explanation, bytes, hash }) =>\n `<<smelt/${MARKER_FORMAT_VERSION}: ${explanation} (${String(bytes)}B) — retrieve(\"${hash}\")>>`;\n\n/**\n * Line-comment leaders per language: the marker always lands as a comment in the\n * survivor's own syntax, because a bare marker line breaks the syntax of what remains\n * around it — in **every** grammar this was tested against, not just the indented ones.\n *\n * The failure classes, each verified by reparsing a bare-marker survivor:\n *\n * - **python** — significant indentation means a parse error does not stay local.\n * The ERROR node swallows the *neighbouring definitions too* — the survivor stops\n * being Python at all, not just at the marker line.\n * - **ruby** and **bash** — the marker *begins with* `<<`, which both languages read\n * as a heredoc operator. A bare marker line opens a heredoc whose terminator never\n * arrives, and everything after it — every kept declaration — is swallowed into a\n * string literal.\n * - **php** — `<<` is an operator here too: the kept function after a bare marker is\n * re-typed into an anonymous-function operand inside the marker's binary\n * expression. The kept declaration is no longer a declaration in the survivor.\n * - **kotlin**, **swift**, and the rest of the brace-delimited set (typescript, tsx,\n * javascript, rust, go, java, c, cpp, c_sharp) — the folk claim that braces keep a\n * parse error local is **empirically false**: reparsing each language's fixture\n * survivor with its own bundled grammar shows ERROR nodes spanning the kept\n * declarations (a C function's signature absorbed into an ERROR, fifteen cascading\n * ERRORs in swift, and so on). The survivor-reparse guard in\n * `test/guards/structural.test.ts` now asserts the opposite property for every\n * structural language: the survivor reparses with no new issues.\n *\n * Only `'unknown'` keeps the bare marker — lexical text has no syntax to break.\n *\n * This does **not** move the frozen wire surface. The `<<smelt/v1: … >>` core is\n * rendered by {@link defaultMarker}, byte-identical and still versioned in band; the\n * leader is part of the substituted marker text, so `outputRange` covers it and\n * reconstruction stays byte-exact. A comment leader in the survivor's own syntax is\n * the one wrapping that cannot change what a model reads out of the marker.\n *\n * A derived view: each leader is the `markerLeader` fact on the language's\n * {@link LanguageProfile} (`src/lang/`), collected here so marker construction keeps\n * one lookup table.\n */\nexport const MARKER_LINE_COMMENT_LEADERS: Readonly<Partial<Record<DetectedLanguage, string>>> =\n Object.fromEntries(\n Object.values(LANGUAGE_PROFILES)\n .filter((profile) => profile.markerLeader !== undefined)\n .map((profile) => [profile.id, profile.markerLeader]),\n );\n\n/**\n * The marker builder for a language: {@link defaultMarker}, wrapped in the language's\n * line-comment leader when {@link MARKER_LINE_COMMENT_LEADERS} names one — so a Python\n * survivor still parses as Python. Everything else gets `base` unchanged.\n */\nexport function markerForLanguage(\n language: DetectedLanguage,\n base: MarkerBuilder = defaultMarker,\n): MarkerBuilder {\n const leader = MARKER_LINE_COMMENT_LEADERS[language];\n if (leader === undefined) return base;\n return (info) => `${leader}${base(info)}`;\n}\n\n/**\n * A stand-in hash of the real length, so a marker can be priced before the cut that\n * would earn it exists. Marker cost depends on the hash's *length*, never its value.\n */\nconst PLACEHOLDER_HASH = '0'.repeat(HASH_LENGTH);\n\n/**\n * The one adapter behind the {@link MarkerPricing} seam.\n *\n * Marker cost is this module's fact: `applyPlan` renders the marker, so only this\n * module can price it without guessing. The pricing is built from the **exact builder\n * `applyPlan` will use** — the same resolution, in the same order: a caller-supplied\n * builder (`SmelterConfig.marker` / `ApplyOptions.marker`) wins wholesale, otherwise\n * the language's leader-wrapped default via {@link markerForLanguage}.\n *\n * The custom-builder leg is load-bearing, not a convenience: a caller who installs a\n * longer `MarkerBuilder` changes what every elision costs, and a planner still pricing\n * the *default* marker would keep planning elisions the real marker makes\n * unprofitable — cuts that grow the output, silently. Pricing with the builder's own\n * rendering closes that hole: `costBytes` measures the marker *that builder* would\n * emit, byte for byte.\n *\n * `createSmelter` (and through it, the CLI) calls this centrally, once per smelt call;\n * a caller driving `planLexical`/`planStructural` directly builds its own and puts it\n * on the {@link PlanInput}.\n */\nexport function markerPricing(\n language: DetectedLanguage = 'unknown',\n markerBuilder?: MarkerBuilder,\n): MarkerPricing {\n // The same resolution applyPlan performs: a supplied builder wins wholesale.\n const build = markerBuilder ?? markerForLanguage(language);\n return {\n costBytes: (reason, elidedBytes) =>\n Buffer.byteLength(\n build({\n hash: PLACEHOLDER_HASH,\n bytes: elidedBytes,\n rule: reason.rule,\n explanation: reason.explanation,\n }),\n 'utf8',\n ),\n };\n}\n\nexport interface ApplyOptions {\n /**\n * Overrides the marker builder. The default follows the *plan's* language —\n * {@link markerForLanguage} — so the documented composition\n * `planStructural → applyPlan` lands a `# `-led marker in python without the caller\n * wiring it, the same as `createSmelter` does. A bare {@link defaultMarker} in a\n * python survivor is exactly the parse-breaking failure the leader exists to prevent.\n */\n readonly marker?: MarkerBuilder;\n /** A consumer-supplied counter. See {@link Measure}; the budget stays in bytes. */\n readonly measure?: Measure;\n}\n\n/**\n * Turn a plan into text.\n *\n * This is the only function in smelt that removes anything, and it contains no\n * judgement at all: it validates the plan, stores every removed run, substitutes\n * markers, and records where each marker landed. All the deciding happens in a\n * {@link Planner}, which is why a plan can be reviewed before a byte moves.\n *\n * @throws {RangeOutOfBoundsError} if a range falls outside the input's UTF-8 bytes.\n * @throws {OverlappingElisionError} if two ranges overlap — applying both would\n * corrupt the output, and picking a winner would be a silent guess.\n */\nexport function applyPlan(\n text: string,\n plan: ElisionPlan,\n store: ElisionStore,\n options: ApplyOptions = {},\n): SmeltResult {\n const buildMarker = options.marker ?? markerForLanguage(plan.language);\n const input = Buffer.from(text, 'utf8');\n\n const ordered = plan.elisions.toSorted((a, b) => a.range.start - b.range.start);\n for (const { range } of ordered) assertInBounds(range, input.length);\n for (let i = 1; i < ordered.length; i += 1) {\n const previous = ordered[i - 1]!;\n const current = ordered[i]!;\n if (current.range.start < previous.range.end) {\n throw new OverlappingElisionError(\n `smelt: plan from \"${plan.planner}\" elides overlapping ranges ` +\n `[${previous.range.start},${previous.range.end}) and ` +\n `[${current.range.start},${current.range.end}). A plan must be a partition.`,\n );\n }\n }\n\n const pieces: Buffer[] = [];\n const applied: AppliedElision[] = [];\n let cursor = 0;\n let outputBytes = 0;\n\n for (const { range, reason } of ordered) {\n const kept = input.subarray(cursor, range.start);\n pieces.push(kept);\n outputBytes += kept.length;\n\n const removed = input.subarray(range.start, range.end);\n const removedText = removed.toString('utf8');\n const hash = store.put(removedText);\n const marker = buildMarker({\n hash,\n bytes: removed.length,\n rule: reason.rule,\n explanation: reason.explanation,\n });\n const markerBuffer = Buffer.from(marker, 'utf8');\n pieces.push(markerBuffer);\n\n applied.push({\n hash,\n range,\n outputRange: { start: outputBytes, end: outputBytes + markerBuffer.length },\n bytes: removed.length,\n reason,\n marker,\n });\n outputBytes += markerBuffer.length;\n cursor = range.end;\n }\n\n const tail = input.subarray(cursor);\n pieces.push(tail);\n outputBytes += tail.length;\n\n const output = Buffer.concat(pieces).toString('utf8');\n const measure = options.measure;\n\n return {\n text: output,\n inputBytes: input.length,\n outputBytes,\n planner: plan.planner,\n language: plan.language,\n elisions: applied,\n ...(measure === undefined\n ? {}\n : {\n measured: {\n measure: measure.id,\n unit: measure.unit,\n input: measure.count(text),\n output: measure.count(output),\n },\n }),\n };\n}\n\n/**\n * Put it all back. `reconstruct(smelt(x), store) === x`, byte for byte — this is Law 3\n * expressed as an executable equation, and `test/guards/reversibility.test.ts` asserts\n * it on every input the suite knows about.\n *\n * Reads through `peek`, **not** `retrieve`: `retrieveCalls` and the expansion rate\n * exist to count *the model asking for hidden material back* — the honest signal this\n * whole project sells. A caller reassembling the original (to diff it, to verify a\n * round trip, to write it to disk) is not that, and counting it would inflate the one\n * number that must never flatter. The guard in\n * `test/guards/expansion-counter.test.ts` pins this: reconstruction leaves every\n * counter exactly where it was.\n *\n * @throws {UnknownHashError} if the store no longer holds an elision's bytes.\n */\nexport function reconstruct(result: SmeltResult, store: ElisionStore): string {\n const output = Buffer.from(result.text, 'utf8');\n const ordered = result.elisions.toSorted((a, b) => a.outputRange.start - b.outputRange.start);\n const pieces: Buffer[] = [];\n let cursor = 0;\n\n for (const elision of ordered) {\n assertInBounds(elision.outputRange, output.length);\n const content = store.peek(elision.hash);\n if (content === undefined) throw new UnknownHashError(elision.hash);\n pieces.push(output.subarray(cursor, elision.outputRange.start));\n pieces.push(Buffer.from(content, 'utf8'));\n cursor = elision.outputRange.end;\n }\n pieces.push(output.subarray(cursor));\n\n return Buffer.concat(pieces).toString('utf8');\n}\n\nfunction assertInBounds(range: ByteRange, length: number): void {\n if (\n !Number.isInteger(range.start) ||\n !Number.isInteger(range.end) ||\n range.start < 0 ||\n range.end > length ||\n range.start >= range.end\n ) {\n throw new RangeOutOfBoundsError(\n `smelt: range [${range.start},${range.end}) is not a non-empty range inside ` +\n `${String(length)} bytes.`,\n );\n }\n}\n"]}
|