@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.
Files changed (348) hide show
  1. package/README.md +3 -3
  2. package/dist/agents/guide.d.ts +68 -0
  3. package/dist/agents/guide.d.ts.map +1 -0
  4. package/dist/agents/guide.js +72 -0
  5. package/dist/agents/guide.js.map +1 -0
  6. package/dist/agents/instructions.d.ts +133 -0
  7. package/dist/agents/instructions.d.ts.map +1 -0
  8. package/dist/agents/instructions.js +212 -0
  9. package/dist/agents/instructions.js.map +1 -0
  10. package/dist/agents/lint.d.ts +154 -0
  11. package/dist/agents/lint.d.ts.map +1 -0
  12. package/dist/agents/lint.js +761 -0
  13. package/dist/agents/lint.js.map +1 -0
  14. package/dist/agents/split.d.ts +113 -0
  15. package/dist/agents/split.d.ts.map +1 -0
  16. package/dist/agents/split.js +234 -0
  17. package/dist/agents/split.js.map +1 -0
  18. package/dist/apply.d.ts.map +1 -1
  19. package/dist/apply.js.map +1 -1
  20. package/dist/cache/prefix.d.ts.map +1 -1
  21. package/dist/cache/prefix.js.map +1 -1
  22. package/dist/cli/agents.d.ts +39 -0
  23. package/dist/cli/agents.d.ts.map +1 -0
  24. package/dist/cli/agents.js +121 -0
  25. package/dist/cli/agents.js.map +1 -0
  26. package/dist/cli/args.d.ts +17 -70
  27. package/dist/cli/args.d.ts.map +1 -1
  28. package/dist/cli/args.js +18 -338
  29. package/dist/cli/args.js.map +1 -1
  30. package/dist/cli/bin.d.ts.map +1 -1
  31. package/dist/cli/bin.js.map +1 -1
  32. package/dist/cli/config.d.ts +60 -0
  33. package/dist/cli/config.d.ts.map +1 -1
  34. package/dist/cli/config.js +89 -2
  35. package/dist/cli/config.js.map +1 -1
  36. package/dist/cli/hooks.d.ts +55 -51
  37. package/dist/cli/hooks.d.ts.map +1 -1
  38. package/dist/cli/hooks.js +206 -658
  39. package/dist/cli/hooks.js.map +1 -1
  40. package/dist/cli/init.d.ts +30 -15
  41. package/dist/cli/init.d.ts.map +1 -1
  42. package/dist/cli/init.js +167 -38
  43. package/dist/cli/init.js.map +1 -1
  44. package/dist/cli/report.d.ts +23 -0
  45. package/dist/cli/report.d.ts.map +1 -1
  46. package/dist/cli/report.js +142 -4
  47. package/dist/cli/report.js.map +1 -1
  48. package/dist/cli/run.d.ts +29 -90
  49. package/dist/cli/run.d.ts.map +1 -1
  50. package/dist/cli/run.js +34 -342
  51. package/dist/cli/run.js.map +1 -1
  52. package/dist/cli/shell.d.ts +106 -0
  53. package/dist/cli/shell.d.ts.map +1 -0
  54. package/dist/cli/shell.js +100 -0
  55. package/dist/cli/shell.js.map +1 -0
  56. package/dist/cli/subcommands/agents.d.ts +72 -0
  57. package/dist/cli/subcommands/agents.d.ts.map +1 -0
  58. package/dist/cli/subcommands/agents.js +162 -0
  59. package/dist/cli/subcommands/agents.js.map +1 -0
  60. package/dist/cli/subcommands/flags.d.ts +144 -0
  61. package/dist/cli/subcommands/flags.d.ts.map +1 -0
  62. package/dist/cli/subcommands/flags.js +220 -0
  63. package/dist/cli/subcommands/flags.js.map +1 -0
  64. package/dist/cli/subcommands/hooks.d.ts +18 -0
  65. package/dist/cli/subcommands/hooks.d.ts.map +1 -0
  66. package/dist/cli/subcommands/hooks.js +80 -0
  67. package/dist/cli/subcommands/hooks.js.map +1 -0
  68. package/dist/cli/subcommands/init.d.ts +19 -0
  69. package/dist/cli/subcommands/init.d.ts.map +1 -0
  70. package/dist/cli/subcommands/init.js +44 -0
  71. package/dist/cli/subcommands/init.js.map +1 -0
  72. package/dist/cli/subcommands/map.d.ts +75 -0
  73. package/dist/cli/subcommands/map.d.ts.map +1 -0
  74. package/dist/cli/subcommands/map.js +132 -0
  75. package/dist/cli/subcommands/map.js.map +1 -0
  76. package/dist/cli/subcommands/registry.d.ts +81 -0
  77. package/dist/cli/subcommands/registry.d.ts.map +1 -0
  78. package/dist/cli/subcommands/registry.js +141 -0
  79. package/dist/cli/subcommands/registry.js.map +1 -0
  80. package/dist/cli/subcommands/retrieve.d.ts +74 -0
  81. package/dist/cli/subcommands/retrieve.d.ts.map +1 -0
  82. package/dist/cli/subcommands/retrieve.js +84 -0
  83. package/dist/cli/subcommands/retrieve.js.map +1 -0
  84. package/dist/cli/subcommands/smelt.d.ts +107 -0
  85. package/dist/cli/subcommands/smelt.d.ts.map +1 -0
  86. package/dist/cli/subcommands/smelt.js +304 -0
  87. package/dist/cli/subcommands/smelt.js.map +1 -0
  88. package/dist/cli/subcommands/stats.d.ts +43 -0
  89. package/dist/cli/subcommands/stats.d.ts.map +1 -0
  90. package/dist/cli/subcommands/stats.js +62 -0
  91. package/dist/cli/subcommands/stats.js.map +1 -0
  92. package/dist/cli/subcommands/subcommand.d.ts +113 -0
  93. package/dist/cli/subcommands/subcommand.d.ts.map +1 -0
  94. package/dist/cli/subcommands/subcommand.js +3 -0
  95. package/dist/cli/subcommands/subcommand.js.map +1 -0
  96. package/dist/cli/usage.d.ts +3 -0
  97. package/dist/cli/usage.d.ts.map +1 -0
  98. package/dist/cli/usage.js +95 -0
  99. package/dist/cli/usage.js.map +1 -0
  100. package/dist/detect.d.ts.map +1 -1
  101. package/dist/detect.js.map +1 -1
  102. package/dist/errors.d.ts +21 -1
  103. package/dist/errors.d.ts.map +1 -1
  104. package/dist/errors.js +33 -1
  105. package/dist/errors.js.map +1 -1
  106. package/dist/harness/aider.d.ts +10 -0
  107. package/dist/harness/aider.d.ts.map +1 -0
  108. package/dist/harness/aider.js +21 -0
  109. package/dist/harness/aider.js.map +1 -0
  110. package/dist/harness/claude-code.d.ts +3 -0
  111. package/dist/harness/claude-code.d.ts.map +1 -0
  112. package/dist/harness/claude-code.js +68 -0
  113. package/dist/harness/claude-code.js.map +1 -0
  114. package/dist/harness/cline.d.ts +3 -0
  115. package/dist/harness/cline.d.ts.map +1 -0
  116. package/dist/harness/cline.js +54 -0
  117. package/dist/harness/cline.js.map +1 -0
  118. package/dist/harness/codex.d.ts +3 -0
  119. package/dist/harness/codex.d.ts.map +1 -0
  120. package/dist/harness/codex.js +92 -0
  121. package/dist/harness/codex.js.map +1 -0
  122. package/dist/harness/cursor.d.ts +3 -0
  123. package/dist/harness/cursor.d.ts.map +1 -0
  124. package/dist/harness/cursor.js +54 -0
  125. package/dist/harness/cursor.js.map +1 -0
  126. package/dist/harness/gemini.d.ts +3 -0
  127. package/dist/harness/gemini.d.ts.map +1 -0
  128. package/dist/harness/gemini.js +60 -0
  129. package/dist/harness/gemini.js.map +1 -0
  130. package/dist/harness/grok.d.ts +3 -0
  131. package/dist/harness/grok.d.ts.map +1 -0
  132. package/dist/harness/grok.js +47 -0
  133. package/dist/harness/grok.js.map +1 -0
  134. package/dist/harness/hermes.d.ts +3 -0
  135. package/dist/harness/hermes.d.ts.map +1 -0
  136. package/dist/harness/hermes.js +72 -0
  137. package/dist/harness/hermes.js.map +1 -0
  138. package/dist/harness/kilocode.d.ts +3 -0
  139. package/dist/harness/kilocode.d.ts.map +1 -0
  140. package/dist/harness/kilocode.js +35 -0
  141. package/dist/harness/kilocode.js.map +1 -0
  142. package/dist/harness/opencode.d.ts +3 -0
  143. package/dist/harness/opencode.d.ts.map +1 -0
  144. package/dist/harness/opencode.js +97 -0
  145. package/dist/harness/opencode.js.map +1 -0
  146. package/dist/harness/paths.d.ts +17 -0
  147. package/dist/harness/paths.d.ts.map +1 -0
  148. package/dist/harness/paths.js +45 -0
  149. package/dist/harness/paths.js.map +1 -0
  150. package/dist/harness/profile.d.ts +209 -0
  151. package/dist/harness/profile.d.ts.map +1 -0
  152. package/dist/harness/profile.js +41 -0
  153. package/dist/harness/profile.js.map +1 -0
  154. package/dist/harness/registry.d.ts +100 -0
  155. package/dist/harness/registry.d.ts.map +1 -0
  156. package/dist/harness/registry.js +144 -0
  157. package/dist/harness/registry.js.map +1 -0
  158. package/dist/harness/snippet.d.ts +19 -0
  159. package/dist/harness/snippet.d.ts.map +1 -0
  160. package/dist/harness/snippet.js +41 -0
  161. package/dist/harness/snippet.js.map +1 -0
  162. package/dist/hash.d.ts.map +1 -1
  163. package/dist/hash.js.map +1 -1
  164. package/dist/hooks/guard-core.d.ts +14 -14
  165. package/dist/hooks/guard-core.d.ts.map +1 -1
  166. package/dist/hooks/guard-core.js +15 -37
  167. package/dist/hooks/guard-core.js.map +1 -1
  168. package/dist/hooks/shim.d.ts +95 -4
  169. package/dist/hooks/shim.d.ts.map +1 -1
  170. package/dist/hooks/shim.js +87 -2
  171. package/dist/hooks/shim.js.map +1 -1
  172. package/dist/hooks/shims/claude-code.d.ts +5 -18
  173. package/dist/hooks/shims/claude-code.d.ts.map +1 -1
  174. package/dist/hooks/shims/claude-code.js +9 -56
  175. package/dist/hooks/shims/claude-code.js.map +1 -1
  176. package/dist/hooks/shims/cline.d.ts +5 -12
  177. package/dist/hooks/shims/cline.d.ts.map +1 -1
  178. package/dist/hooks/shims/cline.js +9 -34
  179. package/dist/hooks/shims/cline.js.map +1 -1
  180. package/dist/hooks/shims/codex.d.ts +5 -18
  181. package/dist/hooks/shims/codex.d.ts.map +1 -1
  182. package/dist/hooks/shims/codex.js +9 -51
  183. package/dist/hooks/shims/codex.js.map +1 -1
  184. package/dist/hooks/shims/cursor.d.ts +5 -14
  185. package/dist/hooks/shims/cursor.d.ts.map +1 -1
  186. package/dist/hooks/shims/cursor.js +9 -42
  187. package/dist/hooks/shims/cursor.js.map +1 -1
  188. package/dist/hooks/shims/gemini.d.ts +5 -18
  189. package/dist/hooks/shims/gemini.d.ts.map +1 -1
  190. package/dist/hooks/shims/gemini.js +9 -48
  191. package/dist/hooks/shims/gemini.js.map +1 -1
  192. package/dist/hooks/shims/grok.d.ts +5 -13
  193. package/dist/hooks/shims/grok.d.ts.map +1 -1
  194. package/dist/hooks/shims/grok.js +9 -32
  195. package/dist/hooks/shims/grok.js.map +1 -1
  196. package/dist/hooks/shims/hermes.d.ts +5 -17
  197. package/dist/hooks/shims/hermes.d.ts.map +1 -1
  198. package/dist/hooks/shims/hermes.js +9 -45
  199. package/dist/hooks/shims/hermes.js.map +1 -1
  200. package/dist/index.d.ts +43 -76
  201. package/dist/index.d.ts.map +1 -1
  202. package/dist/index.js +21 -67
  203. package/dist/index.js.map +1 -1
  204. package/dist/lang/bash.d.ts.map +1 -1
  205. package/dist/lang/bash.js.map +1 -1
  206. package/dist/lang/c.d.ts.map +1 -1
  207. package/dist/lang/c.js.map +1 -1
  208. package/dist/lang/c_sharp.d.ts.map +1 -1
  209. package/dist/lang/c_sharp.js.map +1 -1
  210. package/dist/lang/cpp.d.ts.map +1 -1
  211. package/dist/lang/cpp.js.map +1 -1
  212. package/dist/lang/go.d.ts.map +1 -1
  213. package/dist/lang/go.js.map +1 -1
  214. package/dist/lang/java.d.ts.map +1 -1
  215. package/dist/lang/java.js.map +1 -1
  216. package/dist/lang/javascript.d.ts.map +1 -1
  217. package/dist/lang/javascript.js.map +1 -1
  218. package/dist/lang/kotlin.d.ts.map +1 -1
  219. package/dist/lang/kotlin.js.map +1 -1
  220. package/dist/lang/php.d.ts.map +1 -1
  221. package/dist/lang/php.js.map +1 -1
  222. package/dist/lang/profile.d.ts.map +1 -1
  223. package/dist/lang/profile.js.map +1 -1
  224. package/dist/lang/python.d.ts.map +1 -1
  225. package/dist/lang/python.js.map +1 -1
  226. package/dist/lang/registry.d.ts.map +1 -1
  227. package/dist/lang/registry.js.map +1 -1
  228. package/dist/lang/ruby.d.ts.map +1 -1
  229. package/dist/lang/ruby.js.map +1 -1
  230. package/dist/lang/rust.d.ts.map +1 -1
  231. package/dist/lang/rust.js.map +1 -1
  232. package/dist/lang/swift.d.ts.map +1 -1
  233. package/dist/lang/swift.js.map +1 -1
  234. package/dist/lang/tsx.d.ts.map +1 -1
  235. package/dist/lang/tsx.js.map +1 -1
  236. package/dist/lang/typescript.d.ts.map +1 -1
  237. package/dist/lang/typescript.js.map +1 -1
  238. package/dist/net/policy.d.ts +18 -1
  239. package/dist/net/policy.d.ts.map +1 -1
  240. package/dist/net/policy.js +6 -4
  241. package/dist/net/policy.js.map +1 -1
  242. package/dist/ops/index.d.ts +39 -0
  243. package/dist/ops/index.d.ts.map +1 -0
  244. package/dist/ops/index.js +37 -0
  245. package/dist/ops/index.js.map +1 -0
  246. package/dist/ops/inputs.d.ts +170 -0
  247. package/dist/ops/inputs.d.ts.map +1 -0
  248. package/dist/ops/inputs.js +142 -0
  249. package/dist/ops/inputs.js.map +1 -0
  250. package/dist/ops/verbs.d.ts +136 -0
  251. package/dist/ops/verbs.d.ts.map +1 -0
  252. package/dist/ops/verbs.js +76 -0
  253. package/dist/ops/verbs.js.map +1 -0
  254. package/dist/plan/auto.d.ts +69 -0
  255. package/dist/plan/auto.d.ts.map +1 -0
  256. package/dist/plan/auto.js +70 -0
  257. package/dist/plan/auto.js.map +1 -0
  258. package/dist/plan/budget.d.ts +21 -0
  259. package/dist/plan/budget.d.ts.map +1 -0
  260. package/dist/plan/budget.js +26 -0
  261. package/dist/plan/budget.js.map +1 -0
  262. package/dist/plan/grammar.d.ts +14 -0
  263. package/dist/plan/grammar.d.ts.map +1 -1
  264. package/dist/plan/grammar.js +47 -6
  265. package/dist/plan/grammar.js.map +1 -1
  266. package/dist/plan/lexical.d.ts.map +1 -1
  267. package/dist/plan/lexical.js +1 -7
  268. package/dist/plan/lexical.js.map +1 -1
  269. package/dist/plan/planners.d.ts +35 -2
  270. package/dist/plan/planners.d.ts.map +1 -1
  271. package/dist/plan/planners.js +35 -2
  272. package/dist/plan/planners.js.map +1 -1
  273. package/dist/plan/structural.d.ts +19 -5
  274. package/dist/plan/structural.d.ts.map +1 -1
  275. package/dist/plan/structural.js +180 -33
  276. package/dist/plan/structural.js.map +1 -1
  277. package/dist/repomap/cache.d.ts +48 -0
  278. package/dist/repomap/cache.d.ts.map +1 -1
  279. package/dist/repomap/cache.js +112 -13
  280. package/dist/repomap/cache.js.map +1 -1
  281. package/dist/repomap/io.d.ts +24 -0
  282. package/dist/repomap/io.d.ts.map +1 -0
  283. package/dist/repomap/io.js +34 -0
  284. package/dist/repomap/io.js.map +1 -0
  285. package/dist/repomap/map.d.ts +69 -6
  286. package/dist/repomap/map.d.ts.map +1 -1
  287. package/dist/repomap/map.js +104 -22
  288. package/dist/repomap/map.js.map +1 -1
  289. package/dist/repomap/rank.d.ts +26 -2
  290. package/dist/repomap/rank.d.ts.map +1 -1
  291. package/dist/repomap/rank.js +20 -0
  292. package/dist/repomap/rank.js.map +1 -1
  293. package/dist/repomap/reader.d.ts +73 -0
  294. package/dist/repomap/reader.d.ts.map +1 -0
  295. package/dist/repomap/reader.js +30 -0
  296. package/dist/repomap/reader.js.map +1 -0
  297. package/dist/repomap/tags.d.ts.map +1 -1
  298. package/dist/repomap/tags.js.map +1 -1
  299. package/dist/retrieve.d.ts +8 -0
  300. package/dist/retrieve.d.ts.map +1 -1
  301. package/dist/retrieve.js +12 -1
  302. package/dist/retrieve.js.map +1 -1
  303. package/dist/smelter.d.ts +79 -0
  304. package/dist/smelter.d.ts.map +1 -0
  305. package/dist/smelter.js +65 -0
  306. package/dist/smelter.js.map +1 -0
  307. package/dist/stages.d.ts.map +1 -1
  308. package/dist/stages.js +2 -2
  309. package/dist/stages.js.map +1 -1
  310. package/dist/stats.d.ts +2 -1
  311. package/dist/stats.d.ts.map +1 -1
  312. package/dist/stats.js +2 -1
  313. package/dist/stats.js.map +1 -1
  314. package/dist/store-dir.d.ts +43 -4
  315. package/dist/store-dir.d.ts.map +1 -1
  316. package/dist/store-dir.js +48 -5
  317. package/dist/store-dir.js.map +1 -1
  318. package/dist/store.d.ts +6 -0
  319. package/dist/store.d.ts.map +1 -1
  320. package/dist/store.js +6 -0
  321. package/dist/store.js.map +1 -1
  322. package/dist/text/json-edit.d.ts +71 -0
  323. package/dist/text/json-edit.d.ts.map +1 -0
  324. package/dist/text/json-edit.js +219 -0
  325. package/dist/text/json-edit.js.map +1 -0
  326. package/dist/types.d.ts +35 -14
  327. package/dist/types.d.ts.map +1 -1
  328. package/dist/types.js.map +1 -1
  329. package/grammars/tree-sitter-bash.wasm +0 -0
  330. package/grammars/tree-sitter-c.wasm +0 -0
  331. package/grammars/tree-sitter-c_sharp.wasm +0 -0
  332. package/grammars/tree-sitter-cpp.wasm +0 -0
  333. package/grammars/tree-sitter-go.wasm +0 -0
  334. package/grammars/tree-sitter-java.wasm +0 -0
  335. package/grammars/tree-sitter-javascript.wasm +0 -0
  336. package/grammars/tree-sitter-kotlin.wasm +0 -0
  337. package/grammars/tree-sitter-php.wasm +0 -0
  338. package/grammars/tree-sitter-python.wasm +0 -0
  339. package/grammars/tree-sitter-ruby.wasm +0 -0
  340. package/grammars/tree-sitter-rust.wasm +0 -0
  341. package/grammars/tree-sitter-swift.wasm +0 -0
  342. package/grammars/tree-sitter-tsx.wasm +0 -0
  343. package/grammars/tree-sitter-typescript.wasm +0 -0
  344. package/package.json +11 -11
  345. package/dist/cli/resolve.d.ts +0 -103
  346. package/dist/cli/resolve.d.ts.map +0 -1
  347. package/dist/cli/resolve.js +0 -106
  348. package/dist/cli/resolve.js.map +0 -1
@@ -1,12 +1,13 @@
1
- import { lstatSync, readdirSync, readFileSync } from 'node:fs';
2
1
  import { join } from 'node:path';
3
2
  import { detectLanguage } from '../detect.js';
4
3
  import { SmeltError } from '../errors.js';
5
4
  import { TagsCache, tagsCacheKey } from './cache.js';
5
+ import { fsCall } from './io.js';
6
6
  import { rankDefinitions } from './rank.js';
7
+ import { nodeFsReader } from './reader.js';
7
8
  import { extractTags } from './tags.js';
8
9
  /**
9
- * The repo-map builder — Slice 7, the cross-file shape.
10
+ * The repo-map builder — the cross-file shape.
10
11
  *
11
12
  * **Modelled on Aider's repo-map, and credited as such.** The whole design is prior
12
13
  * art: Paul Gauthier's Aider extracts tree-sitter definition/reference tags per file,
@@ -18,6 +19,9 @@ import { extractTags } from './tags.js';
18
19
  *
19
20
  * - **Local files only** (Law 1). The caller names the root; symlinks are never
20
21
  * followed, so the walk cannot leave it; binary files are skipped; no network.
22
+ * The whole tree is read through one read-only seam, {@link RepoReader} — the
23
+ * default is `node:fs` and the interface has no writer, so the only bytes this
24
+ * module can write are the tags cache the caller asked for by name.
21
25
  * - **Every inclusion is explainable** (Law 2, applied to inclusion rather than
22
26
  * elision): each symbol in the map carries a rule id and a sentence stating its
23
27
  * definition site and the measured reference counts that ranked it.
@@ -25,8 +29,32 @@ import { extractTags } from './tags.js';
25
29
  * path, name, and line. Two runs over the same tree are byte-identical.
26
30
  * - **The budget is bytes**, same contract as the planners, and it is respected by
27
31
  * construction: symbols are added in rank order until the next line would not fit.
28
- * - **The cache lives only in a directory the caller explicitly hands in**, and a
29
- * corrupt entry is discarded loudly — a warning in the result — never trusted.
32
+ * - **The cache lives only in a directory the caller explicitly hands in**, a corrupt
33
+ * entry is discarded loudly — a warning in the result — never trusted, and it is
34
+ * bounded: each build sweeps the entries it did not use, so the superseded
35
+ * pre-edit version of every file does not accumulate forever. The sweep is
36
+ * housekeeping and never fatal: it runs after the map is computed, so a cache it
37
+ * cannot tidy costs a re-parse next time, never this map. See `cache.ts`.
38
+ * - **Every failure is a `SmeltError`**: the consumer contract's one promise about
39
+ * errors holds here too, so a missing root or an unreadable file arrives as
40
+ * {@link RepoMapIoError} naming the path, not as a raw Node `ENOENT`. Every
41
+ * filesystem call in this module goes through `fsCall` in `io.ts`.
42
+ *
43
+ * **What the ranking can and cannot resolve.** A reference binds to a definition **by
44
+ * bare identifier** — the tags carry names, not resolved symbols — so every definition
45
+ * of a name receives every reference to that name, wherever either lives. Two files
46
+ * that both define `run` share one another's inbound references and therefore rank
47
+ * alike; two overloads of one name each count the whole traffic to it, so a name is
48
+ * counted once per definition of it; an identifier that happens to collide with an
49
+ * unrelated one somewhere else in the tree lends it rank. This is Aider's design, not
50
+ * a defect introduced here: resolving properly means per-language import and scope
51
+ * resolution — a type checker per language — and the map is a *ranking heuristic* for
52
+ * deciding what a human or a model should look at first, never a symbol resolver.
53
+ * What it must not do is *claim* a resolution it did not perform, and it does not: the
54
+ * receipt on each entry says what was actually measured — `refsIn` is the number of
55
+ * references to that **name** across the scanned tree — so a reader who knows this
56
+ * paragraph can read the numbers for exactly what they are. Anything that needs true
57
+ * binding (rename, call graph, dead-code detection) needs a different tool.
30
58
  *
31
59
  * **Deliberately NOT a `Planner`.** `buildRepoMap` returns a {@link RepoMap},
32
60
  * not an `ElisionPlan`: nothing here is elided, nothing is stored under a hash, and
@@ -57,11 +85,35 @@ export const REPO_MAP_PATH_ONLY_RULE = 'path-only';
57
85
  /** Rule id for the warning left behind when a corrupt cache entry is discarded. */
58
86
  export const REPO_MAP_CACHE_CORRUPT_RULE = 'cache-entry-corrupt';
59
87
  /**
60
- * The ignore list used when the caller supplies none. Deliberately tiny: `.git` is
61
- * object storage, `node_modules` is other people's code. A caller with opinions passes
62
- * its own list, which *replaces* this one.
88
+ * The ignore list used when the caller supplies none.
89
+ *
90
+ * Two kinds of directory are on it, for one reason: **including them makes the map
91
+ * wrong, not merely large.** `.git` is object storage and `node_modules` is other
92
+ * people's code — neither is the repository's own source. The rest are build outputs,
93
+ * and they are the sharper case: a built TypeScript repo carries `dist/foo.js` and
94
+ * `dist/foo.d.ts` beside `src/foo.ts`, so *every* symbol was ranked and rendered three
95
+ * times, the duplicates referenced each other, and the default map of the commonest
96
+ * repo shape in this ecosystem was a third source and two-thirds its own compiler
97
+ * output. The names here (`dist`, `build`, `out`, `coverage`) are the conventional
98
+ * output directories of the toolchains this map is most often pointed at; a repo that
99
+ * builds somewhere else passes its own list.
100
+ *
101
+ * Deliberately still tiny, and deliberately **not** a `.gitignore` parser: an ignore
102
+ * list smelt cannot state in one sentence is one nobody can predict.
103
+ *
104
+ * A caller with opinions passes its own list, which *replaces* this one — it is not
105
+ * merged with it. Replacement is the documented contract because a merge means there
106
+ * is no way to say "map my `dist`, I meant it", and a default you cannot turn off is
107
+ * not a default.
63
108
  */
64
- export const DEFAULT_REPO_IGNORE = ['.git', 'node_modules'];
109
+ export const DEFAULT_REPO_IGNORE = [
110
+ '.git',
111
+ 'node_modules',
112
+ 'dist',
113
+ 'build',
114
+ 'out',
115
+ 'coverage',
116
+ ];
65
117
  /**
66
118
  * Build a ranked symbol map of the repository under `options.root`.
67
119
  *
@@ -70,6 +122,10 @@ export const DEFAULT_REPO_IGNORE = ['.git', 'node_modules'];
70
122
  * `test/guards/repo-map.test.ts`, not assumed.
71
123
  *
72
124
  * @throws {SmeltError} when `budgetBytes` is not a positive integer.
125
+ * @throws {RepoMapIoError} when a filesystem call fails — a root that is not there, a
126
+ * directory that will not list, a file that will not read. Never a raw Node error:
127
+ * the contract says every error smelt throws is a `SmeltError`, and a repo map that
128
+ * let `ENOENT` past would be the exception that made the promise worthless.
73
129
  * @throws {GrammarUnavailableError} when a supported language's grammar cannot load —
74
130
  * never a silent skip that would make the map quietly incomplete.
75
131
  */
@@ -81,15 +137,24 @@ export async function buildRepoMap(options) {
81
137
  `how much of the map to throw away.`);
82
138
  }
83
139
  const ignore = options.ignore ?? DEFAULT_REPO_IGNORE;
84
- const cache = options.cacheDir === undefined ? undefined : new TagsCache(options.cacheDir);
85
- const cacheCounts = { hits: 0, misses: 0, discarded: 0 };
140
+ const reader = options.reader ?? nodeFsReader();
141
+ const cacheDir = options.cacheDir;
142
+ const cache = cacheDir === undefined
143
+ ? undefined
144
+ : fsCall('open the tags cache directory', cacheDir, () => new TagsCache(cacheDir));
145
+ const cacheCounts = { hits: 0, misses: 0, discarded: 0, pruned: 0 };
146
+ // The keys this build used. Everything else under the cache directory is a
147
+ // superseded entry — the pre-edit version of a file, or another tree's tags — and
148
+ // is swept once the walk is done. See the bound in `cache.ts`.
149
+ const liveKeys = new Set();
86
150
  const warnings = [];
87
- const files = scanFiles(root, ignore);
151
+ const files = scanFiles(reader, root, ignore);
88
152
  let binarySkipped = 0;
89
153
  const parsed = [];
90
154
  const pathOnlyPaths = [];
91
155
  for (const rel of files) {
92
- const bytes = readFileSync(join(root, ...rel.split('/')));
156
+ const path = join(root, ...rel.split('/'));
157
+ const bytes = fsCall('read the file', path, () => reader.read(path));
93
158
  if (bytes.includes(0)) {
94
159
  binarySkipped += 1;
95
160
  continue;
@@ -99,13 +164,18 @@ export async function buildRepoMap(options) {
99
164
  pathOnlyPaths.push(rel);
100
165
  continue;
101
166
  }
102
- const text = bytes.toString('utf8');
103
- const tags = await tagsFor(text, language, cache, cacheCounts, warnings, rel);
167
+ // `RepoReader.read` promises `Uint8Array`, not `Buffer` (its own note says why),
168
+ // and `ignoreBOM` keeps this byte-for-byte what `Buffer.toString('utf8')` did: a
169
+ // leading U+FEFF stays in the text instead of being silently eaten.
170
+ const text = new TextDecoder('utf-8', { ignoreBOM: true }).decode(bytes);
171
+ const tags = await tagsFor(text, language, cache, cacheCounts, liveKeys, warnings, rel);
104
172
  if (tags.defs.length === 0)
105
173
  pathOnlyPaths.push(rel);
106
174
  if (tags.defs.length > 0 || tags.refs.length > 0)
107
175
  parsed.push({ path: rel, tags });
108
176
  }
177
+ if (cache !== undefined)
178
+ cacheCounts.pruned = cache.sweep(liveKeys);
109
179
  const ranked = rankDefinitions(parsed);
110
180
  const focus = (options.focus ?? []).filter((term) => term.length > 0);
111
181
  const ordered = orderWithFocus(ranked, focus);
@@ -161,10 +231,13 @@ export async function buildRepoMap(options) {
161
231
  };
162
232
  }
163
233
  /** One file's tags — from the cache when possible, from the grammar when not. */
164
- async function tagsFor(text, language, cache, counts, warnings, rel) {
234
+ async function tagsFor(text, language, cache, counts, liveKeys, warnings, rel) {
165
235
  if (cache === undefined)
166
236
  return extractTags(text, language);
167
237
  const key = tagsCacheKey(language, text);
238
+ // Recorded before the lookup, not after: this key is what the tree holds now, so it
239
+ // survives the sweep whether it was a hit, a miss, or a discard-and-rewrite.
240
+ liveKeys.add(key);
168
241
  const found = cache.read(key);
169
242
  if (found === 'corrupt') {
170
243
  counts.discarded += 1;
@@ -191,22 +264,31 @@ async function tagsFor(text, language, cache, counts, warnings, rel) {
191
264
  *
192
265
  * Symlinks are skipped outright — file or directory, in-root or out. Never following
193
266
  * one is the simplest true implementation of "never follow a symlink out of the
194
- * root": there is no resolution step to get wrong.
267
+ * root": there is no resolution step to get wrong. The refusal is stated on
268
+ * `isSymlink`, not left to the accident that an `lstat` of a link is neither file
269
+ * nor directory — so a reader whose `stat` resolves the target is refused just the
270
+ * same, and the guard can watch the refusal happen by counting reader calls.
271
+ *
272
+ * The ignore list is applied before the entry is statted, so an ignored path costs
273
+ * nothing and is never even looked at.
195
274
  */
196
- function scanFiles(root, ignore) {
275
+ function scanFiles(reader, root, ignore) {
197
276
  const found = [];
198
277
  const walk = (dir, relDir) => {
199
- for (const entry of readdirSync(dir).toSorted()) {
278
+ const listed = fsCall('list the directory', dir, () => reader.list(dir));
279
+ for (const entry of listed.map((item) => item.name).toSorted()) {
200
280
  const rel = relDir === '' ? entry : `${relDir}/${entry}`;
201
281
  if (isIgnored(rel, ignore))
202
282
  continue;
203
283
  const full = join(dir, entry);
204
- const stat = lstatSync(full);
205
- if (stat.isSymbolicLink())
284
+ const stat = fsCall('stat', full, () => reader.stat(full));
285
+ if (stat === undefined)
286
+ continue; // the reader has nothing there
287
+ if (stat.isSymlink)
206
288
  continue;
207
- if (stat.isDirectory())
289
+ if (stat.isDirectory)
208
290
  walk(full, rel);
209
- else if (stat.isFile())
291
+ else if (stat.isFile)
210
292
  found.push(rel);
211
293
  }
212
294
  };
@@ -1 +1 @@
1
- {"version":3,"file":"map.js","sourceRoot":"","sources":["../../src/repomap/map.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"map.js","sourceRoot":"","sources":["../../src/repomap/map.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from 'node:path';\n\nimport { detectLanguage } from '../detect.ts';\nimport { SmeltError } from '../errors.ts';\nimport type { LanguageId } from '../types.ts';\n\nimport { TagsCache, tagsCacheKey } from './cache.ts';\nimport { fsCall } from './io.ts';\nimport { rankDefinitions } from './rank.ts';\nimport type { FileTagsEntry, RankedDefinition } from './rank.ts';\nimport { nodeFsReader } from './reader.ts';\nimport type { RepoReader } from './reader.ts';\nimport { extractTags } from './tags.ts';\nimport type { FileTags } from './tags.ts';\n\n/**\n * The repo-map builder — the cross-file shape.\n *\n * **Modelled on Aider's repo-map, and credited as such.** The whole design is prior\n * art: Paul Gauthier's Aider extracts tree-sitter definition/reference tags per file,\n * runs PageRank over the cross-file reference graph, fits the ranked result to a\n * budget, and caches tags on disk keyed by content — see\n * https://aider.chat/docs/repomap.html and `aider/repomap.py` in\n * https://github.com/Aider-AI/aider. Nothing about the approach is this project's\n * invention; what this module adds is only smelt's own house rules:\n *\n * - **Local files only** (Law 1). The caller names the root; symlinks are never\n * followed, so the walk cannot leave it; binary files are skipped; no network.\n * The whole tree is read through one read-only seam, {@link RepoReader} — the\n * default is `node:fs` and the interface has no writer, so the only bytes this\n * module can write are the tags cache the caller asked for by name.\n * - **Every inclusion is explainable** (Law 2, applied to inclusion rather than\n * elision): each symbol in the map carries a rule id and a sentence stating its\n * definition site and the measured reference counts that ranked it.\n * - **Deterministic**: fixed PageRank constants, sorted walks, a total tie-break by\n * path, name, and line. Two runs over the same tree are byte-identical.\n * - **The budget is bytes**, same contract as the planners, and it is respected by\n * construction: symbols are added in rank order until the next line would not fit.\n * - **The cache lives only in a directory the caller explicitly hands in**, a corrupt\n * entry is discarded loudly — a warning in the result — never trusted, and it is\n * bounded: each build sweeps the entries it did not use, so the superseded\n * pre-edit version of every file does not accumulate forever. The sweep is\n * housekeeping and never fatal: it runs after the map is computed, so a cache it\n * cannot tidy costs a re-parse next time, never this map. See `cache.ts`.\n * - **Every failure is a `SmeltError`**: the consumer contract's one promise about\n * errors holds here too, so a missing root or an unreadable file arrives as\n * {@link RepoMapIoError} naming the path, not as a raw Node `ENOENT`. Every\n * filesystem call in this module goes through `fsCall` in `io.ts`.\n *\n * **What the ranking can and cannot resolve.** A reference binds to a definition **by\n * bare identifier** — the tags carry names, not resolved symbols — so every definition\n * of a name receives every reference to that name, wherever either lives. Two files\n * that both define `run` share one another's inbound references and therefore rank\n * alike; two overloads of one name each count the whole traffic to it, so a name is\n * counted once per definition of it; an identifier that happens to collide with an\n * unrelated one somewhere else in the tree lends it rank. This is Aider's design, not\n * a defect introduced here: resolving properly means per-language import and scope\n * resolution — a type checker per language — and the map is a *ranking heuristic* for\n * deciding what a human or a model should look at first, never a symbol resolver.\n * What it must not do is *claim* a resolution it did not perform, and it does not: the\n * receipt on each entry says what was actually measured — `refsIn` is the number of\n * references to that **name** across the scanned tree — so a reader who knows this\n * paragraph can read the numbers for exactly what they are. Anything that needs true\n * binding (rename, call graph, dead-code detection) needs a different tool.\n *\n * **Deliberately NOT a `Planner`.** `buildRepoMap` returns a {@link RepoMap},\n * not an `ElisionPlan`: nothing here is elided, nothing is stored under a hash, and\n * there is no original to reconstruct — the map is a *summary built up under a\n * budget*, not a *removal to be reversed*. Forcing the Planner interface onto it\n * would claim Law 3 (reversibility) about output that has no bytes to give back,\n * which is exactly the kind of lie the interface exists to prevent. The CLI serves\n * it as its own subcommand (`smelt map`), never as a `--strategy`.\n */\n\n/** The id stamped on every map this module emits. */\nexport const REPO_MAP_ID = 'repomap/v1';\n\n/**\n * Rule id for a symbol something in the scanned tree references — its own file\n * included, so its rank may still be zero when every reference is same-file (only\n * cross-file references move rank).\n */\nexport const REPO_MAP_RANKED_RULE = 'ranked-definition';\n\n/** Rule id for a definition nothing in the scanned tree references. */\nexport const REPO_MAP_UNREFERENCED_RULE = 'unreferenced-definition';\n\n/**\n * Rule id for a definition promoted because it matches a caller-supplied focus term.\n * The rank stays the measured PageRank share — focus moves a symbol's *place in the\n * fill order*, never its numbers.\n */\nexport const REPO_MAP_FOCUS_RULE = 'focus-match';\n\n/** Rule id for a file listed by path because no definitions were extracted from it. */\nexport const REPO_MAP_PATH_ONLY_RULE = 'path-only';\n\n/** Rule id for the warning left behind when a corrupt cache entry is discarded. */\nexport const REPO_MAP_CACHE_CORRUPT_RULE = 'cache-entry-corrupt';\n\n/**\n * The ignore list used when the caller supplies none.\n *\n * Two kinds of directory are on it, for one reason: **including them makes the map\n * wrong, not merely large.** `.git` is object storage and `node_modules` is other\n * people's code — neither is the repository's own source. The rest are build outputs,\n * and they are the sharper case: a built TypeScript repo carries `dist/foo.js` and\n * `dist/foo.d.ts` beside `src/foo.ts`, so *every* symbol was ranked and rendered three\n * times, the duplicates referenced each other, and the default map of the commonest\n * repo shape in this ecosystem was a third source and two-thirds its own compiler\n * output. The names here (`dist`, `build`, `out`, `coverage`) are the conventional\n * output directories of the toolchains this map is most often pointed at; a repo that\n * builds somewhere else passes its own list.\n *\n * Deliberately still tiny, and deliberately **not** a `.gitignore` parser: an ignore\n * list smelt cannot state in one sentence is one nobody can predict.\n *\n * A caller with opinions passes its own list, which *replaces* this one — it is not\n * merged with it. Replacement is the documented contract because a merge means there\n * is no way to say \"map my `dist`, I meant it\", and a default you cannot turn off is\n * not a default.\n */\nexport const DEFAULT_REPO_IGNORE: readonly string[] = [\n '.git',\n 'node_modules',\n 'dist',\n 'build',\n 'out',\n 'coverage',\n];\n\n/** Why an entry is in the map — same two-register shape as {@link ElisionReason}. */\nexport interface RepoMapReason {\n /** Stable machine id, e.g. `'ranked-definition'`. */\n readonly rule: string;\n /** A sentence a human can read: definition site and the counts that ranked it. */\n readonly explanation: string;\n}\n\nexport interface RepoMapOptions {\n /** The repository root to read. Local files only; symlinks are never followed. */\n readonly root: string;\n /** Ceiling for the rendered map, in UTF-8 bytes. Respected, not aimed at. */\n readonly budgetBytes: number;\n /**\n * What the task is actually about — same meaning as a planner's focus. A\n * definition whose name or path contains a term (case-insensitive, like the\n * lexical planner's default) is promoted to the front of the fill order, so it\n * survives a tight budget. Promotion only: the measured rank and reference\n * counts are never altered, and each promoted entry says which term it matched.\n */\n readonly focus?: readonly string[];\n /**\n * Paths to skip, replacing {@link DEFAULT_REPO_IGNORE}. An entry containing `/` is\n * matched as a root-relative path prefix — a trailing slash counts, so `build/`\n * means the root-level `build` tree, never every `build` segment anywhere; a bare\n * name matches any path segment.\n */\n readonly ignore?: readonly string[];\n /**\n * Directory for the tags cache. **Only** when this is passed is anything written\n * to disk — smelt never writes outside a store or cache it was explicitly handed.\n */\n readonly cacheDir?: string;\n /**\n * The filesystem the map reads through. Defaults to {@link nodeFsReader} — plain\n * `node:fs`, the calls this module used to make in-line — so callers that do not\n * care never mention it. Hand in a {@link RepoReader} to map a tree that is not on\n * this disk, or to watch, call by call, exactly what the walk touched. Read-only\n * by construction: the interface has no writer.\n */\n readonly reader?: RepoReader;\n}\n\n/** One symbol included in the rendered map, with the receipt for its inclusion. */\nexport interface RepoMapEntry {\n readonly path: string;\n readonly name: string;\n readonly kind: string;\n /** 1-based line of the definition. */\n readonly line: number;\n /** PageRank share this definition attracted. `0` for unreferenced definitions. */\n readonly rank: number;\n /** Measured: total references to this name across the scanned tree. */\n readonly refsIn: number;\n /** Measured: distinct files holding those references. */\n readonly refsInFiles: number;\n /** Measured: references the defining file makes to names defined elsewhere. */\n readonly refsOut: number;\n readonly reason: RepoMapReason;\n}\n\n/** A file listed by path because no definitions were extracted from it. */\nexport interface RepoMapPathEntry {\n readonly path: string;\n readonly reason: RepoMapReason;\n}\n\n/** Something worth telling the caller that is not worth failing the build over. */\nexport interface RepoMapWarning {\n readonly rule: string;\n readonly explanation: string;\n}\n\n/** Measured cache activity for one build. Counts only — smelt claims no rates. */\nexport interface RepoMapCacheCounts {\n /** Lookups answered from disk. */\n readonly hits: number;\n /** Lookups that found nothing and re-extracted from source. */\n readonly misses: number;\n /** Corrupt entries deleted and re-extracted; each one also left a warning. */\n readonly discarded: number;\n /**\n * Entries this build swept because it did not use them — the bound on the cache,\n * made visible. A non-zero count on a repeat build over an unchanged tree means the\n * cache directory is shared with another tree, which costs re-parses.\n */\n readonly pruned: number;\n}\n\nexport interface RepoMap {\n readonly id: typeof REPO_MAP_ID;\n /** The rendered map. Always at most `budgetBytes` UTF-8 bytes. */\n readonly text: string;\n readonly outputBytes: number;\n readonly budgetBytes: number;\n /** The symbols that fit, in fill order (focus matches first, then rank). */\n readonly entries: readonly RepoMapEntry[];\n /** Path-only files that fit, after the symbols, in path order. */\n readonly pathOnly: readonly RepoMapPathEntry[];\n /** All ranked definitions found, before the budget cut anything. */\n readonly definitionsTotal: number;\n /** All path-only candidates found, before the budget cut anything. */\n readonly pathOnlyTotal: number;\n /** Regular files examined under the root, after the ignore list. */\n readonly filesScanned: number;\n /** Files skipped because their bytes contain a NUL — binary, not mappable. */\n readonly binarySkipped: number;\n readonly warnings: readonly RepoMapWarning[];\n /** Present only when the caller handed in a `cacheDir`. Never invented. */\n readonly cache?: RepoMapCacheCounts;\n}\n\n/**\n * Build a ranked symbol map of the repository under `options.root`.\n *\n * Reads local files only. Deterministic: two runs over the same tree, with or without\n * a warm cache, produce byte-identical maps — asserted by the guard in\n * `test/guards/repo-map.test.ts`, not assumed.\n *\n * @throws {SmeltError} when `budgetBytes` is not a positive integer.\n * @throws {RepoMapIoError} when a filesystem call fails — a root that is not there, a\n * directory that will not list, a file that will not read. Never a raw Node error:\n * the contract says every error smelt throws is a `SmeltError`, and a repo map that\n * let `ENOENT` past would be the exception that made the promise worthless.\n * @throws {GrammarUnavailableError} when a supported language's grammar cannot load —\n * never a silent skip that would make the map quietly incomplete.\n */\nexport async function buildRepoMap(options: RepoMapOptions): Promise<RepoMap> {\n const { root, budgetBytes } = options;\n if (!Number.isInteger(budgetBytes) || budgetBytes < 1) {\n throw new SmeltError(\n `smelt: budgetBytes must be a positive integer, got ${String(budgetBytes)}. ` +\n `There is no default budget — a budget smelt invented would silently decide ` +\n `how much of the map to throw away.`,\n );\n }\n const ignore = options.ignore ?? DEFAULT_REPO_IGNORE;\n const reader = options.reader ?? nodeFsReader();\n const cacheDir = options.cacheDir;\n const cache =\n cacheDir === undefined\n ? undefined\n : fsCall('open the tags cache directory', cacheDir, () => new TagsCache(cacheDir));\n const cacheCounts = { hits: 0, misses: 0, discarded: 0, pruned: 0 };\n // The keys this build used. Everything else under the cache directory is a\n // superseded entry — the pre-edit version of a file, or another tree's tags — and\n // is swept once the walk is done. See the bound in `cache.ts`.\n const liveKeys = new Set<string>();\n const warnings: RepoMapWarning[] = [];\n\n const files = scanFiles(reader, root, ignore);\n let binarySkipped = 0;\n const parsed: FileTagsEntry[] = [];\n const pathOnlyPaths: string[] = [];\n\n for (const rel of files) {\n const path = join(root, ...rel.split('/'));\n const bytes = fsCall('read the file', path, () => reader.read(path));\n if (bytes.includes(0)) {\n binarySkipped += 1;\n continue;\n }\n const language = detectLanguage(rel);\n if (language === 'unknown') {\n pathOnlyPaths.push(rel);\n continue;\n }\n // `RepoReader.read` promises `Uint8Array`, not `Buffer` (its own note says why),\n // and `ignoreBOM` keeps this byte-for-byte what `Buffer.toString('utf8')` did: a\n // leading U+FEFF stays in the text instead of being silently eaten.\n const text = new TextDecoder('utf-8', { ignoreBOM: true }).decode(bytes);\n const tags = await tagsFor(text, language, cache, cacheCounts, liveKeys, warnings, rel);\n if (tags.defs.length === 0) pathOnlyPaths.push(rel);\n if (tags.defs.length > 0 || tags.refs.length > 0) parsed.push({ path: rel, tags });\n }\n\n if (cache !== undefined) cacheCounts.pruned = cache.sweep(liveKeys);\n\n const ranked = rankDefinitions(parsed);\n const focus = (options.focus ?? []).filter((term) => term.length > 0);\n const ordered = orderWithFocus(ranked, focus);\n\n // Fit to the budget: ranked symbols first — focus matches promoted to the front,\n // each partition in rank order — then path-only files, in path order. Filling\n // stops at the first line that does not fit, so the included set is always a\n // prefix of that order — a map that skipped its #2 symbol to squeeze in its #9\n // would be lying about what mattered.\n const lines: string[] = [];\n let bytes = 0;\n const tryAppend = (line: string): boolean => {\n const lineBytes = Buffer.byteLength(line, 'utf8') + 1; // its trailing newline\n if (bytes + lineBytes > budgetBytes) return false;\n lines.push(line);\n bytes += lineBytes;\n return true;\n };\n\n const entries: RepoMapEntry[] = [];\n for (const { definition, focusTerm } of ordered) {\n if (!tryAppend(renderDefinition(definition))) break;\n entries.push(toEntry(definition, focusTerm));\n }\n const pathOnly: RepoMapPathEntry[] = [];\n if (entries.length === ordered.length) {\n for (const rel of pathOnlyPaths) {\n if (!tryAppend(`${rel} [path only]`)) break;\n pathOnly.push({\n path: rel,\n reason: {\n rule: REPO_MAP_PATH_ONLY_RULE,\n explanation: `no definitions extracted from ${rel}; listed by path so the file stays visible`,\n },\n });\n }\n }\n\n const text = lines.length === 0 ? '' : `${lines.join('\\n')}\\n`;\n return {\n id: REPO_MAP_ID,\n text,\n outputBytes: Buffer.byteLength(text, 'utf8'),\n budgetBytes,\n entries,\n pathOnly,\n definitionsTotal: ranked.length,\n pathOnlyTotal: pathOnlyPaths.length,\n filesScanned: files.length,\n binarySkipped,\n warnings,\n ...(cache === undefined ? {} : { cache: { ...cacheCounts } }),\n };\n}\n\n/** One file's tags — from the cache when possible, from the grammar when not. */\nasync function tagsFor(\n text: string,\n language: LanguageId,\n cache: TagsCache | undefined,\n counts: { hits: number; misses: number; discarded: number; pruned: number },\n liveKeys: Set<string>,\n warnings: RepoMapWarning[],\n rel: string,\n): Promise<FileTags> {\n if (cache === undefined) return extractTags(text, language);\n\n const key = tagsCacheKey(language, text);\n // Recorded before the lookup, not after: this key is what the tree holds now, so it\n // survives the sweep whether it was a hit, a miss, or a discard-and-rewrite.\n liveKeys.add(key);\n const found = cache.read(key);\n if (found === 'corrupt') {\n counts.discarded += 1;\n warnings.push({\n rule: REPO_MAP_CACHE_CORRUPT_RULE,\n explanation:\n `discarded a corrupt cache entry for ${rel} and re-extracted its tags from ` +\n `source; the entry file was deleted so it cannot be read again`,\n });\n } else if (found !== undefined) {\n counts.hits += 1;\n return found;\n } else {\n counts.misses += 1;\n }\n const tags = await extractTags(text, language);\n cache.write(key, tags);\n return tags;\n}\n\n/**\n * Walk the tree under `root`, depth-first in sorted order, returning `/`-separated\n * relative paths of every regular file that survives the ignore list.\n *\n * Symlinks are skipped outright — file or directory, in-root or out. Never following\n * one is the simplest true implementation of \"never follow a symlink out of the\n * root\": there is no resolution step to get wrong. The refusal is stated on\n * `isSymlink`, not left to the accident that an `lstat` of a link is neither file\n * nor directory — so a reader whose `stat` resolves the target is refused just the\n * same, and the guard can watch the refusal happen by counting reader calls.\n *\n * The ignore list is applied before the entry is statted, so an ignored path costs\n * nothing and is never even looked at.\n */\nfunction scanFiles(reader: RepoReader, root: string, ignore: readonly string[]): readonly string[] {\n const found: string[] = [];\n const walk = (dir: string, relDir: string): void => {\n const listed = fsCall('list the directory', dir, () => reader.list(dir));\n for (const entry of listed.map((item) => item.name).toSorted()) {\n const rel = relDir === '' ? entry : `${relDir}/${entry}`;\n if (isIgnored(rel, ignore)) continue;\n const full = join(dir, entry);\n const stat = fsCall('stat', full, () => reader.stat(full));\n if (stat === undefined) continue; // the reader has nothing there\n if (stat.isSymlink) continue;\n if (stat.isDirectory) walk(full, rel);\n else if (stat.isFile) found.push(rel);\n }\n };\n walk(root, '');\n return found;\n}\n\n/** See {@link RepoMapOptions.ignore} for the two match modes. */\nfunction isIgnored(rel: string, ignore: readonly string[]): boolean {\n for (const entry of ignore) {\n const cleaned = entry.replace(/\\/+$/, '');\n if (cleaned === '') continue;\n // Prefix mode is decided on the entry AS WRITTEN, before the trailing slash is\n // trimmed: `build/` contains a `/` and so is the documented root-relative prefix\n // (the root-level `build` tree), not a bare name that would match a `build`\n // segment at any depth.\n if (entry.includes('/')) {\n if (rel === cleaned || rel.startsWith(`${cleaned}/`)) return true;\n } else if (rel.split('/').includes(cleaned)) {\n return true;\n }\n }\n return false;\n}\n\n/** One ranked definition in fill order, with the focus term that promoted it, if any. */\ninterface OrderedDefinition {\n readonly definition: RankedDefinition;\n readonly focusTerm?: string;\n}\n\n/**\n * The fill order: focus-matched definitions first, then the rest, each partition\n * keeping the ranker's total order. A stable partition of a deterministic order is\n * itself deterministic, so the map's byte-for-byte claim survives focus untouched.\n * The match is a case-insensitive substring over name and path — the lexical\n * planner's default, so \"focus\" means the same thing in both places — and the\n * *first* matching term in caller order is the one the receipt names.\n */\nfunction orderWithFocus(\n ranked: readonly RankedDefinition[],\n focus: readonly string[],\n): readonly OrderedDefinition[] {\n if (focus.length === 0) return ranked.map((definition) => ({ definition }));\n const needles = focus.map((term) => term.toLowerCase());\n const matched: OrderedDefinition[] = [];\n const rest: OrderedDefinition[] = [];\n for (const definition of ranked) {\n const haystack = `${definition.path}\\0${definition.name}`.toLowerCase();\n const index = needles.findIndex((needle) => haystack.includes(needle));\n if (index === -1) rest.push({ definition });\n else matched.push({ definition, focusTerm: focus[index]! });\n }\n return [...matched, ...rest];\n}\n\n/** One rendered map line. Everything in it is measured, nothing estimated. */\nfunction renderDefinition(definition: RankedDefinition): string {\n return (\n `${definition.path}:${String(definition.line)} ${definition.kind} ${definition.name} ` +\n `[${String(definition.refsIn)} in from ${String(definition.refsInFiles)} ` +\n `${plural('file', definition.refsInFiles)}, ${String(definition.refsOut)} out]`\n );\n}\n\n/** Law 2, applied to inclusion: the receipt every included symbol carries. */\nfunction toEntry(definition: RankedDefinition, focusTerm?: string): RepoMapEntry {\n const site = `defined at ${definition.path}:${String(definition.line)}`;\n const counts =\n `${String(definition.refsIn)} ${plural('reference', definition.refsIn)} in ` +\n `from ${String(definition.refsInFiles)} ${plural('file', definition.refsInFiles)}; ` +\n `its file makes ${String(definition.refsOut)} ` +\n `${plural('reference', definition.refsOut)} out`;\n const reason: RepoMapReason =\n focusTerm !== undefined\n ? {\n rule: REPO_MAP_FOCUS_RULE,\n explanation: `${site}; matches focus \"${focusTerm}\"; ${counts}`,\n }\n : definition.refsIn === 0\n ? {\n rule: REPO_MAP_UNREFERENCED_RULE,\n explanation: `${site}; no references to it anywhere in the scanned tree`,\n }\n : {\n rule: REPO_MAP_RANKED_RULE,\n explanation: `${site}; ${counts}`,\n };\n return {\n path: definition.path,\n name: definition.name,\n kind: definition.kind,\n line: definition.line,\n rank: definition.rank,\n refsIn: definition.refsIn,\n refsInFiles: definition.refsInFiles,\n refsOut: definition.refsOut,\n reason,\n };\n}\n\nfunction plural(word: string, count: number): string {\n return count === 1 ? word : `${word}s`;\n}\n"]}
@@ -13,6 +13,26 @@ import type { FileTags } from './tags.ts';
13
13
  * fixed damping, a fixed iteration count, nodes visited in sorted-path order, and a
14
14
  * total tie-break (rank, then path, then name, then line). No `Math.random`, no
15
15
  * `Date`, no map-iteration order leaking into the output.
16
+ *
17
+ * **The resolution limit, stated plainly: references bind by bare identifier.** A
18
+ * reference tag is a name and a count; there is no import graph, no scope, no
19
+ * declaration lookup. So `defsByName` below maps one identifier to *every* definition
20
+ * of it in the tree, and each reference to that identifier reaches all of them:
21
+ *
22
+ * - Same-name symbols in different files share rank and share `refsIn`. Two `run`s
23
+ * are, to this ranker, one name that several files happen to define.
24
+ * - Overloads and re-exports double-count: a name defined twice attracts the whole
25
+ * traffic to it twice, so it takes two lines near the top of the map.
26
+ * - A common identifier (`get`, `render`, `main`) collects references that in truth
27
+ * belong to something else entirely.
28
+ *
29
+ * This is Aider's design, inherited on purpose. The alternative is per-language import
30
+ * and scope resolution — a type checker per language — and the map is a *ranking
31
+ * heuristic* for what to read first, not a symbol resolver; something needing true
32
+ * binding (rename, call graph, dead code) needs a different tool. What matters under
33
+ * Law 4 is that nothing here claims otherwise: `refsIn` and `refsInFiles` are honestly
34
+ * the references to and the files mentioning the **name**, and each entry's receipt
35
+ * says so, so the numbers can be read for exactly what they measure.
16
36
  */
17
37
  /** The damping factor. 0.85 is the classic PageRank constant; fixed, never sampled. */
18
38
  export declare const PAGERANK_DAMPING = 0.85;
@@ -31,9 +51,13 @@ export interface RankedDefinition {
31
51
  readonly line: number;
32
52
  /** Accumulated PageRank share. `0` when no other file references this name. */
33
53
  readonly rank: number;
34
- /** Total references to this name across every scanned file (its own included). */
54
+ /**
55
+ * Total references to this **name** across every scanned file (its own included).
56
+ * Per name, not per symbol: see the module comment — every definition of a name
57
+ * shares the count, because references bind by bare identifier.
58
+ */
35
59
  readonly refsIn: number;
36
- /** Distinct files holding at least one of those references. */
60
+ /** Distinct files holding at least one of those references. Also per name. */
37
61
  readonly refsInFiles: number;
38
62
  /** References the defining file makes to names defined in *other* files. */
39
63
  readonly refsOut: number;
@@ -1 +1 @@
1
- {"version":3,"file":"rank.d.ts","sourceRoot":"","sources":["../../src/repomap/rank.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1C;;;;;;;;;;;;;;GAcG;AAEH,uFAAuF;AACvF,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,qFAAqF;AACrF,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,gFAAgF;AAChF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;CACzB;AAED,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,GAAG,SAAS,gBAAgB,EAAE,CAkH5F"}
1
+ {"version":3,"file":"rank.d.ts","sourceRoot":"","sources":["../../src/repomap/rank.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,uFAAuF;AACvF,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,qFAAqF;AACrF,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,gFAAgF;AAChF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;CACzB;AAED,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,GAAG,SAAS,gBAAgB,EAAE,CAkH5F","sourcesContent":["import type { FileTags } from './tags.ts';\n\n/**\n * Deterministic PageRank over the cross-file reference graph.\n *\n * Modelled on Aider's repo-map ranking (https://aider.chat/docs/repomap.html; see\n * `aider/repomap.py` in https://github.com/Aider-AI/aider): files are the graph's\n * nodes, and a file that references an identifier defined in another file contributes\n * a weighted edge toward the definer. A file's rank is then distributed across its\n * definitions in proportion to the reference weight each one attracted. The idea is\n * Aider's; only the implementation is this repository's.\n *\n * Deterministic by construction, which is what makes the map snapshot-testable:\n * fixed damping, a fixed iteration count, nodes visited in sorted-path order, and a\n * total tie-break (rank, then path, then name, then line). No `Math.random`, no\n * `Date`, no map-iteration order leaking into the output.\n *\n * **The resolution limit, stated plainly: references bind by bare identifier.** A\n * reference tag is a name and a count; there is no import graph, no scope, no\n * declaration lookup. So `defsByName` below maps one identifier to *every* definition\n * of it in the tree, and each reference to that identifier reaches all of them:\n *\n * - Same-name symbols in different files share rank and share `refsIn`. Two `run`s\n * are, to this ranker, one name that several files happen to define.\n * - Overloads and re-exports double-count: a name defined twice attracts the whole\n * traffic to it twice, so it takes two lines near the top of the map.\n * - A common identifier (`get`, `render`, `main`) collects references that in truth\n * belong to something else entirely.\n *\n * This is Aider's design, inherited on purpose. The alternative is per-language import\n * and scope resolution — a type checker per language — and the map is a *ranking\n * heuristic* for what to read first, not a symbol resolver; something needing true\n * binding (rename, call graph, dead code) needs a different tool. What matters under\n * Law 4 is that nothing here claims otherwise: `refsIn` and `refsInFiles` are honestly\n * the references to and the files mentioning the **name**, and each entry's receipt\n * says so, so the numbers can be read for exactly what they measure.\n */\n\n/** The damping factor. 0.85 is the classic PageRank constant; fixed, never sampled. */\nexport const PAGERANK_DAMPING = 0.85;\n\n/** Fixed iteration count — no convergence test, so runtime and output never vary. */\nexport const PAGERANK_ITERATIONS = 50;\n\n/** What the caller hands the ranker: one file's path and its extracted tags. */\nexport interface FileTagsEntry {\n readonly path: string;\n readonly tags: FileTags;\n}\n\n/** One definition with its computed rank and the measured counts that explain it. */\nexport interface RankedDefinition {\n readonly path: string;\n readonly name: string;\n readonly kind: string;\n readonly line: number;\n /** Accumulated PageRank share. `0` when no other file references this name. */\n readonly rank: number;\n /**\n * Total references to this **name** across every scanned file (its own included).\n * Per name, not per symbol: see the module comment — every definition of a name\n * shares the count, because references bind by bare identifier.\n */\n readonly refsIn: number;\n /** Distinct files holding at least one of those references. Also per name. */\n readonly refsInFiles: number;\n /** References the defining file makes to names defined in *other* files. */\n readonly refsOut: number;\n}\n\n/**\n * Rank every definition in the tag set.\n *\n * Only cross-file references move rank — a file citing its own definitions does not\n * boost itself — but `refsIn`/`refsInFiles` count every reference, so the explanation\n * a symbol carries never hides same-file usage.\n *\n * Returns all definitions (rank `0` included), sorted by rank descending with the\n * stable tie-break: path ascending, then name ascending, then line ascending.\n */\nexport function rankDefinitions(files: readonly FileTagsEntry[]): readonly RankedDefinition[] {\n const sorted = [...files].toSorted((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n const paths = sorted.map((file) => file.path);\n\n // Which files define each identifier, and every definition, in deterministic order.\n interface MutableDef {\n readonly path: string;\n readonly name: string;\n readonly kind: string;\n readonly line: number;\n rank: number;\n }\n const defsByName = new Map<string, MutableDef[]>();\n const allDefs: MutableDef[] = [];\n for (const { path, tags } of sorted) {\n for (const def of tags.defs) {\n const mutable: MutableDef = { path, name: def.name, kind: def.kind, line: def.line, rank: 0 };\n allDefs.push(mutable);\n const existing = defsByName.get(def.name);\n if (existing === undefined) defsByName.set(def.name, [mutable]);\n else existing.push(mutable);\n }\n }\n\n // Cross-file edges: referencing file → defining file, weighted by reference count.\n // outWeight is each file's total outgoing weight — the PageRank edge denominator,\n // which legitimately grows once per definer file so each edge keeps its full weight.\n // refsOutByFile is the *measured* count a Law 2 explanation may quote: it adds\n // `ref.count` exactly once per reference to a name some other file defines, no\n // matter how many files define it. edgeWeights aggregates per file pair for the\n // PageRank pass, while rank distribution below re-walks the refs so each\n // *definition* receives its own share.\n const outWeight = new Map<string, number>(paths.map((path) => [path, 0]));\n const refsOutByFile = new Map<string, number>(paths.map((path) => [path, 0]));\n const edgeWeights = new Map<string, Map<string, number>>(paths.map((path) => [path, new Map()]));\n const refsInTotal = new Map<string, number>();\n const refsInFiles = new Map<string, number>();\n for (const { path, tags } of sorted) {\n for (const ref of tags.refs) {\n const definers = defsByName.get(ref.name);\n if (definers === undefined) continue;\n refsInTotal.set(ref.name, (refsInTotal.get(ref.name) ?? 0) + ref.count);\n refsInFiles.set(ref.name, (refsInFiles.get(ref.name) ?? 0) + 1);\n const targets = new Set(definers.map((def) => def.path));\n let crossFile = false;\n for (const target of [...targets].toSorted()) {\n if (target === path) continue;\n crossFile = true;\n outWeight.set(path, (outWeight.get(path) ?? 0) + ref.count);\n const row = edgeWeights.get(path)!;\n row.set(target, (row.get(target) ?? 0) + ref.count);\n }\n if (crossFile) refsOutByFile.set(path, (refsOutByFile.get(path) ?? 0) + ref.count);\n }\n }\n\n // PageRank, fixed iterations. Dangling files (no outgoing weight) spread their rank\n // uniformly, the standard treatment; every sum runs in sorted-path order so the\n // floating-point result is identical on every run.\n const nodeCount = paths.length;\n let rank = new Map<string, number>(\n paths.map((path) => [path, nodeCount === 0 ? 0 : 1 / nodeCount]),\n );\n for (let iteration = 0; iteration < PAGERANK_ITERATIONS; iteration += 1) {\n let danglingSum = 0;\n for (const path of paths) {\n if (outWeight.get(path) === 0) danglingSum += rank.get(path)!;\n }\n const next = new Map<string, number>();\n const base =\n nodeCount === 0\n ? 0\n : (1 - PAGERANK_DAMPING) / nodeCount + (PAGERANK_DAMPING * danglingSum) / nodeCount;\n for (const path of paths) next.set(path, base);\n for (const source of paths) {\n const total = outWeight.get(source)!;\n if (total === 0) continue;\n const share = (PAGERANK_DAMPING * rank.get(source)!) / total;\n for (const [target, weight] of edgeWeights.get(source)!) {\n next.set(target, next.get(target)! + share * weight);\n }\n }\n rank = next;\n }\n\n // Distribute each file's rank across the definitions that attracted it, exactly the\n // way Aider does: every cross-file reference edge hands the referencing file's rank,\n // scaled by the edge's share of that file's outgoing weight, to the definition it\n // points at.\n for (const { path, tags } of sorted) {\n const total = outWeight.get(path)!;\n if (total === 0) continue;\n const sourceRank = rank.get(path)!;\n for (const ref of tags.refs) {\n const definers = defsByName.get(ref.name);\n if (definers === undefined) continue;\n for (const def of definers) {\n if (def.path === path) continue;\n def.rank += (sourceRank * ref.count) / total;\n }\n }\n }\n\n const ranked: RankedDefinition[] = allDefs.map((def) => ({\n path: def.path,\n name: def.name,\n kind: def.kind,\n line: def.line,\n rank: def.rank,\n refsIn: refsInTotal.get(def.name) ?? 0,\n refsInFiles: refsInFiles.get(def.name) ?? 0,\n refsOut: refsOutByFile.get(def.path) ?? 0,\n }));\n return ranked.toSorted(compareRanked);\n}\n\n/**\n * The total order every emitted map obeys: rank descending, then the stable\n * tie-break by path, name, and line. Total, so equal-rank symbols land in the same\n * place on every run and on every machine — remove any leg of it and the map's\n * byte-for-byte determinism claim quietly dies.\n */\nfunction compareRanked(a: RankedDefinition, b: RankedDefinition): number {\n if (a.rank !== b.rank) return a.rank > b.rank ? -1 : 1;\n if (a.path !== b.path) return a.path < b.path ? -1 : 1;\n if (a.name !== b.name) return a.name < b.name ? -1 : 1;\n return a.line - b.line;\n}\n"]}
@@ -12,6 +12,26 @@
12
12
  * fixed damping, a fixed iteration count, nodes visited in sorted-path order, and a
13
13
  * total tie-break (rank, then path, then name, then line). No `Math.random`, no
14
14
  * `Date`, no map-iteration order leaking into the output.
15
+ *
16
+ * **The resolution limit, stated plainly: references bind by bare identifier.** A
17
+ * reference tag is a name and a count; there is no import graph, no scope, no
18
+ * declaration lookup. So `defsByName` below maps one identifier to *every* definition
19
+ * of it in the tree, and each reference to that identifier reaches all of them:
20
+ *
21
+ * - Same-name symbols in different files share rank and share `refsIn`. Two `run`s
22
+ * are, to this ranker, one name that several files happen to define.
23
+ * - Overloads and re-exports double-count: a name defined twice attracts the whole
24
+ * traffic to it twice, so it takes two lines near the top of the map.
25
+ * - A common identifier (`get`, `render`, `main`) collects references that in truth
26
+ * belong to something else entirely.
27
+ *
28
+ * This is Aider's design, inherited on purpose. The alternative is per-language import
29
+ * and scope resolution — a type checker per language — and the map is a *ranking
30
+ * heuristic* for what to read first, not a symbol resolver; something needing true
31
+ * binding (rename, call graph, dead code) needs a different tool. What matters under
32
+ * Law 4 is that nothing here claims otherwise: `refsIn` and `refsInFiles` are honestly
33
+ * the references to and the files mentioning the **name**, and each entry's receipt
34
+ * says so, so the numbers can be read for exactly what they measure.
15
35
  */
16
36
  /** The damping factor. 0.85 is the classic PageRank constant; fixed, never sampled. */
17
37
  export const PAGERANK_DAMPING = 0.85;
@@ -1 +1 @@
1
- {"version":3,"file":"rank.js","sourceRoot":"","sources":["../../src/repomap/rank.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rank.js","sourceRoot":"","sources":["../../src/repomap/rank.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FileTags } from './tags.ts';\n\n/**\n * Deterministic PageRank over the cross-file reference graph.\n *\n * Modelled on Aider's repo-map ranking (https://aider.chat/docs/repomap.html; see\n * `aider/repomap.py` in https://github.com/Aider-AI/aider): files are the graph's\n * nodes, and a file that references an identifier defined in another file contributes\n * a weighted edge toward the definer. A file's rank is then distributed across its\n * definitions in proportion to the reference weight each one attracted. The idea is\n * Aider's; only the implementation is this repository's.\n *\n * Deterministic by construction, which is what makes the map snapshot-testable:\n * fixed damping, a fixed iteration count, nodes visited in sorted-path order, and a\n * total tie-break (rank, then path, then name, then line). No `Math.random`, no\n * `Date`, no map-iteration order leaking into the output.\n *\n * **The resolution limit, stated plainly: references bind by bare identifier.** A\n * reference tag is a name and a count; there is no import graph, no scope, no\n * declaration lookup. So `defsByName` below maps one identifier to *every* definition\n * of it in the tree, and each reference to that identifier reaches all of them:\n *\n * - Same-name symbols in different files share rank and share `refsIn`. Two `run`s\n * are, to this ranker, one name that several files happen to define.\n * - Overloads and re-exports double-count: a name defined twice attracts the whole\n * traffic to it twice, so it takes two lines near the top of the map.\n * - A common identifier (`get`, `render`, `main`) collects references that in truth\n * belong to something else entirely.\n *\n * This is Aider's design, inherited on purpose. The alternative is per-language import\n * and scope resolution — a type checker per language — and the map is a *ranking\n * heuristic* for what to read first, not a symbol resolver; something needing true\n * binding (rename, call graph, dead code) needs a different tool. What matters under\n * Law 4 is that nothing here claims otherwise: `refsIn` and `refsInFiles` are honestly\n * the references to and the files mentioning the **name**, and each entry's receipt\n * says so, so the numbers can be read for exactly what they measure.\n */\n\n/** The damping factor. 0.85 is the classic PageRank constant; fixed, never sampled. */\nexport const PAGERANK_DAMPING = 0.85;\n\n/** Fixed iteration count — no convergence test, so runtime and output never vary. */\nexport const PAGERANK_ITERATIONS = 50;\n\n/** What the caller hands the ranker: one file's path and its extracted tags. */\nexport interface FileTagsEntry {\n readonly path: string;\n readonly tags: FileTags;\n}\n\n/** One definition with its computed rank and the measured counts that explain it. */\nexport interface RankedDefinition {\n readonly path: string;\n readonly name: string;\n readonly kind: string;\n readonly line: number;\n /** Accumulated PageRank share. `0` when no other file references this name. */\n readonly rank: number;\n /**\n * Total references to this **name** across every scanned file (its own included).\n * Per name, not per symbol: see the module comment — every definition of a name\n * shares the count, because references bind by bare identifier.\n */\n readonly refsIn: number;\n /** Distinct files holding at least one of those references. Also per name. */\n readonly refsInFiles: number;\n /** References the defining file makes to names defined in *other* files. */\n readonly refsOut: number;\n}\n\n/**\n * Rank every definition in the tag set.\n *\n * Only cross-file references move rank — a file citing its own definitions does not\n * boost itself — but `refsIn`/`refsInFiles` count every reference, so the explanation\n * a symbol carries never hides same-file usage.\n *\n * Returns all definitions (rank `0` included), sorted by rank descending with the\n * stable tie-break: path ascending, then name ascending, then line ascending.\n */\nexport function rankDefinitions(files: readonly FileTagsEntry[]): readonly RankedDefinition[] {\n const sorted = [...files].toSorted((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n const paths = sorted.map((file) => file.path);\n\n // Which files define each identifier, and every definition, in deterministic order.\n interface MutableDef {\n readonly path: string;\n readonly name: string;\n readonly kind: string;\n readonly line: number;\n rank: number;\n }\n const defsByName = new Map<string, MutableDef[]>();\n const allDefs: MutableDef[] = [];\n for (const { path, tags } of sorted) {\n for (const def of tags.defs) {\n const mutable: MutableDef = { path, name: def.name, kind: def.kind, line: def.line, rank: 0 };\n allDefs.push(mutable);\n const existing = defsByName.get(def.name);\n if (existing === undefined) defsByName.set(def.name, [mutable]);\n else existing.push(mutable);\n }\n }\n\n // Cross-file edges: referencing file → defining file, weighted by reference count.\n // outWeight is each file's total outgoing weight — the PageRank edge denominator,\n // which legitimately grows once per definer file so each edge keeps its full weight.\n // refsOutByFile is the *measured* count a Law 2 explanation may quote: it adds\n // `ref.count` exactly once per reference to a name some other file defines, no\n // matter how many files define it. edgeWeights aggregates per file pair for the\n // PageRank pass, while rank distribution below re-walks the refs so each\n // *definition* receives its own share.\n const outWeight = new Map<string, number>(paths.map((path) => [path, 0]));\n const refsOutByFile = new Map<string, number>(paths.map((path) => [path, 0]));\n const edgeWeights = new Map<string, Map<string, number>>(paths.map((path) => [path, new Map()]));\n const refsInTotal = new Map<string, number>();\n const refsInFiles = new Map<string, number>();\n for (const { path, tags } of sorted) {\n for (const ref of tags.refs) {\n const definers = defsByName.get(ref.name);\n if (definers === undefined) continue;\n refsInTotal.set(ref.name, (refsInTotal.get(ref.name) ?? 0) + ref.count);\n refsInFiles.set(ref.name, (refsInFiles.get(ref.name) ?? 0) + 1);\n const targets = new Set(definers.map((def) => def.path));\n let crossFile = false;\n for (const target of [...targets].toSorted()) {\n if (target === path) continue;\n crossFile = true;\n outWeight.set(path, (outWeight.get(path) ?? 0) + ref.count);\n const row = edgeWeights.get(path)!;\n row.set(target, (row.get(target) ?? 0) + ref.count);\n }\n if (crossFile) refsOutByFile.set(path, (refsOutByFile.get(path) ?? 0) + ref.count);\n }\n }\n\n // PageRank, fixed iterations. Dangling files (no outgoing weight) spread their rank\n // uniformly, the standard treatment; every sum runs in sorted-path order so the\n // floating-point result is identical on every run.\n const nodeCount = paths.length;\n let rank = new Map<string, number>(\n paths.map((path) => [path, nodeCount === 0 ? 0 : 1 / nodeCount]),\n );\n for (let iteration = 0; iteration < PAGERANK_ITERATIONS; iteration += 1) {\n let danglingSum = 0;\n for (const path of paths) {\n if (outWeight.get(path) === 0) danglingSum += rank.get(path)!;\n }\n const next = new Map<string, number>();\n const base =\n nodeCount === 0\n ? 0\n : (1 - PAGERANK_DAMPING) / nodeCount + (PAGERANK_DAMPING * danglingSum) / nodeCount;\n for (const path of paths) next.set(path, base);\n for (const source of paths) {\n const total = outWeight.get(source)!;\n if (total === 0) continue;\n const share = (PAGERANK_DAMPING * rank.get(source)!) / total;\n for (const [target, weight] of edgeWeights.get(source)!) {\n next.set(target, next.get(target)! + share * weight);\n }\n }\n rank = next;\n }\n\n // Distribute each file's rank across the definitions that attracted it, exactly the\n // way Aider does: every cross-file reference edge hands the referencing file's rank,\n // scaled by the edge's share of that file's outgoing weight, to the definition it\n // points at.\n for (const { path, tags } of sorted) {\n const total = outWeight.get(path)!;\n if (total === 0) continue;\n const sourceRank = rank.get(path)!;\n for (const ref of tags.refs) {\n const definers = defsByName.get(ref.name);\n if (definers === undefined) continue;\n for (const def of definers) {\n if (def.path === path) continue;\n def.rank += (sourceRank * ref.count) / total;\n }\n }\n }\n\n const ranked: RankedDefinition[] = allDefs.map((def) => ({\n path: def.path,\n name: def.name,\n kind: def.kind,\n line: def.line,\n rank: def.rank,\n refsIn: refsInTotal.get(def.name) ?? 0,\n refsInFiles: refsInFiles.get(def.name) ?? 0,\n refsOut: refsOutByFile.get(def.path) ?? 0,\n }));\n return ranked.toSorted(compareRanked);\n}\n\n/**\n * The total order every emitted map obeys: rank descending, then the stable\n * tie-break by path, name, and line. Total, so equal-rank symbols land in the same\n * place on every run and on every machine — remove any leg of it and the map's\n * byte-for-byte determinism claim quietly dies.\n */\nfunction compareRanked(a: RankedDefinition, b: RankedDefinition): number {\n if (a.rank !== b.rank) return a.rank > b.rank ? -1 : 1;\n if (a.path !== b.path) return a.path < b.path ? -1 : 1;\n if (a.name !== b.name) return a.name < b.name ? -1 : 1;\n return a.line - b.line;\n}\n"]}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The repo map's view of the filesystem — the seam `buildRepoMap` reads a tree
3
+ * through.
4
+ *
5
+ * The map used to call `node:fs` in-line, so every claim about the *walk* — symlinks
6
+ * refused, binary files skipped, nothing read that the ignore list excluded — could
7
+ * only be checked by materialising a real tree on disk and inspecting the map that
8
+ * came back. That makes the cheap cases expensive to write and the awkward ones
9
+ * (an unreadable file, a symlink whose stat resolves its target) impossible to
10
+ * express portably. `decide(request, settings, cwd, statFile?)` in
11
+ * `src/hooks/guard-core.ts` is the counter-example this interface copies: one
12
+ * injectable stat turned seven shims into table-driven tests over a four-line stub.
13
+ *
14
+ * Three methods, all read-only, and that is the point: this is smelt's whole door to
15
+ * the tree it maps, and **there is no writer on it**. The only bytes `buildRepoMap`
16
+ * ever writes are the tags cache, which exists only when the caller hands in a
17
+ * `cacheDir` (Law 1's sibling ruling: smelt never writes outside a store or cache it
18
+ * was explicitly given).
19
+ *
20
+ * The interface is deliberately small and deliberately optional —
21
+ * {@link RepoMapOptions.reader} defaults to {@link nodeFsReader}, so the map's
22
+ * interface is unchanged for every caller that does not care.
23
+ */
24
+ export interface RepoReader {
25
+ /** One directory's entries, in whatever order the backing store returns them. */
26
+ list(dir: string): readonly DirEntry[];
27
+ /**
28
+ * One file's bytes. Throws what the backing store throws; nothing is swallowed.
29
+ *
30
+ * `Uint8Array`, not `Buffer`, and for the same reason `AnswerStream` is not
31
+ * `NodeJS.ReadableStream`: `Buffer` is a global only a compilation that included
32
+ * `@types/node` has, so naming it here put an error into the shipped `.d.ts` for
33
+ * every consumer who builds with `skipLibCheck: false` and no node types of their
34
+ * own. A `Buffer` *is* a `Uint8Array`, so `nodeFsReader` and every reader that
35
+ * returns `readFileSync(path)` satisfies this unchanged.
36
+ */
37
+ read(path: string): Uint8Array;
38
+ /**
39
+ * What `path` is, **without following it**: a symlink reports `isSymlink`, and a
40
+ * reader whose backing store has nothing at `path` reports `undefined`.
41
+ */
42
+ stat(path: string): FileStat | undefined;
43
+ }
44
+ /** One entry of a directory listing. Only the name — {@link RepoReader.stat} says what it is. */
45
+ export interface DirEntry {
46
+ readonly name: string;
47
+ }
48
+ /**
49
+ * What the reader knows about one path.
50
+ *
51
+ * `isDirectory` sits beside `isFile` rather than being inferred from it, because the
52
+ * walk must tell a directory from a fifo, a socket or a device node: "not a file"
53
+ * would send the walk into one, where the map has always simply skipped it.
54
+ */
55
+ export interface FileStat {
56
+ /** The file's size in bytes, as the reader sees it. */
57
+ readonly size: number;
58
+ readonly isFile: boolean;
59
+ readonly isDirectory: boolean;
60
+ /** True for the link itself. A reader must never resolve a link to answer this. */
61
+ readonly isSymlink: boolean;
62
+ }
63
+ /**
64
+ * The default reader: `node:fs`, exactly the calls `buildRepoMap` used to make
65
+ * in-line — `readdirSync`, `lstatSync` (never `statSync`: the link, never its
66
+ * target), `readFileSync`.
67
+ *
68
+ * It never returns `undefined` from `stat`: an entry that vanished between the
69
+ * listing and the stat throws, as `lstatSync` has always done here. `undefined` is
70
+ * for readers whose backing store can honestly say "nothing there".
71
+ */
72
+ export declare function nodeFsReader(): RepoReader;
73
+ //# sourceMappingURL=reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../../src/repomap/reader.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,UAAU;IACzB,iFAAiF;IACjF,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,QAAQ,EAAE,CAAC;IACvC;;;;;;;;;OASG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IAC/B;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;CAC1C;AAED,iGAAiG;AACjG,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,uDAAuD;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,mFAAmF;IACnF,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,IAAI,UAAU,CAkBzC","sourcesContent":["import { lstatSync, readdirSync, readFileSync } from 'node:fs';\n\n/**\n * The repo map's view of the filesystem — the seam `buildRepoMap` reads a tree\n * through.\n *\n * The map used to call `node:fs` in-line, so every claim about the *walk* — symlinks\n * refused, binary files skipped, nothing read that the ignore list excluded — could\n * only be checked by materialising a real tree on disk and inspecting the map that\n * came back. That makes the cheap cases expensive to write and the awkward ones\n * (an unreadable file, a symlink whose stat resolves its target) impossible to\n * express portably. `decide(request, settings, cwd, statFile?)` in\n * `src/hooks/guard-core.ts` is the counter-example this interface copies: one\n * injectable stat turned seven shims into table-driven tests over a four-line stub.\n *\n * Three methods, all read-only, and that is the point: this is smelt's whole door to\n * the tree it maps, and **there is no writer on it**. The only bytes `buildRepoMap`\n * ever writes are the tags cache, which exists only when the caller hands in a\n * `cacheDir` (Law 1's sibling ruling: smelt never writes outside a store or cache it\n * was explicitly given).\n *\n * The interface is deliberately small and deliberately optional —\n * {@link RepoMapOptions.reader} defaults to {@link nodeFsReader}, so the map's\n * interface is unchanged for every caller that does not care.\n */\nexport interface RepoReader {\n /** One directory's entries, in whatever order the backing store returns them. */\n list(dir: string): readonly DirEntry[];\n /**\n * One file's bytes. Throws what the backing store throws; nothing is swallowed.\n *\n * `Uint8Array`, not `Buffer`, and for the same reason `AnswerStream` is not\n * `NodeJS.ReadableStream`: `Buffer` is a global only a compilation that included\n * `@types/node` has, so naming it here put an error into the shipped `.d.ts` for\n * every consumer who builds with `skipLibCheck: false` and no node types of their\n * own. A `Buffer` *is* a `Uint8Array`, so `nodeFsReader` and every reader that\n * returns `readFileSync(path)` satisfies this unchanged.\n */\n read(path: string): Uint8Array;\n /**\n * What `path` is, **without following it**: a symlink reports `isSymlink`, and a\n * reader whose backing store has nothing at `path` reports `undefined`.\n */\n stat(path: string): FileStat | undefined;\n}\n\n/** One entry of a directory listing. Only the name — {@link RepoReader.stat} says what it is. */\nexport interface DirEntry {\n readonly name: string;\n}\n\n/**\n * What the reader knows about one path.\n *\n * `isDirectory` sits beside `isFile` rather than being inferred from it, because the\n * walk must tell a directory from a fifo, a socket or a device node: \"not a file\"\n * would send the walk into one, where the map has always simply skipped it.\n */\nexport interface FileStat {\n /** The file's size in bytes, as the reader sees it. */\n readonly size: number;\n readonly isFile: boolean;\n readonly isDirectory: boolean;\n /** True for the link itself. A reader must never resolve a link to answer this. */\n readonly isSymlink: boolean;\n}\n\n/**\n * The default reader: `node:fs`, exactly the calls `buildRepoMap` used to make\n * in-line — `readdirSync`, `lstatSync` (never `statSync`: the link, never its\n * target), `readFileSync`.\n *\n * It never returns `undefined` from `stat`: an entry that vanished between the\n * listing and the stat throws, as `lstatSync` has always done here. `undefined` is\n * for readers whose backing store can honestly say \"nothing there\".\n */\nexport function nodeFsReader(): RepoReader {\n return {\n list(dir) {\n return readdirSync(dir).map((name) => ({ name }));\n },\n read(path) {\n return readFileSync(path);\n },\n stat(path) {\n const stat = lstatSync(path);\n return {\n size: stat.size,\n isFile: stat.isFile(),\n isDirectory: stat.isDirectory(),\n isSymlink: stat.isSymbolicLink(),\n };\n },\n };\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import { lstatSync, readdirSync, readFileSync } from 'node:fs';
2
+ /**
3
+ * The default reader: `node:fs`, exactly the calls `buildRepoMap` used to make
4
+ * in-line — `readdirSync`, `lstatSync` (never `statSync`: the link, never its
5
+ * target), `readFileSync`.
6
+ *
7
+ * It never returns `undefined` from `stat`: an entry that vanished between the
8
+ * listing and the stat throws, as `lstatSync` has always done here. `undefined` is
9
+ * for readers whose backing store can honestly say "nothing there".
10
+ */
11
+ export function nodeFsReader() {
12
+ return {
13
+ list(dir) {
14
+ return readdirSync(dir).map((name) => ({ name }));
15
+ },
16
+ read(path) {
17
+ return readFileSync(path);
18
+ },
19
+ stat(path) {
20
+ const stat = lstatSync(path);
21
+ return {
22
+ size: stat.size,
23
+ isFile: stat.isFile(),
24
+ isDirectory: stat.isDirectory(),
25
+ isSymlink: stat.isSymbolicLink(),
26
+ };
27
+ },
28
+ };
29
+ }
30
+ //# sourceMappingURL=reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader.js","sourceRoot":"","sources":["../../src/repomap/reader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAmE/D;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO;QACL,IAAI,CAAC,GAAG;YACN,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACpD,CAAC;QACD,IAAI,CAAC,IAAI;YACP,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;QACD,IAAI,CAAC,IAAI;YACP,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;YAC7B,OAAO;gBACL,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;gBACrB,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE;gBAC/B,SAAS,EAAE,IAAI,CAAC,cAAc,EAAE;aACjC,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { lstatSync, readdirSync, readFileSync } from 'node:fs';\n\n/**\n * The repo map's view of the filesystem — the seam `buildRepoMap` reads a tree\n * through.\n *\n * The map used to call `node:fs` in-line, so every claim about the *walk* — symlinks\n * refused, binary files skipped, nothing read that the ignore list excluded — could\n * only be checked by materialising a real tree on disk and inspecting the map that\n * came back. That makes the cheap cases expensive to write and the awkward ones\n * (an unreadable file, a symlink whose stat resolves its target) impossible to\n * express portably. `decide(request, settings, cwd, statFile?)` in\n * `src/hooks/guard-core.ts` is the counter-example this interface copies: one\n * injectable stat turned seven shims into table-driven tests over a four-line stub.\n *\n * Three methods, all read-only, and that is the point: this is smelt's whole door to\n * the tree it maps, and **there is no writer on it**. The only bytes `buildRepoMap`\n * ever writes are the tags cache, which exists only when the caller hands in a\n * `cacheDir` (Law 1's sibling ruling: smelt never writes outside a store or cache it\n * was explicitly given).\n *\n * The interface is deliberately small and deliberately optional —\n * {@link RepoMapOptions.reader} defaults to {@link nodeFsReader}, so the map's\n * interface is unchanged for every caller that does not care.\n */\nexport interface RepoReader {\n /** One directory's entries, in whatever order the backing store returns them. */\n list(dir: string): readonly DirEntry[];\n /**\n * One file's bytes. Throws what the backing store throws; nothing is swallowed.\n *\n * `Uint8Array`, not `Buffer`, and for the same reason `AnswerStream` is not\n * `NodeJS.ReadableStream`: `Buffer` is a global only a compilation that included\n * `@types/node` has, so naming it here put an error into the shipped `.d.ts` for\n * every consumer who builds with `skipLibCheck: false` and no node types of their\n * own. A `Buffer` *is* a `Uint8Array`, so `nodeFsReader` and every reader that\n * returns `readFileSync(path)` satisfies this unchanged.\n */\n read(path: string): Uint8Array;\n /**\n * What `path` is, **without following it**: a symlink reports `isSymlink`, and a\n * reader whose backing store has nothing at `path` reports `undefined`.\n */\n stat(path: string): FileStat | undefined;\n}\n\n/** One entry of a directory listing. Only the name — {@link RepoReader.stat} says what it is. */\nexport interface DirEntry {\n readonly name: string;\n}\n\n/**\n * What the reader knows about one path.\n *\n * `isDirectory` sits beside `isFile` rather than being inferred from it, because the\n * walk must tell a directory from a fifo, a socket or a device node: \"not a file\"\n * would send the walk into one, where the map has always simply skipped it.\n */\nexport interface FileStat {\n /** The file's size in bytes, as the reader sees it. */\n readonly size: number;\n readonly isFile: boolean;\n readonly isDirectory: boolean;\n /** True for the link itself. A reader must never resolve a link to answer this. */\n readonly isSymlink: boolean;\n}\n\n/**\n * The default reader: `node:fs`, exactly the calls `buildRepoMap` used to make\n * in-line — `readdirSync`, `lstatSync` (never `statSync`: the link, never its\n * target), `readFileSync`.\n *\n * It never returns `undefined` from `stat`: an entry that vanished between the\n * listing and the stat throws, as `lstatSync` has always done here. `undefined` is\n * for readers whose backing store can honestly say \"nothing there\".\n */\nexport function nodeFsReader(): RepoReader {\n return {\n list(dir) {\n return readdirSync(dir).map((name) => ({ name }));\n },\n read(path) {\n return readFileSync(path);\n },\n stat(path) {\n const stat = lstatSync(path);\n return {\n size: stat.size,\n isFile: stat.isFile(),\n isDirectory: stat.isDirectory(),\n isSymlink: stat.isSymbolicLink(),\n };\n },\n };\n}\n"]}