@smeltjs/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +55 -0
  3. package/THIRD-PARTY.md +140 -0
  4. package/dist/apply.d.ts +145 -0
  5. package/dist/apply.d.ts.map +1 -0
  6. package/dist/apply.js +244 -0
  7. package/dist/apply.js.map +1 -0
  8. package/dist/cache/prefix.d.ts +164 -0
  9. package/dist/cache/prefix.d.ts.map +1 -0
  10. package/dist/cache/prefix.js +361 -0
  11. package/dist/cache/prefix.js.map +1 -0
  12. package/dist/cli/args.d.ts +71 -0
  13. package/dist/cli/args.d.ts.map +1 -0
  14. package/dist/cli/args.js +308 -0
  15. package/dist/cli/args.js.map +1 -0
  16. package/dist/cli/bin.d.ts +3 -0
  17. package/dist/cli/bin.d.ts.map +1 -0
  18. package/dist/cli/bin.js +161 -0
  19. package/dist/cli/bin.js.map +1 -0
  20. package/dist/cli/config.d.ts +73 -0
  21. package/dist/cli/config.d.ts.map +1 -0
  22. package/dist/cli/config.js +144 -0
  23. package/dist/cli/config.js.map +1 -0
  24. package/dist/cli/init.d.ts +56 -0
  25. package/dist/cli/init.d.ts.map +1 -0
  26. package/dist/cli/init.js +449 -0
  27. package/dist/cli/init.js.map +1 -0
  28. package/dist/cli/report.d.ts +44 -0
  29. package/dist/cli/report.d.ts.map +1 -0
  30. package/dist/cli/report.js +113 -0
  31. package/dist/cli/report.js.map +1 -0
  32. package/dist/cli/resolve.d.ts +103 -0
  33. package/dist/cli/resolve.d.ts.map +1 -0
  34. package/dist/cli/resolve.js +106 -0
  35. package/dist/cli/resolve.js.map +1 -0
  36. package/dist/cli/run.d.ts +99 -0
  37. package/dist/cli/run.d.ts.map +1 -0
  38. package/dist/cli/run.js +351 -0
  39. package/dist/cli/run.js.map +1 -0
  40. package/dist/detect.d.ts +16 -0
  41. package/dist/detect.d.ts.map +1 -0
  42. package/dist/detect.js +18 -0
  43. package/dist/detect.js.map +1 -0
  44. package/dist/errors.d.ts +88 -0
  45. package/dist/errors.d.ts.map +1 -0
  46. package/dist/errors.js +107 -0
  47. package/dist/errors.js.map +1 -0
  48. package/dist/hash.d.ts +13 -0
  49. package/dist/hash.d.ts.map +1 -0
  50. package/dist/hash.js +16 -0
  51. package/dist/hash.js.map +1 -0
  52. package/dist/index.d.ts +121 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +93 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/lang/bash.d.ts +3 -0
  57. package/dist/lang/bash.d.ts.map +1 -0
  58. package/dist/lang/bash.js +28 -0
  59. package/dist/lang/bash.js.map +1 -0
  60. package/dist/lang/c.d.ts +3 -0
  61. package/dist/lang/c.d.ts.map +1 -0
  62. package/dist/lang/c.js +39 -0
  63. package/dist/lang/c.js.map +1 -0
  64. package/dist/lang/c_sharp.d.ts +3 -0
  65. package/dist/lang/c_sharp.d.ts.map +1 -0
  66. package/dist/lang/c_sharp.js +37 -0
  67. package/dist/lang/c_sharp.js.map +1 -0
  68. package/dist/lang/cpp.d.ts +3 -0
  69. package/dist/lang/cpp.d.ts.map +1 -0
  70. package/dist/lang/cpp.js +46 -0
  71. package/dist/lang/cpp.js.map +1 -0
  72. package/dist/lang/go.d.ts +3 -0
  73. package/dist/lang/go.d.ts.map +1 -0
  74. package/dist/lang/go.js +31 -0
  75. package/dist/lang/go.js.map +1 -0
  76. package/dist/lang/java.d.ts +3 -0
  77. package/dist/lang/java.d.ts.map +1 -0
  78. package/dist/lang/java.js +34 -0
  79. package/dist/lang/java.js.map +1 -0
  80. package/dist/lang/javascript.d.ts +3 -0
  81. package/dist/lang/javascript.d.ts.map +1 -0
  82. package/dist/lang/javascript.js +28 -0
  83. package/dist/lang/javascript.js.map +1 -0
  84. package/dist/lang/kotlin.d.ts +3 -0
  85. package/dist/lang/kotlin.d.ts.map +1 -0
  86. package/dist/lang/kotlin.js +37 -0
  87. package/dist/lang/kotlin.js.map +1 -0
  88. package/dist/lang/php.d.ts +3 -0
  89. package/dist/lang/php.d.ts.map +1 -0
  90. package/dist/lang/php.js +37 -0
  91. package/dist/lang/php.js.map +1 -0
  92. package/dist/lang/profile.d.ts +130 -0
  93. package/dist/lang/profile.d.ts.map +1 -0
  94. package/dist/lang/profile.js +2 -0
  95. package/dist/lang/profile.js.map +1 -0
  96. package/dist/lang/python.d.ts +3 -0
  97. package/dist/lang/python.d.ts.map +1 -0
  98. package/dist/lang/python.js +33 -0
  99. package/dist/lang/python.js.map +1 -0
  100. package/dist/lang/registry.d.ts +31 -0
  101. package/dist/lang/registry.d.ts.map +1 -0
  102. package/dist/lang/registry.js +89 -0
  103. package/dist/lang/registry.js.map +1 -0
  104. package/dist/lang/ruby.d.ts +3 -0
  105. package/dist/lang/ruby.d.ts.map +1 -0
  106. package/dist/lang/ruby.js +47 -0
  107. package/dist/lang/ruby.js.map +1 -0
  108. package/dist/lang/rust.d.ts +3 -0
  109. package/dist/lang/rust.d.ts.map +1 -0
  110. package/dist/lang/rust.js +45 -0
  111. package/dist/lang/rust.js.map +1 -0
  112. package/dist/lang/swift.d.ts +3 -0
  113. package/dist/lang/swift.d.ts.map +1 -0
  114. package/dist/lang/swift.js +34 -0
  115. package/dist/lang/swift.js.map +1 -0
  116. package/dist/lang/tsx.d.ts +4 -0
  117. package/dist/lang/tsx.d.ts.map +1 -0
  118. package/dist/lang/tsx.js +11 -0
  119. package/dist/lang/tsx.js.map +1 -0
  120. package/dist/lang/typescript.d.ts +18 -0
  121. package/dist/lang/typescript.d.ts.map +1 -0
  122. package/dist/lang/typescript.js +61 -0
  123. package/dist/lang/typescript.js.map +1 -0
  124. package/dist/net/policy.d.ts +49 -0
  125. package/dist/net/policy.d.ts.map +1 -0
  126. package/dist/net/policy.js +120 -0
  127. package/dist/net/policy.js.map +1 -0
  128. package/dist/plan/grammar.d.ts +38 -0
  129. package/dist/plan/grammar.d.ts.map +1 -0
  130. package/dist/plan/grammar.js +85 -0
  131. package/dist/plan/grammar.js.map +1 -0
  132. package/dist/plan/lexical.d.ts +51 -0
  133. package/dist/plan/lexical.d.ts.map +1 -0
  134. package/dist/plan/lexical.js +167 -0
  135. package/dist/plan/lexical.js.map +1 -0
  136. package/dist/plan/planners.d.ts +35 -0
  137. package/dist/plan/planners.d.ts.map +1 -0
  138. package/dist/plan/planners.js +26 -0
  139. package/dist/plan/planners.js.map +1 -0
  140. package/dist/plan/structural.d.ts +68 -0
  141. package/dist/plan/structural.d.ts.map +1 -0
  142. package/dist/plan/structural.js +458 -0
  143. package/dist/plan/structural.js.map +1 -0
  144. package/dist/repomap/cache.d.ts +53 -0
  145. package/dist/repomap/cache.d.ts.map +1 -0
  146. package/dist/repomap/cache.js +139 -0
  147. package/dist/repomap/cache.js.map +1 -0
  148. package/dist/repomap/map.d.ts +161 -0
  149. package/dist/repomap/map.d.ts.map +1 -0
  150. package/dist/repomap/map.js +302 -0
  151. package/dist/repomap/map.js.map +1 -0
  152. package/dist/repomap/rank.d.ts +52 -0
  153. package/dist/repomap/rank.d.ts.map +1 -0
  154. package/dist/repomap/rank.js +155 -0
  155. package/dist/repomap/rank.js.map +1 -0
  156. package/dist/repomap/tags.d.ts +47 -0
  157. package/dist/repomap/tags.d.ts.map +1 -0
  158. package/dist/repomap/tags.js +101 -0
  159. package/dist/repomap/tags.js.map +1 -0
  160. package/dist/retrieve.d.ts +14 -0
  161. package/dist/retrieve.d.ts.map +1 -0
  162. package/dist/retrieve.js +44 -0
  163. package/dist/retrieve.js.map +1 -0
  164. package/dist/stages.d.ts +22 -0
  165. package/dist/stages.d.ts.map +1 -0
  166. package/dist/stages.js +33 -0
  167. package/dist/stages.js.map +1 -0
  168. package/dist/stats.d.ts +40 -0
  169. package/dist/stats.d.ts.map +1 -0
  170. package/dist/stats.js +37 -0
  171. package/dist/stats.js.map +1 -0
  172. package/dist/store-dir.d.ts +98 -0
  173. package/dist/store-dir.d.ts.map +1 -0
  174. package/dist/store-dir.js +379 -0
  175. package/dist/store-dir.js.map +1 -0
  176. package/dist/store.d.ts +36 -0
  177. package/dist/store.d.ts.map +1 -0
  178. package/dist/store.js +56 -0
  179. package/dist/store.js.map +1 -0
  180. package/dist/types.d.ts +298 -0
  181. package/dist/types.d.ts.map +1 -0
  182. package/dist/types.js +2 -0
  183. package/dist/types.js.map +1 -0
  184. package/grammars/tree-sitter-bash.wasm +0 -0
  185. package/grammars/tree-sitter-c.wasm +0 -0
  186. package/grammars/tree-sitter-c_sharp.wasm +0 -0
  187. package/grammars/tree-sitter-cpp.wasm +0 -0
  188. package/grammars/tree-sitter-go.wasm +0 -0
  189. package/grammars/tree-sitter-java.wasm +0 -0
  190. package/grammars/tree-sitter-javascript.wasm +0 -0
  191. package/grammars/tree-sitter-kotlin.wasm +0 -0
  192. package/grammars/tree-sitter-php.wasm +0 -0
  193. package/grammars/tree-sitter-python.wasm +0 -0
  194. package/grammars/tree-sitter-ruby.wasm +0 -0
  195. package/grammars/tree-sitter-rust.wasm +0 -0
  196. package/grammars/tree-sitter-swift.wasm +0 -0
  197. package/grammars/tree-sitter-tsx.wasm +0 -0
  198. package/grammars/tree-sitter-typescript.wasm +0 -0
  199. package/package.json +68 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Human words for the tree-sitter node types the structural planner expects at the
3
+ * top level of a TypeScript or TSX file. What the map does not name, the planner
4
+ * still labels honestly — see {@link LanguageStructure.kindLabels}.
5
+ */
6
+ export const TS_KIND_LABELS = {
7
+ function_declaration: 'function',
8
+ generator_function_declaration: 'function',
9
+ function_signature: 'function',
10
+ class_declaration: 'class',
11
+ abstract_class_declaration: 'class',
12
+ interface_declaration: 'interface',
13
+ type_alias_declaration: 'type alias',
14
+ enum_declaration: 'enum',
15
+ lexical_declaration: 'variable',
16
+ variable_declaration: 'variable',
17
+ import_statement: 'import statement',
18
+ internal_module: 'namespace',
19
+ module: 'namespace',
20
+ ambient_declaration: 'ambient declaration',
21
+ expression_statement: 'statement',
22
+ hash_bang_line: 'shebang',
23
+ comment: 'comment',
24
+ };
25
+ /** One grammar family, two ids: the tsx profile shares this structure verbatim. */
26
+ export const TS_STRUCTURE = {
27
+ commentTypes: new Set(['comment']),
28
+ attributeTypes: new Set(),
29
+ // `#!/usr/bin/env -S npx tsx` parses as a hash_bang_line here too — same node, same
30
+ // law as javascript's: collapsing it changes which interpreter runs the file.
31
+ pinnedTypes: new Set(['hash_bang_line']),
32
+ wrapperTypes: { export_statement: 'export' },
33
+ kindLabels: TS_KIND_LABELS,
34
+ };
35
+ /**
36
+ * Repo-map definition kinds for the typescript grammar family — shared by the
37
+ * typescript, tsx and javascript profiles, exactly as the extraction treats them.
38
+ */
39
+ export const TS_DEF_KINDS = {
40
+ function_declaration: 'function',
41
+ generator_function_declaration: 'function',
42
+ function_signature: 'function',
43
+ class_declaration: 'class',
44
+ abstract_class_declaration: 'class',
45
+ interface_declaration: 'interface',
46
+ type_alias_declaration: 'type alias',
47
+ enum_declaration: 'enum',
48
+ method_definition: 'method',
49
+ variable_declarator: 'variable',
50
+ };
51
+ /** Repo-map reference node types for the typescript grammar family. */
52
+ export const TS_REF_TYPES = ['identifier', 'type_identifier'];
53
+ export const typescript = {
54
+ id: 'typescript',
55
+ extensions: ['ts', 'mts', 'cts'],
56
+ wasm: 'tree-sitter-typescript.wasm',
57
+ markerLeader: '// ',
58
+ structure: TS_STRUCTURE,
59
+ repomap: { defKinds: TS_DEF_KINDS, refTypes: TS_REF_TYPES },
60
+ };
61
+ //# sourceMappingURL=typescript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typescript.js","sourceRoot":"","sources":["../../src/lang/typescript.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAqC;IAC9D,oBAAoB,EAAE,UAAU;IAChC,8BAA8B,EAAE,UAAU;IAC1C,kBAAkB,EAAE,UAAU;IAC9B,iBAAiB,EAAE,OAAO;IAC1B,0BAA0B,EAAE,OAAO;IACnC,qBAAqB,EAAE,WAAW;IAClC,sBAAsB,EAAE,YAAY;IACpC,gBAAgB,EAAE,MAAM;IACxB,mBAAmB,EAAE,UAAU;IAC/B,oBAAoB,EAAE,UAAU;IAChC,gBAAgB,EAAE,kBAAkB;IACpC,eAAe,EAAE,WAAW;IAC5B,MAAM,EAAE,WAAW;IACnB,mBAAmB,EAAE,qBAAqB;IAC1C,oBAAoB,EAAE,WAAW;IACjC,cAAc,EAAE,SAAS;IACzB,OAAO,EAAE,SAAS;CACnB,CAAC;AAEF,mFAAmF;AACnF,MAAM,CAAC,MAAM,YAAY,GAAsB;IAC7C,YAAY,EAAE,IAAI,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;IAClC,cAAc,EAAE,IAAI,GAAG,EAAE;IACzB,oFAAoF;IACpF,8EAA8E;IAC9E,WAAW,EAAE,IAAI,GAAG,CAAC,CAAC,gBAAgB,CAAC,CAAC;IACxC,YAAY,EAAE,EAAE,gBAAgB,EAAE,QAAQ,EAAE;IAC5C,UAAU,EAAE,cAAc;CAC3B,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAqC;IAC5D,oBAAoB,EAAE,UAAU;IAChC,8BAA8B,EAAE,UAAU;IAC1C,kBAAkB,EAAE,UAAU;IAC9B,iBAAiB,EAAE,OAAO;IAC1B,0BAA0B,EAAE,OAAO;IACnC,qBAAqB,EAAE,WAAW;IAClC,sBAAsB,EAAE,YAAY;IACpC,gBAAgB,EAAE,MAAM;IACxB,iBAAiB,EAAE,QAAQ;IAC3B,mBAAmB,EAAE,UAAU;CAChC,CAAC;AAEF,uEAAuE;AACvE,MAAM,CAAC,MAAM,YAAY,GAAsB,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC;AAEjF,MAAM,CAAC,MAAM,UAAU,GAAoB;IACzC,EAAE,EAAE,YAAY;IAChB,UAAU,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC;IAChC,IAAI,EAAE,6BAA6B;IACnC,YAAY,EAAE,KAAK;IACnB,SAAS,EAAE,YAAY;IACvB,OAAO,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE;CAC5D,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Law 1 — zero network in v1 — written down once, in one place, so that the guard
3
+ * test and the runtime check cannot drift apart. `test/guards/no-network.test.ts`
4
+ * imports these lists and fails if any module reachable from the public entrypoint
5
+ * imports one of them.
6
+ *
7
+ * This is a *partition of a discovered set*, not an allowlist over an assumed one:
8
+ * the guard walks the real import graph and classifies every edge it finds. A new
9
+ * transport nobody thought of still gets classified — as an unknown bare import,
10
+ * which the guard reports rather than ignores.
11
+ */
12
+ export declare const FORBIDDEN_NODE_MODULES: readonly string[];
13
+ /** Third-party transports. A dependency that pulls one in is a Law 1 violation too. */
14
+ export declare const FORBIDDEN_PACKAGES: readonly string[];
15
+ /**
16
+ * Globals that reach the network without an import. Grepped for by the guard — both
17
+ * bare (`fetch(…)`) and qualified through a global object (`globalThis.fetch(…)`),
18
+ * which is why `globalThis` and `global` are themselves on the list: an access
19
+ * routed through the global object is the escape hatch around a bare-name grep.
20
+ */
21
+ export declare const FORBIDDEN_GLOBALS: readonly string[];
22
+ /**
23
+ * The other half of the partition.
24
+ *
25
+ * A forbidden-list alone is an allowlist over an assumed set: it can only catch
26
+ * transports somebody already thought of. So the guard classifies *every* import edge
27
+ * it discovers, and an edge that matches neither the forbidden lists nor these
28
+ * permitted ones **fails the build**. Adding a dependency therefore requires adding it
29
+ * here, in a diff, next to the reason — which is the point.
30
+ */
31
+ export declare const ALLOWED_NODE_BUILTINS: readonly string[];
32
+ /** Third-party packages any smelt module may import. Keep this list boring and short. */
33
+ export declare const ALLOWED_PACKAGES: readonly string[];
34
+ /**
35
+ * The only URL schemes any smelt code path may resolve.
36
+ *
37
+ * This is not paranoia about our own code. `web-tree-sitter`'s `Language.load()`
38
+ * accepts a `string | URL`, and an `https:` URL there would fetch a grammar over the
39
+ * wire — a real network call inside the elision path, from a dependency's happy path.
40
+ * Every resource smelt hands to a loader goes through {@link assertLocalResource} first.
41
+ */
42
+ export declare const ALLOWED_URL_SCHEMES: readonly string[];
43
+ /**
44
+ * Reject anything that is not a local file, and return the resolved `file:` URL.
45
+ *
46
+ * @throws {NetworkPolicyError} if the input names a remote scheme.
47
+ */
48
+ export declare function assertLocalResource(input: string | URL): URL;
49
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/net/policy.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAenD,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAU/C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAQ9C,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAYlD,CAAC;AAEF,yFAAyF;AACzF,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAG7C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAAc,CAAC;AAEhE;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,GAAG,CAS5D"}
@@ -0,0 +1,120 @@
1
+ import { NetworkPolicyError } from '../errors.js';
2
+ /**
3
+ * Law 1 — zero network in v1 — written down once, in one place, so that the guard
4
+ * test and the runtime check cannot drift apart. `test/guards/no-network.test.ts`
5
+ * imports these lists and fails if any module reachable from the public entrypoint
6
+ * imports one of them.
7
+ *
8
+ * This is a *partition of a discovered set*, not an allowlist over an assumed one:
9
+ * the guard walks the real import graph and classifies every edge it finds. A new
10
+ * transport nobody thought of still gets classified — as an unknown bare import,
11
+ * which the guard reports rather than ignores.
12
+ */
13
+ export const FORBIDDEN_NODE_MODULES = [
14
+ 'http',
15
+ 'https',
16
+ 'http2',
17
+ 'net',
18
+ 'tls',
19
+ 'dgram',
20
+ 'dns',
21
+ 'node:http',
22
+ 'node:https',
23
+ 'node:http2',
24
+ 'node:net',
25
+ 'node:tls',
26
+ 'node:dgram',
27
+ 'node:dns',
28
+ ];
29
+ /** Third-party transports. A dependency that pulls one in is a Law 1 violation too. */
30
+ export const FORBIDDEN_PACKAGES = [
31
+ 'undici',
32
+ 'axios',
33
+ 'node-fetch',
34
+ 'got',
35
+ 'ky',
36
+ 'superagent',
37
+ 'ws',
38
+ 'socket.io-client',
39
+ 'eventsource',
40
+ ];
41
+ /**
42
+ * Globals that reach the network without an import. Grepped for by the guard — both
43
+ * bare (`fetch(…)`) and qualified through a global object (`globalThis.fetch(…)`),
44
+ * which is why `globalThis` and `global` are themselves on the list: an access
45
+ * routed through the global object is the escape hatch around a bare-name grep.
46
+ */
47
+ export const FORBIDDEN_GLOBALS = [
48
+ 'fetch',
49
+ 'XMLHttpRequest',
50
+ 'WebSocket',
51
+ 'EventSource',
52
+ 'navigator',
53
+ 'globalThis',
54
+ 'global',
55
+ ];
56
+ /**
57
+ * The other half of the partition.
58
+ *
59
+ * A forbidden-list alone is an allowlist over an assumed set: it can only catch
60
+ * transports somebody already thought of. So the guard classifies *every* import edge
61
+ * it discovers, and an edge that matches neither the forbidden lists nor these
62
+ * permitted ones **fails the build**. Adding a dependency therefore requires adding it
63
+ * here, in a diff, next to the reason — which is the point.
64
+ */
65
+ export const ALLOWED_NODE_BUILTINS = [
66
+ 'node:crypto', // content hashing for the elision store
67
+ 'node:fs', // reading grammar .wasm files off disk
68
+ 'node:fs/promises',
69
+ 'node:path',
70
+ 'node:url',
71
+ 'node:buffer',
72
+ 'node:module', // web-tree-sitter's own loader shim needs createRequire
73
+ 'node:util', // parseArgs, for the CLI. Argument parsing with zero dependencies.
74
+ 'node:process', // argv, stdin/stdout/stderr and the exit code, for the CLI
75
+ 'node:readline/promises', // line-by-line answers for `smelt init` — reads streams it is handed, opens nothing
76
+ 'node:tty', // isatty(0) for the CLI's TTY check — a plain syscall, no stream, no socket
77
+ ];
78
+ /** Third-party packages any smelt module may import. Keep this list boring and short. */
79
+ export const ALLOWED_PACKAGES = [
80
+ 'web-tree-sitter', // WASM parsers. No native build step, no download at runtime.
81
+ 'tree-sitter-wasms', // prebuilt grammar .wasm blobs, resolved to local file paths
82
+ ];
83
+ /**
84
+ * The only URL schemes any smelt code path may resolve.
85
+ *
86
+ * This is not paranoia about our own code. `web-tree-sitter`'s `Language.load()`
87
+ * accepts a `string | URL`, and an `https:` URL there would fetch a grammar over the
88
+ * wire — a real network call inside the elision path, from a dependency's happy path.
89
+ * Every resource smelt hands to a loader goes through {@link assertLocalResource} first.
90
+ */
91
+ export const ALLOWED_URL_SCHEMES = ['file:'];
92
+ /**
93
+ * Reject anything that is not a local file, and return the resolved `file:` URL.
94
+ *
95
+ * @throws {NetworkPolicyError} if the input names a remote scheme.
96
+ */
97
+ export function assertLocalResource(input) {
98
+ const url = toUrl(input);
99
+ if (!ALLOWED_URL_SCHEMES.includes(url.protocol)) {
100
+ throw new NetworkPolicyError(`refusing to load "${String(input)}": scheme "${url.protocol}" is not local. ` +
101
+ `v1 makes zero network calls; only ${ALLOWED_URL_SCHEMES.join(', ')} is allowed.`);
102
+ }
103
+ return url;
104
+ }
105
+ function toUrl(input) {
106
+ if (input instanceof URL)
107
+ return input;
108
+ // A bare path is a local path. Anything with a scheme is parsed as-is so that
109
+ // "https://evil/grammar.wasm" is caught rather than treated as a relative path.
110
+ if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(input)) {
111
+ try {
112
+ return new URL(input);
113
+ }
114
+ catch {
115
+ throw new NetworkPolicyError(`refusing to load "${input}": not a parseable URL or path.`);
116
+ }
117
+ }
118
+ return new URL(`file://${input.startsWith('/') ? '' : '/'}${input}`);
119
+ }
120
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/net/policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAsB;IACvD,MAAM;IACN,OAAO;IACP,OAAO;IACP,KAAK;IACL,KAAK;IACL,OAAO;IACP,KAAK;IACL,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,UAAU;IACV,UAAU;IACV,YAAY;IACZ,UAAU;CACX,CAAC;AAEF,uFAAuF;AACvF,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,QAAQ;IACR,OAAO;IACP,YAAY;IACZ,KAAK;IACL,IAAI;IACJ,YAAY;IACZ,IAAI;IACJ,kBAAkB;IAClB,aAAa;CACd,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IAClD,OAAO;IACP,gBAAgB;IAChB,WAAW;IACX,aAAa;IACb,WAAW;IACX,YAAY;IACZ,QAAQ;CACT,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAsB;IACtD,aAAa,EAAE,wCAAwC;IACvD,SAAS,EAAE,uCAAuC;IAClD,kBAAkB;IAClB,WAAW;IACX,UAAU;IACV,aAAa;IACb,aAAa,EAAE,wDAAwD;IACvE,WAAW,EAAE,mEAAmE;IAChF,cAAc,EAAE,2DAA2D;IAC3E,wBAAwB,EAAE,oFAAoF;IAC9G,UAAU,EAAE,4EAA4E;CACzF,CAAC;AAEF,yFAAyF;AACzF,MAAM,CAAC,MAAM,gBAAgB,GAAsB;IACjD,iBAAiB,EAAE,8DAA8D;IACjF,mBAAmB,EAAE,6DAA6D;CACnF,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAsB,CAAC,OAAO,CAAC,CAAC;AAEhE;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAmB;IACrD,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IACzB,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,kBAAkB,CAC1B,qBAAqB,MAAM,CAAC,KAAK,CAAC,cAAc,GAAG,CAAC,QAAQ,kBAAkB;YAC5E,qCAAqC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CACpF,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,KAAK,CAAC,KAAmB;IAChC,IAAI,KAAK,YAAY,GAAG;QAAE,OAAO,KAAK,CAAC;IACvC,8EAA8E;IAC9E,gFAAgF;IAChF,IAAI,2BAA2B,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5C,IAAI,CAAC;YACH,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,kBAAkB,CAAC,qBAAqB,KAAK,iCAAiC,CAAC,CAAC;QAC5F,CAAC;IACH,CAAC;IACD,OAAO,IAAI,GAAG,CAAC,UAAU,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC;AACvE,CAAC"}
@@ -0,0 +1,38 @@
1
+ import { Language } from 'web-tree-sitter';
2
+ import type { LanguageId } from '../types.ts';
3
+ /**
4
+ * Grammar file for each language smelt claims to parse — the registry's `wasm`
5
+ * facts, as a map. Derived from `LANGUAGE_PROFILES` (which is
6
+ * `Record<LanguageId, LanguageProfile>`, so adding a `LanguageId` without a profile —
7
+ * and its grammar — is a type error and the two cannot drift). It stays exported
8
+ * because `scripts/bundle-grammars.mjs` and the attribution generator both read it —
9
+ * a hand-written second list of grammar filenames would be exactly the drift the
10
+ * registry exists to prevent.
11
+ */
12
+ export declare const WASM_BY_LANGUAGE: Readonly<Record<LanguageId, string>>;
13
+ /**
14
+ * Resolve a grammar to a path on this machine.
15
+ *
16
+ * The copy bundled in this package wins; `tree-sitter-wasms` is the fallback, for a
17
+ * source checkout that has not run `pnpm build` yet. Note what this function does *not*
18
+ * do: it never constructs a URL from a version string, a CDN base, or anything else.
19
+ * Grammars come off disk — either the ones shipped here or the ones a package manager
20
+ * already installed. A "fetch the grammar on first use" cache is the most natural way
21
+ * to break Law 1 without noticing, because it works perfectly on the machine that
22
+ * wrote it.
23
+ */
24
+ export declare function grammarPath(language: LanguageId): string;
25
+ /**
26
+ * Load a grammar, from disk, once.
27
+ *
28
+ * The bytes are read here and handed to tree-sitter as a `Uint8Array` rather than
29
+ * passing it a path. `Language.load()` accepts `string | URL`, and a `URL` with an
30
+ * `https:` scheme would make it fetch — inside the elision path, from a dependency's
31
+ * ordinary happy path. Reading the file ourselves removes that capability instead of
32
+ * documenting it, and {@link assertLocalResource} rejects a remote path before we get
33
+ * that far.
34
+ */
35
+ export declare function loadGrammar(language: LanguageId): Promise<Language>;
36
+ /** Reset the grammar cache. Tests use it; production has no reason to. */
37
+ export declare function clearGrammarCache(): void;
38
+ //# sourceMappingURL=grammar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grammar.d.ts","sourceRoot":"","sources":["../../src/plan/grammar.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,QAAQ,EAAU,MAAM,iBAAiB,CAAC;AAKnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAEnC,CAAC;AAkBhC;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,CAexD;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAAC,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAYzE;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC"}
@@ -0,0 +1,85 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { Language, Parser } from 'web-tree-sitter';
6
+ import { GrammarUnavailableError } from '../errors.js';
7
+ import { LANGUAGE_PROFILES } from '../lang/registry.js';
8
+ import { assertLocalResource } from '../net/policy.js';
9
+ /**
10
+ * Grammar file for each language smelt claims to parse — the registry's `wasm`
11
+ * facts, as a map. Derived from `LANGUAGE_PROFILES` (which is
12
+ * `Record<LanguageId, LanguageProfile>`, so adding a `LanguageId` without a profile —
13
+ * and its grammar — is a type error and the two cannot drift). It stays exported
14
+ * because `scripts/bundle-grammars.mjs` and the attribution generator both read it —
15
+ * a hand-written second list of grammar filenames would be exactly the drift the
16
+ * registry exists to prevent.
17
+ */
18
+ export const WASM_BY_LANGUAGE = Object.fromEntries(Object.values(LANGUAGE_PROFILES).map((profile) => [profile.id, profile.wasm]));
19
+ /**
20
+ * Where the bundled grammars live, relative to this module.
21
+ *
22
+ * From `dist/plan/grammar.js` and from `src/plan/grammar.ts` alike, `../../grammars/`
23
+ * is this package's own `grammars/` directory — filled by `pnpm build` and shipped
24
+ * inside the npm tarball. That is what makes "zero native compilation, works offline"
25
+ * true rather than aspirational: whoever installs the package has the parsers, with no
26
+ * post-install download and no optional peer dependency to remember. It is also
27
+ * redistribution, which is why `THIRD-PARTY.md` exists and is generated.
28
+ */
29
+ const BUNDLED_GRAMMAR_DIR = new URL('../../grammars/', import.meta.url);
30
+ const require = createRequire(import.meta.url);
31
+ const cache = new Map();
32
+ let runtimeReady;
33
+ /**
34
+ * Resolve a grammar to a path on this machine.
35
+ *
36
+ * The copy bundled in this package wins; `tree-sitter-wasms` is the fallback, for a
37
+ * source checkout that has not run `pnpm build` yet. Note what this function does *not*
38
+ * do: it never constructs a URL from a version string, a CDN base, or anything else.
39
+ * Grammars come off disk — either the ones shipped here or the ones a package manager
40
+ * already installed. A "fetch the grammar on first use" cache is the most natural way
41
+ * to break Law 1 without noticing, because it works perfectly on the machine that
42
+ * wrote it.
43
+ */
44
+ export function grammarPath(language) {
45
+ const file = WASM_BY_LANGUAGE[language];
46
+ const bundled = fileURLToPath(new URL(file, BUNDLED_GRAMMAR_DIR));
47
+ if (existsSync(bundled))
48
+ return bundled;
49
+ try {
50
+ return require.resolve(`tree-sitter-wasms/out/${file}`);
51
+ }
52
+ catch {
53
+ throw new GrammarUnavailableError(`smelt: no grammar for "${language}". The bundled copy is missing (run ` +
54
+ `\`pnpm build\` in a source checkout) and \`tree-sitter-wasms\` is not installed ` +
55
+ `either. Pass \`language: 'unknown'\` to use the lexical planner.`);
56
+ }
57
+ }
58
+ /**
59
+ * Load a grammar, from disk, once.
60
+ *
61
+ * The bytes are read here and handed to tree-sitter as a `Uint8Array` rather than
62
+ * passing it a path. `Language.load()` accepts `string | URL`, and a `URL` with an
63
+ * `https:` scheme would make it fetch — inside the elision path, from a dependency's
64
+ * ordinary happy path. Reading the file ourselves removes that capability instead of
65
+ * documenting it, and {@link assertLocalResource} rejects a remote path before we get
66
+ * that far.
67
+ */
68
+ export async function loadGrammar(language) {
69
+ const cached = cache.get(language);
70
+ if (cached !== undefined)
71
+ return cached;
72
+ runtimeReady ??= Parser.init();
73
+ await runtimeReady;
74
+ const resolved = assertLocalResource(grammarPath(language));
75
+ const bytes = await readFile(fileURLToPath(resolved));
76
+ const grammar = await Language.load(new Uint8Array(bytes));
77
+ cache.set(language, grammar);
78
+ return grammar;
79
+ }
80
+ /** Reset the grammar cache. Tests use it; production has no reason to. */
81
+ export function clearGrammarCache() {
82
+ cache.clear();
83
+ runtimeReady = undefined;
84
+ }
85
+ //# sourceMappingURL=grammar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grammar.js","sourceRoot":"","sources":["../../src/plan/grammar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGvD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAyC,MAAM,CAAC,WAAW,CACtF,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAChD,CAAC;AAEhC;;;;;;;;;GASG;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,iBAAiB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAExE,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAC;AAC9C,IAAI,YAAuC,CAAC;AAE5C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,QAAoB;IAC9C,MAAM,IAAI,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAExC,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAClE,IAAI,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IAExC,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,OAAO,CAAC,yBAAyB,IAAI,EAAE,CAAC,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,uBAAuB,CAC/B,0BAA0B,QAAQ,sCAAsC;YACtE,kFAAkF;YAClF,kEAAkE,CACrE,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAoB;IACpD,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IAExC,YAAY,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC;IAC/B,MAAM,YAAY,CAAC;IAEnB,MAAM,QAAQ,GAAG,mBAAmB,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3D,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC7B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,iBAAiB;IAC/B,KAAK,CAAC,KAAK,EAAE,CAAC;IACd,YAAY,GAAG,SAAS,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,51 @@
1
+ import type { ElisionPlan, PlanInput, Planner } from '../types.ts';
2
+ export declare const LEXICAL_PLANNER_ID = "lexical/v1";
3
+ export interface LexicalPlannerOptions {
4
+ /** Lines of context kept either side of a focus match. Shrinks under budget pressure. */
5
+ readonly contextLines?: number;
6
+ /** Never collapse a run shorter than this, however tempting. */
7
+ readonly minRunLines?: number;
8
+ /** With no focus terms: lines kept at the top. */
9
+ readonly headLines?: number;
10
+ /** With no focus terms: lines kept at the bottom. */
11
+ readonly tailLines?: number;
12
+ /** Focus matching is substring, case-insensitive by default. */
13
+ readonly caseSensitive?: boolean;
14
+ }
15
+ /**
16
+ * The fallback planner, and the reference implementation of the whole pipeline.
17
+ *
18
+ * It knows nothing about syntax: it keeps the lines you were looking for plus a window
19
+ * of context, and collapses the runs in between. That is a weak strategy on code and a
20
+ * perfectly good one on log files, stack traces, JSON dumps and every other thing a
21
+ * coding agent shovels into a prompt — which is why it is the fallback rather than an
22
+ * embarrassment. It also runs on languages smelt has no grammar for, so
23
+ * `language: 'unknown'` is never a dead end.
24
+ *
25
+ * Everything here is deterministic and rule-named. Same input, same plan, and every
26
+ * elision can say which rule produced it.
27
+ */
28
+ export declare class LexicalPlanner implements Planner {
29
+ #private;
30
+ readonly id = "lexical/v1";
31
+ constructor(options?: LexicalPlannerOptions);
32
+ plan(input: PlanInput): Promise<ElisionPlan>;
33
+ }
34
+ /**
35
+ * The synchronous core, exported because it is worth testing and reusing directly.
36
+ *
37
+ * `budgetBytes` is a target, not a guarantee. If the smallest context window still
38
+ * exceeds it, the plan comes back over budget rather than eliding the matches the
39
+ * caller asked to keep — an optimizer that silently drops the thing you searched for is
40
+ * the exact failure this design is built to prevent. Callers who need a hard ceiling
41
+ * check `outputBytes` and decide; smelt will not decide for them.
42
+ *
43
+ * Profitability and budget prediction are **measured, not estimated**: every candidate
44
+ * elision is priced through the input's {@link MarkerPricing} — the seam `apply.ts`
45
+ * builds from the exact builder `applyPlan` will use, comment leader and custom
46
+ * builder included. A guessed constant here once under-counted real ~105-byte markers
47
+ * as 64, so a plan could be "chosen as fitting" and then come back over budget after
48
+ * the markers landed.
49
+ */
50
+ export declare function planLexical(input: PlanInput, options?: LexicalPlannerOptions): ElisionPlan;
51
+ //# sourceMappingURL=lexical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lexical.d.ts","sourceRoot":"","sources":["../../src/plan/lexical.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAiB,SAAS,EAAkB,OAAO,EAAE,MAAM,aAAa,CAAC;AAElG,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAK/C,MAAM,WAAW,qBAAqB;IACpC,yFAAyF;IACzF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,gEAAgE;IAChE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,qDAAqD;IACrD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;CAClC;AAUD;;;;;;;;;;;;GAYG;AACH,qBAAa,cAAe,YAAW,OAAO;;IAC5C,QAAQ,CAAC,EAAE,gBAAsB;IAGjC,YAAY,OAAO,GAAE,qBAA0B,EAE9C;IAED,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAE3C;CACF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,qBAA0B,GAAG,WAAW,CAsC9F"}
@@ -0,0 +1,167 @@
1
+ import { MissingMarkerPricingError } from '../errors.js';
2
+ export const LEXICAL_PLANNER_ID = 'lexical/v1';
3
+ /** How hard the head/tail strategy squeezes, in order, when the budget is not met. */
4
+ const HEAD_TAIL_LADDER = [1, 0.5, 0.25, 0.1, 0.05];
5
+ /**
6
+ * The fallback planner, and the reference implementation of the whole pipeline.
7
+ *
8
+ * It knows nothing about syntax: it keeps the lines you were looking for plus a window
9
+ * of context, and collapses the runs in between. That is a weak strategy on code and a
10
+ * perfectly good one on log files, stack traces, JSON dumps and every other thing a
11
+ * coding agent shovels into a prompt — which is why it is the fallback rather than an
12
+ * embarrassment. It also runs on languages smelt has no grammar for, so
13
+ * `language: 'unknown'` is never a dead end.
14
+ *
15
+ * Everything here is deterministic and rule-named. Same input, same plan, and every
16
+ * elision can say which rule produced it.
17
+ */
18
+ export class LexicalPlanner {
19
+ id = LEXICAL_PLANNER_ID;
20
+ #options;
21
+ constructor(options = {}) {
22
+ this.#options = options;
23
+ }
24
+ plan(input) {
25
+ return Promise.resolve(planLexical(input, this.#options));
26
+ }
27
+ }
28
+ /**
29
+ * The synchronous core, exported because it is worth testing and reusing directly.
30
+ *
31
+ * `budgetBytes` is a target, not a guarantee. If the smallest context window still
32
+ * exceeds it, the plan comes back over budget rather than eliding the matches the
33
+ * caller asked to keep — an optimizer that silently drops the thing you searched for is
34
+ * the exact failure this design is built to prevent. Callers who need a hard ceiling
35
+ * check `outputBytes` and decide; smelt will not decide for them.
36
+ *
37
+ * Profitability and budget prediction are **measured, not estimated**: every candidate
38
+ * elision is priced through the input's {@link MarkerPricing} — the seam `apply.ts`
39
+ * builds from the exact builder `applyPlan` will use, comment leader and custom
40
+ * builder included. A guessed constant here once under-counted real ~105-byte markers
41
+ * as 64, so a plan could be "chosen as fitting" and then come back over budget after
42
+ * the markers landed.
43
+ */
44
+ export function planLexical(input, options = {}) {
45
+ const pricing = requirePricing(input);
46
+ const lines = splitLines(input.text);
47
+ const focus = (input.focus ?? []).filter((term) => term.length > 0);
48
+ const minRunLines = options.minRunLines ?? 3;
49
+ const attempts = focus.length > 0
50
+ ? ladder(options.contextLines ?? 4).map((context) => collapse(lines, keepByFocus(lines, focus, context, options.caseSensitive ?? false), {
51
+ minRunLines,
52
+ rule: 'focus-window',
53
+ pricing,
54
+ }))
55
+ : HEAD_TAIL_LADDER.map((shrink) => collapse(lines, keepByHeadTail(lines, Math.max(3, Math.round((options.headLines ?? 40) * shrink)), Math.max(3, Math.round((options.tailLines ?? 20) * shrink))), { minRunLines, rule: 'head-tail', pricing }));
56
+ const inputBytes = Buffer.byteLength(input.text, 'utf8');
57
+ const chosen = attempts.find((elisions) => predictOutputBytes(inputBytes, elisions, pricing) <= input.budgetBytes) ?? attempts[attempts.length - 1];
58
+ return {
59
+ planner: LEXICAL_PLANNER_ID,
60
+ language: input.language,
61
+ elisions: chosen,
62
+ };
63
+ }
64
+ /** Context-window sizes to try, largest first. */
65
+ function ladder(start) {
66
+ const sizes = [];
67
+ for (let n = start; n >= 0; n -= 1)
68
+ sizes.push(n);
69
+ if (sizes.length === 0)
70
+ sizes.push(0);
71
+ return sizes;
72
+ }
73
+ /**
74
+ * The runtime backstop for JS callers: TypeScript makes `pricing` required, but a JS
75
+ * caller can omit it, and the honest answer is a named refusal rather than a planner
76
+ * quietly pricing markers itself — the inversion the seam removed.
77
+ */
78
+ function requirePricing(input) {
79
+ const pricing = input.pricing;
80
+ if (pricing === undefined)
81
+ throw new MissingMarkerPricingError(LEXICAL_PLANNER_ID);
82
+ return pricing;
83
+ }
84
+ /** The exact UTF-8 cost of the marker this elision would earn. Asked, not guessed. */
85
+ function markerBytes(elision, pricing) {
86
+ return pricing.costBytes(elision.reason, elision.range.end - elision.range.start);
87
+ }
88
+ function predictOutputBytes(inputBytes, elisions, pricing) {
89
+ return elisions.reduce((bytes, elision) => bytes - (elision.range.end - elision.range.start) + markerBytes(elision, pricing), inputBytes);
90
+ }
91
+ function splitLines(text) {
92
+ const lines = [];
93
+ let start = 0;
94
+ let byte = 0;
95
+ const raw = text.split('\n');
96
+ for (let i = 0; i < raw.length; i += 1) {
97
+ const content = raw[i];
98
+ const contentBytes = Buffer.byteLength(content, 'utf8');
99
+ byte = start + contentBytes;
100
+ lines.push({ start, end: byte, text: content });
101
+ // +1 for the '\n' we split on; the final fragment has none.
102
+ start = byte + 1;
103
+ }
104
+ return lines;
105
+ }
106
+ function keepByFocus(lines, focus, contextLines, caseSensitive) {
107
+ const needles = caseSensitive ? focus : focus.map((t) => t.toLowerCase());
108
+ const keep = Array.from({ length: lines.length }, () => false);
109
+ for (let i = 0; i < lines.length; i += 1) {
110
+ const haystack = caseSensitive ? lines[i].text : lines[i].text.toLowerCase();
111
+ if (!needles.some((needle) => haystack.includes(needle)))
112
+ continue;
113
+ const from = Math.max(0, i - contextLines);
114
+ const to = Math.min(lines.length - 1, i + contextLines);
115
+ for (let j = from; j <= to; j += 1)
116
+ keep[j] = true;
117
+ }
118
+ return keep;
119
+ }
120
+ function keepByHeadTail(lines, head, tail) {
121
+ const keep = Array.from({ length: lines.length }, () => false);
122
+ for (let i = 0; i < Math.min(head, lines.length); i += 1)
123
+ keep[i] = true;
124
+ for (let i = Math.max(0, lines.length - tail); i < lines.length; i += 1)
125
+ keep[i] = true;
126
+ return keep;
127
+ }
128
+ function collapse(lines, keep, config) {
129
+ const elisions = [];
130
+ let runStart = -1;
131
+ const flush = (endExclusive) => {
132
+ if (runStart < 0)
133
+ return;
134
+ const count = endExclusive - runStart;
135
+ const range = { start: lines[runStart].start, end: lines[endExclusive - 1].end };
136
+ runStart = -1;
137
+ if (count < config.minRunLines)
138
+ return;
139
+ const candidate = {
140
+ range,
141
+ reason: {
142
+ rule: config.rule,
143
+ explanation: `collapsed ${String(count)} ${count === 1 ? 'line' : 'lines'}${describe(config.rule)}`,
144
+ },
145
+ };
146
+ // Profitability, measured rather than estimated: a marker that costs at least as
147
+ // many bytes as it removes grows the output — same rule, same seam, as the
148
+ // structural planner.
149
+ if (range.end - range.start <= markerBytes(candidate, config.pricing))
150
+ return;
151
+ elisions.push(candidate);
152
+ };
153
+ for (let i = 0; i < lines.length; i += 1) {
154
+ if (keep[i] === true) {
155
+ flush(i);
156
+ }
157
+ else if (runStart < 0) {
158
+ runStart = i;
159
+ }
160
+ }
161
+ flush(lines.length);
162
+ return elisions;
163
+ }
164
+ function describe(rule) {
165
+ return rule === 'focus-window' ? ' with no match for the focus terms' : ' from the middle';
166
+ }
167
+ //# sourceMappingURL=lexical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lexical.js","sourceRoot":"","sources":["../../src/plan/lexical.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { Planner } from '../types.ts';
2
+ import type { LexicalPlannerOptions } from './lexical.ts';
3
+ import type { StructuralPlannerOptions } from './structural.ts';
4
+ /**
5
+ * The option bags a strategy factory may draw from — the same fields
6
+ * `SmelterConfig` carries, so the config object itself can be handed to a factory.
7
+ */
8
+ export interface PlannerFactoryOptions {
9
+ readonly lexical?: LexicalPlannerOptions;
10
+ readonly structural?: StructuralPlannerOptions;
11
+ }
12
+ /**
13
+ * The one registry of planner strategies — string in, constructed {@link Planner} out.
14
+ *
15
+ * This object is the single place the strategy names live. `createSmelter` builds from
16
+ * it, `--strategy` and `smelt.config.json` validation accept exactly its keys, and the
17
+ * `--help` text renders its keys — so a strategy cannot exist in one of those faces and
18
+ * be missing from another. Before this registry the pair was restated in three places,
19
+ * which is how help text rots.
20
+ *
21
+ * `'structural'` parses every language named in {@link STRUCTURAL_LANGUAGES} with a
22
+ * bundled grammar and throws {@link GrammarUnavailableError} for anything else — never
23
+ * a silent lexical fallback. See {@link StructuralPlanner}.
24
+ */
25
+ export declare const PLANNERS: {
26
+ readonly lexical: (options: PlannerFactoryOptions) => Planner;
27
+ readonly structural: (options: PlannerFactoryOptions) => Planner;
28
+ };
29
+ /** Which planner a smelter uses, named by string. Exactly the keys of {@link PLANNERS}. */
30
+ export type Strategy = keyof typeof PLANNERS;
31
+ /** The registry's keys, in declaration order, for help text and error messages. */
32
+ export declare const STRATEGIES: readonly Strategy[];
33
+ /** The one membership test `--strategy` and config validation both use. */
34
+ export declare function isStrategy(value: string): value is Strategy;
35
+ //# sourceMappingURL=planners.d.ts.map