clast-ai 0.1.2 → 0.2.1

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 (294) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/.claude-plugin/plugin.json +11 -9
  3. package/README.md +468 -398
  4. package/bench/ab/onair-concept.json +22 -0
  5. package/bench/ab/onair-deadcode.json +7 -0
  6. package/bench/ab/onair-hard.json +32 -0
  7. package/bench/ab/onair-impact.json +17 -0
  8. package/bench/ab/onair-refactors.json +18 -0
  9. package/bench/ab/onair-survey.json +17 -0
  10. package/bench/ab/onair-tasks.json +20 -0
  11. package/bench/ab/onair.json +52 -0
  12. package/bench/ab/vscode-concept.json +22 -0
  13. package/bench/ab/vscode-hard.json +32 -0
  14. package/bench/ab/vscode-impact.json +17 -0
  15. package/bench/ab/vscode-refactors.json +18 -0
  16. package/bench/ab/vscode-tasks.json +20 -0
  17. package/bench/ab/vscode.json +52 -0
  18. package/bench/ab.mjs +213 -0
  19. package/bench/bench.mjs +98 -0
  20. package/bench/resolution.mjs +75 -0
  21. package/dist/cli.d.ts +2 -0
  22. package/dist/cli.js +266 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/config/defaults.d.ts +12 -2
  25. package/dist/config/defaults.js +38 -31
  26. package/dist/config/defaults.js.map +1 -1
  27. package/dist/config/index.d.ts +1 -1
  28. package/dist/config/index.js +26 -14
  29. package/dist/config/index.js.map +1 -1
  30. package/dist/db/index.d.ts +73 -13
  31. package/dist/db/index.js +481 -39
  32. package/dist/db/index.js.map +1 -1
  33. package/dist/engine.d.ts +15 -0
  34. package/dist/engine.js +28 -0
  35. package/dist/engine.js.map +1 -0
  36. package/dist/indexer/imports.d.ts +6 -0
  37. package/dist/indexer/imports.js +254 -0
  38. package/dist/indexer/imports.js.map +1 -0
  39. package/dist/indexer/index.d.ts +63 -0
  40. package/dist/indexer/index.js +360 -0
  41. package/dist/indexer/index.js.map +1 -0
  42. package/dist/indexer/process.d.ts +22 -0
  43. package/dist/indexer/process.js +42 -0
  44. package/dist/indexer/process.js.map +1 -0
  45. package/dist/indexer/scan.d.ts +43 -0
  46. package/dist/indexer/scan.js +223 -0
  47. package/dist/indexer/scan.js.map +1 -0
  48. package/dist/indexer/worker.d.ts +1 -0
  49. package/dist/indexer/worker.js +23 -0
  50. package/dist/indexer/worker.js.map +1 -0
  51. package/dist/llm/comment-generator.d.ts +17 -19
  52. package/dist/llm/comment-generator.js +46 -80
  53. package/dist/llm/comment-generator.js.map +1 -1
  54. package/dist/llm/index.d.ts +0 -1
  55. package/dist/llm/index.js.map +1 -1
  56. package/dist/llm/prompts.d.ts +0 -1
  57. package/dist/llm/prompts.js +16 -16
  58. package/dist/migrate.d.ts +35 -0
  59. package/dist/migrate.js +194 -0
  60. package/dist/migrate.js.map +1 -0
  61. package/dist/parser/extractor.d.ts +21 -16
  62. package/dist/parser/extractor.js +700 -228
  63. package/dist/parser/extractor.js.map +1 -1
  64. package/dist/parser/index.d.ts +17 -21
  65. package/dist/parser/index.js +115 -171
  66. package/dist/parser/index.js.map +1 -1
  67. package/dist/parser/langs/c.d.ts +3 -0
  68. package/dist/parser/langs/c.js +879 -0
  69. package/dist/parser/langs/c.js.map +1 -0
  70. package/dist/parser/langs/csharp.d.ts +2 -0
  71. package/dist/parser/langs/csharp.js +548 -0
  72. package/dist/parser/langs/csharp.js.map +1 -0
  73. package/dist/parser/langs/dart.d.ts +2 -0
  74. package/dist/parser/langs/dart.js +893 -0
  75. package/dist/parser/langs/dart.js.map +1 -0
  76. package/dist/parser/langs/go.d.ts +2 -0
  77. package/dist/parser/langs/go.js +455 -0
  78. package/dist/parser/langs/go.js.map +1 -0
  79. package/dist/parser/langs/java.d.ts +2 -0
  80. package/dist/parser/langs/java.js +390 -0
  81. package/dist/parser/langs/java.js.map +1 -0
  82. package/dist/parser/langs/php.d.ts +2 -0
  83. package/dist/parser/langs/php.js +486 -0
  84. package/dist/parser/langs/php.js.map +1 -0
  85. package/dist/parser/langs/python.d.ts +2 -0
  86. package/dist/parser/langs/python.js +216 -0
  87. package/dist/parser/langs/python.js.map +1 -0
  88. package/dist/parser/langs/ruby.d.ts +2 -0
  89. package/dist/parser/langs/ruby.js +602 -0
  90. package/dist/parser/langs/ruby.js.map +1 -0
  91. package/dist/parser/langs/rust.d.ts +2 -0
  92. package/dist/parser/langs/rust.js +931 -0
  93. package/dist/parser/langs/rust.js.map +1 -0
  94. package/dist/parser/langs/swift.d.ts +2 -0
  95. package/dist/parser/langs/swift.js +616 -0
  96. package/dist/parser/langs/swift.js.map +1 -0
  97. package/dist/parser/langs/typescript.d.ts +4 -0
  98. package/dist/parser/langs/typescript.js +535 -0
  99. package/dist/parser/langs/typescript.js.map +1 -0
  100. package/dist/parser/registry.d.ts +16 -0
  101. package/dist/parser/registry.js +87 -0
  102. package/dist/parser/registry.js.map +1 -0
  103. package/dist/parser/returns.d.ts +9 -0
  104. package/dist/parser/returns.js +152 -0
  105. package/dist/parser/returns.js.map +1 -0
  106. package/dist/parser/spec.d.ts +165 -0
  107. package/dist/parser/spec.js +188 -0
  108. package/dist/parser/spec.js.map +1 -0
  109. package/dist/query/arity.d.ts +27 -0
  110. package/dist/query/arity.js +187 -0
  111. package/dist/query/arity.js.map +1 -0
  112. package/dist/query/callgraph.d.ts +113 -0
  113. package/dist/query/callgraph.js +332 -0
  114. package/dist/query/callgraph.js.map +1 -0
  115. package/dist/query/format.d.ts +36 -0
  116. package/dist/query/format.js +136 -0
  117. package/dist/query/format.js.map +1 -0
  118. package/dist/query/hierarchy.d.ts +44 -0
  119. package/dist/query/hierarchy.js +441 -0
  120. package/dist/query/hierarchy.js.map +1 -0
  121. package/dist/query/resolve.d.ts +265 -0
  122. package/dist/query/resolve.js +1957 -0
  123. package/dist/query/resolve.js.map +1 -0
  124. package/dist/query/search.d.ts +35 -0
  125. package/dist/query/search.js +379 -0
  126. package/dist/query/search.js.map +1 -0
  127. package/dist/query/words.d.ts +30 -0
  128. package/dist/query/words.js +201 -0
  129. package/dist/query/words.js.map +1 -0
  130. package/dist/server/index.d.ts +1 -3
  131. package/dist/server/index.js +313 -73
  132. package/dist/server/index.js.map +1 -1
  133. package/dist/tools/context.d.ts +35 -0
  134. package/dist/tools/context.js +79 -0
  135. package/dist/tools/context.js.map +1 -0
  136. package/dist/tools/explore.d.ts +51 -0
  137. package/dist/tools/explore.js +779 -0
  138. package/dist/tools/explore.js.map +1 -0
  139. package/dist/tools/impact.d.ts +21 -0
  140. package/dist/tools/impact.js +743 -0
  141. package/dist/tools/impact.js.map +1 -0
  142. package/dist/tools/search.d.ts +59 -0
  143. package/dist/tools/search.js +843 -0
  144. package/dist/tools/search.js.map +1 -0
  145. package/dist/tools/stats.d.ts +26 -0
  146. package/dist/tools/stats.js +604 -0
  147. package/dist/tools/stats.js.map +1 -0
  148. package/dist/types.d.ts +95 -40
  149. package/dist/types.js +9 -1
  150. package/dist/types.js.map +1 -1
  151. package/dist/version.d.ts +2 -0
  152. package/dist/version.js +3 -0
  153. package/dist/version.js.map +1 -0
  154. package/dist/watcher/index.d.ts +18 -16
  155. package/dist/watcher/index.js +94 -77
  156. package/dist/watcher/index.js.map +1 -1
  157. package/package.json +21 -24
  158. package/plugin/server.mjs +72 -0
  159. package/skills/audit/SKILL.md +37 -0
  160. package/skills/deadcode/SKILL.md +33 -0
  161. package/skills/explore/SKILL.md +54 -0
  162. package/skills/impact/SKILL.md +24 -0
  163. package/skills/status/SKILL.md +11 -0
  164. package/src/cli.ts +265 -0
  165. package/src/config/defaults.ts +50 -32
  166. package/src/config/index.ts +23 -14
  167. package/src/db/index.ts +540 -43
  168. package/src/engine.ts +30 -0
  169. package/src/indexer/imports.ts +241 -0
  170. package/src/indexer/index.ts +369 -0
  171. package/src/indexer/process.ts +63 -0
  172. package/src/indexer/scan.ts +226 -0
  173. package/src/indexer/worker.ts +24 -0
  174. package/src/llm/comment-generator.ts +52 -103
  175. package/src/llm/index.ts +80 -80
  176. package/src/llm/prompts.ts +95 -95
  177. package/src/migrate.ts +213 -0
  178. package/src/parser/extractor.ts +643 -256
  179. package/src/parser/index.ts +105 -205
  180. package/src/parser/langs/c.ts +860 -0
  181. package/src/parser/langs/csharp.ts +519 -0
  182. package/src/parser/langs/dart.ts +820 -0
  183. package/src/parser/langs/go.ts +437 -0
  184. package/src/parser/langs/java.ts +361 -0
  185. package/src/parser/langs/php.ts +450 -0
  186. package/src/parser/langs/python.ts +206 -0
  187. package/src/parser/langs/ruby.ts +554 -0
  188. package/src/parser/langs/rust.ts +904 -0
  189. package/src/parser/langs/swift.ts +590 -0
  190. package/src/parser/langs/typescript.ts +499 -0
  191. package/src/parser/registry.ts +88 -0
  192. package/src/parser/returns.ts +137 -0
  193. package/src/parser/spec.ts +309 -0
  194. package/src/query/arity.ts +173 -0
  195. package/src/query/callgraph.ts +473 -0
  196. package/src/query/format.ts +136 -0
  197. package/src/query/hierarchy.ts +447 -0
  198. package/src/query/resolve.ts +1972 -0
  199. package/src/query/search.ts +430 -0
  200. package/src/query/words.ts +183 -0
  201. package/src/server/index.ts +390 -79
  202. package/src/tools/context.ts +82 -0
  203. package/src/tools/explore.ts +860 -0
  204. package/src/tools/impact.ts +748 -0
  205. package/src/tools/search.ts +886 -0
  206. package/src/tools/stats.ts +633 -0
  207. package/src/types.ts +113 -45
  208. package/src/version.ts +2 -0
  209. package/src/watcher/index.ts +82 -88
  210. package/tsconfig.json +6 -3
  211. package/.mcp.json +0 -8
  212. package/commands/clast-status.md +0 -9
  213. package/dist/config/defaults.d.ts.map +0 -1
  214. package/dist/config/index.d.ts.map +0 -1
  215. package/dist/db/index.d.ts.map +0 -1
  216. package/dist/db/queries.d.ts +0 -44
  217. package/dist/db/queries.d.ts.map +0 -1
  218. package/dist/db/queries.js +0 -251
  219. package/dist/db/queries.js.map +0 -1
  220. package/dist/db/schema.d.ts +0 -3
  221. package/dist/db/schema.d.ts.map +0 -1
  222. package/dist/db/schema.js +0 -94
  223. package/dist/db/schema.js.map +0 -1
  224. package/dist/db/types.d.ts +0 -44
  225. package/dist/db/types.d.ts.map +0 -1
  226. package/dist/db/types.js +0 -2
  227. package/dist/db/types.js.map +0 -1
  228. package/dist/llm/comment-generator.d.ts.map +0 -1
  229. package/dist/llm/index.d.ts.map +0 -1
  230. package/dist/llm/prompts.d.ts.map +0 -1
  231. package/dist/parser/call-resolver.d.ts +0 -15
  232. package/dist/parser/call-resolver.d.ts.map +0 -1
  233. package/dist/parser/call-resolver.js +0 -16
  234. package/dist/parser/call-resolver.js.map +0 -1
  235. package/dist/parser/extractor.d.ts.map +0 -1
  236. package/dist/parser/index.d.ts.map +0 -1
  237. package/dist/parser/languages.d.ts +0 -11
  238. package/dist/parser/languages.d.ts.map +0 -1
  239. package/dist/parser/languages.js +0 -74
  240. package/dist/parser/languages.js.map +0 -1
  241. package/dist/server/index.d.ts.map +0 -1
  242. package/dist/server/register-tools.d.ts +0 -7
  243. package/dist/server/register-tools.d.ts.map +0 -1
  244. package/dist/server/register-tools.js +0 -17
  245. package/dist/server/register-tools.js.map +0 -1
  246. package/dist/server/tools/clast-call-graph.d.ts +0 -4
  247. package/dist/server/tools/clast-call-graph.d.ts.map +0 -1
  248. package/dist/server/tools/clast-call-graph.js +0 -70
  249. package/dist/server/tools/clast-call-graph.js.map +0 -1
  250. package/dist/server/tools/clast-comment.d.ts +0 -5
  251. package/dist/server/tools/clast-comment.d.ts.map +0 -1
  252. package/dist/server/tools/clast-comment.js +0 -64
  253. package/dist/server/tools/clast-comment.js.map +0 -1
  254. package/dist/server/tools/clast-file-summary.d.ts +0 -4
  255. package/dist/server/tools/clast-file-summary.d.ts.map +0 -1
  256. package/dist/server/tools/clast-file-summary.js +0 -42
  257. package/dist/server/tools/clast-file-summary.js.map +0 -1
  258. package/dist/server/tools/clast-get-context.d.ts +0 -4
  259. package/dist/server/tools/clast-get-context.d.ts.map +0 -1
  260. package/dist/server/tools/clast-get-context.js +0 -80
  261. package/dist/server/tools/clast-get-context.js.map +0 -1
  262. package/dist/server/tools/clast-reindex.d.ts +0 -5
  263. package/dist/server/tools/clast-reindex.d.ts.map +0 -1
  264. package/dist/server/tools/clast-reindex.js +0 -36
  265. package/dist/server/tools/clast-reindex.js.map +0 -1
  266. package/dist/server/tools/clast-search.d.ts +0 -4
  267. package/dist/server/tools/clast-search.d.ts.map +0 -1
  268. package/dist/server/tools/clast-search.js +0 -29
  269. package/dist/server/tools/clast-search.js.map +0 -1
  270. package/dist/server/tools/clast-status.d.ts +0 -5
  271. package/dist/server/tools/clast-status.d.ts.map +0 -1
  272. package/dist/server/tools/clast-status.js +0 -23
  273. package/dist/server/tools/clast-status.js.map +0 -1
  274. package/dist/types.d.ts.map +0 -1
  275. package/dist/watcher/gitignore.d.ts +0 -9
  276. package/dist/watcher/gitignore.d.ts.map +0 -1
  277. package/dist/watcher/gitignore.js +0 -38
  278. package/dist/watcher/gitignore.js.map +0 -1
  279. package/dist/watcher/index.d.ts.map +0 -1
  280. package/skills/clast-explore/SKILL.md +0 -41
  281. package/src/db/queries.ts +0 -332
  282. package/src/db/schema.ts +0 -106
  283. package/src/db/types.ts +0 -47
  284. package/src/parser/call-resolver.ts +0 -17
  285. package/src/parser/languages.ts +0 -86
  286. package/src/server/register-tools.ts +0 -30
  287. package/src/server/tools/clast-call-graph.ts +0 -82
  288. package/src/server/tools/clast-comment.ts +0 -76
  289. package/src/server/tools/clast-file-summary.ts +0 -59
  290. package/src/server/tools/clast-get-context.ts +0 -97
  291. package/src/server/tools/clast-reindex.ts +0 -52
  292. package/src/server/tools/clast-search.ts +0 -38
  293. package/src/server/tools/clast-status.ts +0 -37
  294. package/src/watcher/gitignore.ts +0 -42
@@ -0,0 +1,22 @@
1
+ [
2
+ {
3
+ "id": "dc-spike",
4
+ "q": "Before the samples reach the demodulators, something takes out the strong spike in the middle of the spectrum that many radios produce. Which function in the engine does that, and in which file?",
5
+ "expect": ["cleanSamples", "engine.cpp"]
6
+ },
7
+ {
8
+ "id": "adc-overdrive",
9
+ "q": "Which function decides whether the analog-to-digital converter is being driven too hard or too weakly, from the measured signal levels? Name the function and the file.",
10
+ "expect": ["classifyAdc", "gain.cpp"]
11
+ },
12
+ {
13
+ "id": "mute-gate",
14
+ "q": "Where is the logic that keeps the speaker quiet until a signal is strong enough, with different levels for opening and closing? Name the struct or class and the source file with its update logic.",
15
+ "expect": ["SquelchCore", "audio_control"]
16
+ },
17
+ {
18
+ "id": "background-level",
19
+ "q": "Which function works out how strong the background hiss of the band is, so that the automatic gain can be set? Name the function and its file.",
20
+ "expect": ["estimateFloor", "autogain_measure.cpp"]
21
+ }
22
+ ]
@@ -0,0 +1,7 @@
1
+ [
2
+ {
3
+ "id": "dead-code-area",
4
+ "q": "Find dead code under app/: name five functions defined there that nothing in the repository calls or references, each with its file and line. Only list functions you have verified are unused.",
5
+ "expect": []
6
+ }
7
+ ]
@@ -0,0 +1,32 @@
1
+ [
2
+ {
3
+ "id": "iqsource-transitive",
4
+ "q": "List every non-test class that is a direct or indirect subclass of IqSource (follow the inheritance chain to the leaves).",
5
+ "expect": ["BeamIqSource", "DiversitySource", "SyntheticWideSource", "UsbHackrf", "NativeSource", "AirspySource", "AirspyHfSource", "BladeSource", "FobosSource", "HpsdrSource", "HydraSource", "IcomSource", "KiwiSource", "LimeSource", "NrspStSource", "PlutoSource", "RfnmSource", "RtlSource", "RtlTcpSource", "Rx888Source", "SdrplaySource", "SdrppSource", "SpyServerSource", "TciSource", "UsrpSource", "AudioSource", "HamlibSource", "SoapySource", "HackrfSource", "HackrfAuto", "PacedSource", "FileSource", "SyntheticSource"]
6
+ },
7
+ {
8
+ "id": "classify-adc-core",
9
+ "q": "Inside core/ (not the app UI), which classes' methods call classifyAdc? Name the class and method for each.",
10
+ "expect": ["AutoGain", "GainOptimiser", "GainSweep"]
11
+ },
12
+ {
13
+ "id": "vfo-chain",
14
+ "q": "Give the full call chain by which the per-frame UI draw code ends up calling VfoBank::setChannels: every function from the draw loop down to setChannels, with files.",
15
+ "expect": ["floatingWindows", "draw_ui.cpp", "vfoSync", "vfo_ui.cpp", "setVfoChannels", "engine.h"]
16
+ },
17
+ {
18
+ "id": "generic-gain-chain",
19
+ "q": "Which functions call genericGain outside tests, and for the one in the app status bar, which functions call it in turn? Give function names and files.",
20
+ "expect": ["gainControl", "statusbar.cpp", "GainSweep", "AutoGain", "jni_onair.cpp", "specx_ui.cpp", "draw_ui.cpp"]
21
+ },
22
+ {
23
+ "id": "set-channels-other",
24
+ "q": "Besides Engine::setVfoChannels, which other code in core calls setChannels on a VfoBank? Give the file and the enclosing functions.",
25
+ "expect": ["wide_session.cpp", "clearState", "bankPlace"]
26
+ },
27
+ {
28
+ "id": "paced-sources",
29
+ "q": "Which classes derive from PacedSource, what does PacedSource itself derive from, and in which file are they all defined?",
30
+ "expect": ["FileSource", "SyntheticSource", "IqSource", "core/src/source/source.cpp"]
31
+ }
32
+ ]
@@ -0,0 +1,17 @@
1
+ [
2
+ {
3
+ "id": "tests-for-symbol",
4
+ "q": "I am about to change the behaviour of the function genericGain in core/src/source/gain.cpp. No test calls it by name. Which test files exercise it through the functions that call it? List the test files.",
5
+ "expect": ["test_gain.cpp", "test_agc.cpp"]
6
+ },
7
+ {
8
+ "id": "signature-change",
9
+ "q": "I want to add a parameter to the function genericGain (core/src/source/gain.cpp). List every file I have to edit so that the project still compiles, including the declaration.",
10
+ "expect": ["gain.h", "gain.cpp", "statusbar.cpp", "jni_onair.cpp"]
11
+ },
12
+ {
13
+ "id": "dead-code-file",
14
+ "q": "Which functions defined in core/src/uat/uat_decode.c are dead code, meaning nothing in the repository calls them, directly or only through other dead functions? Name the top-level functions that nothing calls at all.",
15
+ "expect": ["uat_display_adsb_mdb", "uat_display_uplink_mdb"]
16
+ }
17
+ ]
@@ -0,0 +1,18 @@
1
+ [
2
+ {
3
+ "id": "rename-generic-gain",
4
+ "task": true,
5
+ "q": "Rename the function genericGain to genericGainFor. Update its declaration, its definition and every caller in the whole repository (core, app, android, tests), so that nothing refers to the old name any more.",
6
+ "expect": ["genericGainFor", "core/include/dect2/source/gain.h", "core/src/source/gain.cpp", "android/native/src/jni_onair.cpp", "app/ui/statusbar.cpp"],
7
+ "mustNotRemain": "\\bgenericGain\\(",
8
+ "paths": ["core", "app", "android", "tests", "tools"]
9
+ },
10
+ {
11
+ "id": "adc-advice-param",
12
+ "task": true,
13
+ "q": "Add a required second parameter bool brief to adcAdvice. When brief is true it returns only the first sentence of the advice; when false the result is as it is now. Update the declaration, the definition and every caller in the repository; existing callers pass false.",
14
+ "expect": ["bool brief", "core/include/dect2/source/gain.h", "core/src/source/gain.cpp", "app/ui/statusbar.cpp", "app/dab/dab_ui.cpp", "app/fm/fm_ui.cpp", "app/atsc3/atsc3_ui.cpp", "app/dtmb/dtmb_ui.cpp", "app/isdbt/isdbt_ui.cpp"],
15
+ "mustNotRemain": "adcAdvice\\([A-Za-z_:]+\\)",
16
+ "paths": ["core", "app", "android", "tests", "tools"]
17
+ }
18
+ ]
@@ -0,0 +1,17 @@
1
+ [
2
+ {
3
+ "id": "mode-count",
4
+ "q": "How many receive modes does OnAir support according to its own mode list, and which source file is that list counted from? Give the number.",
5
+ "expect": ["333", "mode_table.cpp"]
6
+ },
7
+ {
8
+ "id": "receiver-count",
9
+ "q": "How many distinct receiver classes (a class or struct whose name ends in Receiver) are defined under core/? Give the number and five examples.",
10
+ "expect": ["199", "189"]
11
+ },
12
+ {
13
+ "id": "radio-drivers",
14
+ "q": "Which radio hardware has a native driver class in this code base (the classes built on the shared native source base)? List the driver classes and say how many there are.",
15
+ "expect": ["AirspySource", "BladeSource", "LimeSource", "PlutoSource", "RtlSource", "SdrplaySource", "UsrpSource", "Rx888Source", "20"]
16
+ }
17
+ ]
@@ -0,0 +1,20 @@
1
+ [
2
+ {
3
+ "id": "silence-source",
4
+ "task": true,
5
+ "q": "Add a new built-in IQ source called SilenceSource that delivers all-zero samples at the requested sample rate, paced in real time like the existing file and synthetic sources. Build it on the same base class those two use, put it next to them, and leave a short comment on where the device factory would create it.",
6
+ "expect": ["class SilenceSource : public PacedSource", "core/src/source/source.cpp", "override"]
7
+ },
8
+ {
9
+ "id": "adc-status-verylow",
10
+ "task": true,
11
+ "q": "Add a new ADC status value VeryLow (between NoSignal and Low) for a signal more than 12 dB below the existing low threshold. Update the enum, the classifier, the status name and the advice text so that every function that switches over the status handles it.",
12
+ "expect": ["VeryLow", "core/include/dect2/source/gain.h", "core/src/source/gain.cpp", "case AdcStatus::VeryLow"]
13
+ },
14
+ {
15
+ "id": "vfo-mode-cw",
16
+ "task": true,
17
+ "q": "Add a new multi-VFO channel mode Cw (narrow CW audio, default bandwidth 500 Hz) placed before Count in the mode enum. Update every function in the VFO engine code that maps or switches over the mode (name, default bandwidth, decoder check) so the new mode is handled; demodulate it like Usb for now.",
18
+ "expect": ["Cw", "core/include/dect2/engine/vfo.h", "core/src/engine/vfo.cpp", "500"]
19
+ }
20
+ ]
@@ -0,0 +1,52 @@
1
+ [
2
+ {
3
+ "id": "iqsource-subclasses",
4
+ "q": "Which classes derive directly from IqSource (outside tests)? List every one with its file.",
5
+ "expect": ["BeamIqSource", "DiversitySource", "SyntheticWideSource", "UsbHackrf", "NativeSource", "HackrfSource", "HackrfAuto", "PacedSource", "AudioSource", "HamlibSource", "SoapySource"]
6
+ },
7
+ {
8
+ "id": "prefs-implementers",
9
+ "q": "The app has an abstract Prefs interface with a pure virtual setChannels. Where is it declared and which structs implement it?",
10
+ "expect": ["platform.h", "DefaultsPrefs", "FilePrefs", "RxPrefs"]
11
+ },
12
+ {
13
+ "id": "gain-for-total",
14
+ "q": "Where is gainForTotal implemented, and which code outside tests calls it? Give file paths.",
15
+ "expect": ["core/src/source/gain.cpp", "jni_onair.cpp"]
16
+ },
17
+ {
18
+ "id": "vfo-default-bandwidth",
19
+ "q": "Where is vfoDefaultBandwidthHz defined, and where is it called (outside tests)?",
20
+ "expect": ["core/src/engine/vfo.cpp", "vfo_state.h"]
21
+ },
22
+ {
23
+ "id": "vfo-set-channels",
24
+ "q": "How do the multi-VFO channels reach VfoBank::setChannels from the UI? Name the Engine method in between and the UI file that calls it.",
25
+ "expect": ["setVfoChannels", "engine.h", "vfo_ui.cpp"]
26
+ },
27
+ {
28
+ "id": "mode-tunings",
29
+ "q": "Where is modeTunings() implemented and which non-test code calls it?",
30
+ "expect": ["modes.cpp", "jni_onair.cpp"]
31
+ },
32
+ {
33
+ "id": "crash-check",
34
+ "q": "Which function tells whether the previous run of the program crashed, where is it implemented, and where is it called at startup?",
35
+ "expect": ["lastRunCrashed", "crash_report.cpp", "main.cpp"]
36
+ },
37
+ {
38
+ "id": "generic-gain",
39
+ "q": "Which non-test files call genericGain, and in which file is it implemented?",
40
+ "expect": ["core/src/source/gain.cpp", "jni_onair.cpp", "statusbar.cpp"]
41
+ },
42
+ {
43
+ "id": "adc-advice",
44
+ "q": "Which function produces the one-line advice text for an ADC status, where is it implemented, and which app UI files use it?",
45
+ "expect": ["adcAdvice", "gain.cpp", "atsc3_ui.cpp", "dab_ui.cpp", "dtmb_ui.cpp", "fm_ui.cpp", "isdbt_ui.cpp", "statusbar.cpp"]
46
+ },
47
+ {
48
+ "id": "classify-adc",
49
+ "q": "Where is classifyAdc implemented, what are the possible AdcStatus values, and roughly how many app UI files call it?",
50
+ "expect": ["gain.cpp", "NoSignal", "Overload", "Good"]
51
+ }
52
+ ]
@@ -0,0 +1,22 @@
1
+ [
2
+ {
3
+ "id": "hot-exit",
4
+ "q": "Which class is responsible for making sure work the user has not saved survives a crash or shutdown and comes back on the next start? Name the base class and its file.",
5
+ "expect": ["WorkingCopyBackupTracker", "workingCopyBackupTracker.ts"]
6
+ },
7
+ {
8
+ "id": "extension-placement",
9
+ "q": "Where is it decided whether a given extension runs locally, in the web worker or on the remote machine? Name the class that keeps track of that decision and its file.",
10
+ "expect": ["ExtensionRunningLocationTracker", "extensionRunningLocationTracker.ts"]
11
+ },
12
+ {
13
+ "id": "file-event-merge",
14
+ "q": "When many low-level file system notifications arrive for the same path in a short time, which code merges them into fewer events before they are passed on? Name the function or class and the file.",
15
+ "expect": ["watcher.ts", "oalesce"]
16
+ },
17
+ {
18
+ "id": "indent-folding",
19
+ "q": "Which function works out the collapsible regions of a document from how far its lines are indented, when no language-specific provider is available? Name the function and the file.",
20
+ "expect": ["computeRanges", "indentRangeProvider.ts"]
21
+ }
22
+ ]
@@ -0,0 +1,32 @@
1
+ [
2
+ {
3
+ "id": "picker-subclasses-transitive",
4
+ "q": "List every non-test class that is a direct or indirect subclass of PickerQuickAccessProvider (follow the inheritance chain to the leaves).",
5
+ "expect": ["AbstractCommandsQuickAccessProvider", "AbstractEditorCommandsQuickAccessProvider", "StandaloneCommandsQuickAccessProvider", "CommandsQuickAccessProvider", "BaseEditorQuickAccessProvider", "ActiveGroupEditorsByMostRecentlyUsedQuickAccess", "AllEditorsByAppearanceQuickAccess", "AllEditorsByMostRecentlyUsedQuickAccess", "DebugConsoleQuickAccess", "StartDebugQuickAccessProvider", "InstallExtensionQuickAccessProvider", "ManageExtensionsQuickAccessProvider", "IssueQuickAccess", "ViewQuickAccessProvider", "AnythingQuickAccessProvider", "TextSearchQuickAccess", "SymbolsQuickAccessProvider", "TasksQuickAccessProvider", "TerminalQuickAccessProvider"]
6
+ },
7
+ {
8
+ "id": "coalesce-chain",
9
+ "q": "Trace upwards from the file watcher's coalesceEvents function in production code: which methods call it, and which methods call those? Give method names.",
10
+ "expect": ["handleFileChanges", "fileChangesAggregator", "handleParcelEvents", "startPolling", "startWatching"]
11
+ },
12
+ {
13
+ "id": "resolver-users-and-subclasses",
14
+ "q": "Which methods of AbstractKeybindingService call _getResolver(), and which classes (including tests, direct or indirect) extend AbstractKeybindingService?",
15
+ "expect": ["getDefaultKeybindings", "getKeybindings", "lookupKeybindings", "lookupKeybinding", "softDispatch", "_doDispatch", "StandaloneKeybindingService", "WorkbenchKeybindingService", "MobileKeybindingService", "TestKeybindingService", "TestStandaloneKeybindingService"]
16
+ },
17
+ {
18
+ "id": "soft-dispatch-callers",
19
+ "q": "Which non-test files call softDispatch on the keybinding service? List every file.",
20
+ "expect": ["contentHoverController.ts", "actionList.ts", "hoverService.ts", "listService.ts", "agentsWindowExtensionsWidget.ts", "developerActions.ts", "dialog.ts", "modalEditorPart.ts", "accessibleView.ts", "issueReporterKeybindings.ts", "nativeIssueFormService.ts", "markersView.ts", "terminalInstance.ts"]
21
+ },
22
+ {
23
+ "id": "resolve-impact",
24
+ "q": "I want to add a required parameter to KeybindingResolver.resolve. Which methods call it directly, and which methods of the same service call the dispatching one of those (one more level up)?",
25
+ "expect": ["softDispatch", "_doDispatch", "_dispatch", "_singleModifierDispatch", "dispatchByUserSettingsLabel"]
26
+ },
27
+ {
28
+ "id": "mobile-keybinding-chain",
29
+ "q": "Give the complete supertype chain of MobileKeybindingService: every class it inherits from and every interface those implement.",
30
+ "expect": ["WorkbenchKeybindingService", "AbstractKeybindingService", "Disposable", "IKeybindingService", "IDisposable"]
31
+ }
32
+ ]
@@ -0,0 +1,17 @@
1
+ [
2
+ {
3
+ "id": "signature-change-count",
4
+ "q": "IKeybindingService.softDispatch (src/vs/platform/keybinding/common/keybinding.ts) gets a new required parameter. In how many production (non-test) files are there call sites that must be updated? Give the number and list the files.",
5
+ "expect": ["14", "contentHoverController.ts", "modalEditorPart.ts", "terminalInstance.ts", "markersView.ts"]
6
+ },
7
+ {
8
+ "id": "test-for-symbol",
9
+ "q": "Which test file directly tests AbstractKeybindingService.softDispatch, and which other classes implement softDispatch and would have to change with it? Name the test file and the mock class.",
10
+ "expect": ["abstractKeybindingService.test.ts", "MockKeybindingService"]
11
+ },
12
+ {
13
+ "id": "dead-code-file",
14
+ "q": "Is there an exported function in src/vs/base/common/validation.ts that nothing in the repository uses? Name it.",
15
+ "expect": ["vLazy"]
16
+ }
17
+ ]
@@ -0,0 +1,18 @@
1
+ [
2
+ {
3
+ "id": "rename-soft-dispatch",
4
+ "task": true,
5
+ "q": "Rename the keybinding service method softDispatch to softDispatchKey. Update the interface, every implementation (including test doubles and mocks) and every caller in src/ and extensions/, so that nothing refers to the old name any more.",
6
+ "expect": ["softDispatchKey", "src/vs/platform/keybinding/common/keybinding.ts", "src/vs/platform/keybinding/common/abstractKeybindingService.ts", "src/vs/workbench/contrib/terminal/browser/terminalInstance.ts", "src/vs/platform/list/browser/listService.ts"],
7
+ "mustNotRemain": "\\bsoftDispatch\\b",
8
+ "paths": ["src", "extensions"]
9
+ },
10
+ {
11
+ "id": "quick-access-param",
12
+ "task": true,
13
+ "q": "Add a required third parameter includeDisabled: boolean to getQuickAccessProvider on the quick access registry. When it is false the behaviour stays as it is now. Update the interface, the implementation and every caller including tests; existing callers pass false.",
14
+ "expect": ["includeDisabled: boolean", "src/vs/platform/quickinput/common/quickAccess.ts", "src/vs/platform/quickinput/browser/quickAccess.ts", "src/vs/platform/quickinput/browser/helpQuickAccess.ts", "unifiedQuickAccess.ts", "quickAccess.test.ts"],
15
+ "mustNotRemain": "getQuickAccessProvider\\([^,()]*(\\([^()]*\\))?[^,()]*,[^,()]*(\\([^()]*\\))?[^,()]*\\)[^:]",
16
+ "paths": ["src"]
17
+ }
18
+ ]
@@ -0,0 +1,20 @@
1
+ [
2
+ {
3
+ "id": "quick-access-provider",
4
+ "task": true,
5
+ "q": "Add a new quick access provider to the workbench with the prefix \"todo \" that shows three fixed placeholder picks (\"First\", \"Second\", \"Third\"). It must build on the same base class the other pick-based quick access providers use and be registered the same way they are, with a help entry. Put it in its own file under the existing workbench quick access contribution and make sure it is loaded.",
6
+ "expect": ["extends PickerQuickAccessProvider", "registerQuickAccessProvider", "src/vs/workbench/contrib/quickaccess/browser", "'todo '", "helpEntries"]
7
+ },
8
+ {
9
+ "id": "editor-contribution",
10
+ "task": true,
11
+ "q": "Add a new editor contribution under src/vs/editor/contrib/ (its own folder) that, whenever the cursor moves to line 1, logs \"cursor at top\" through the log service. It must implement the editor contribution interface, clean up its listeners on dispose, and be registered the way other editor contributions are registered. Make sure the file is actually loaded by the editor.",
12
+ "expect": ["IEditorContribution", "registerEditorContribution", "onDidChangeCursorPosition", "ILogService", "editor.all.ts"]
13
+ },
14
+ {
15
+ "id": "terminal-copy-cwd",
16
+ "task": true,
17
+ "q": "Add a terminal command with id workbench.action.terminal.copyCwd that copies the current working directory of the active terminal instance to the clipboard. Register it the way the other terminal actions that operate on the active instance are registered, and add its id to the terminal command id enum.",
18
+ "expect": ["workbench.action.terminal.copyCwd", "registerActiveInstanceAction", "writeText", "terminalActions.ts", "TerminalCommandId"]
19
+ }
20
+ ]
@@ -0,0 +1,52 @@
1
+ [
2
+ {
3
+ "id": "coalesce",
4
+ "q": "In the file watcher, where are raw file change events deduplicated / coalesced before they are emitted, and which production (non-test) code calls that function?",
5
+ "expect": ["coalesceEvents", "watcher.ts", "nodejsWatcherLib", "parcelWatcher"]
6
+ },
7
+ {
8
+ "id": "terminal-hierarchy",
9
+ "q": "What does the class TerminalInstance extend and implement? Give the full chain of supertypes (classes and interfaces, including what those extend).",
10
+ "expect": ["Disposable", "ITerminalInstance", "IBaseTerminalInstance"]
11
+ },
12
+ {
13
+ "id": "quick-access",
14
+ "q": "Which method picks the quick access provider for a typed prefix, in which class and file is it implemented, and what does it return when no registered prefix matches?",
15
+ "expect": ["QuickAccessRegistry", "quickAccess.ts", "defaultProvider"]
16
+ },
17
+ {
18
+ "id": "keybinding",
19
+ "q": "Which method decides what command a keypress maps to given the current context and pending chords, where is it defined, and which production file calls it?",
20
+ "expect": ["KeybindingResolver", "keybindingResolver.ts", "abstractKeybindingService"]
21
+ },
22
+ {
23
+ "id": "folding-contrib",
24
+ "q": "Which class under src/vs/editor/contrib/folding implements IEditorContribution, and in which file?",
25
+ "expect": ["FoldingController", "folding.ts"]
26
+ },
27
+ {
28
+ "id": "open-code-editor",
29
+ "q": "I want to change the signature of ICodeEditorService.openCodeEditor. Which class implements it, and which non-test files under src/vs/editor/contrib call it?",
30
+ "expect": ["AbstractCodeEditorService", "gotoError.ts", "goToCommands.ts", "referencesController.ts", "symbolNavigation.ts", "inlineCompletionsModel.ts", "linkedEditing.ts", "rename.ts"]
31
+ },
32
+ {
33
+ "id": "activate-by-event",
34
+ "q": "Which classes (not interfaces) under src/vs/workbench define a method named activateByEvent? List every one.",
35
+ "expect": ["ExtensionsActivator", "AbstractExtensionService", "ExtensionHostManager", "LazyCreateExtensionHostManager", "NullExtensionService", "ExtensionHostProxy"]
36
+ },
37
+ {
38
+ "id": "code-editor-service-subclasses",
39
+ "q": "Which classes extend AbstractCodeEditorService, including test helpers?",
40
+ "expect": ["StandaloneCodeEditorService", "TestCodeEditorService", "workbench/services/editor/browser/codeEditorService.ts"]
41
+ },
42
+ {
43
+ "id": "show-message",
44
+ "q": "When an extension calls vscode.window.showInformationMessage, which main-thread class receives the request, and in which file is it?",
45
+ "expect": ["MainThreadMessageService", "mainThreadMessageService.ts"]
46
+ },
47
+ {
48
+ "id": "glob-regexp",
49
+ "q": "In the base glob implementation, which function turns a glob pattern string into regular expression source, and which function in the same file calls it to build the RegExp matcher?",
50
+ "expect": ["parseRegExp", "glob.ts", "toRegExp"]
51
+ }
52
+ ]
package/bench/ab.mjs ADDED
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env node
2
+ // A/B benchmark: the same questions answered by headless Claude Code sessions, once with the clast
3
+ // MCP server and once with only Read / Grep / Glob. One session per question, so every number is
4
+ // per question. Usage:
5
+ // node bench/ab.mjs <repo-dir> <questions.json> [--runs 3] [--parallel 4] [--model <id>]
6
+ // [--arms clast,grep] [--only id1,id2] [--out results.json]
7
+ // Requires the `claude` CLI on PATH and an index that is already built (`clast-ai index`).
8
+ import { spawn, spawnSync } from 'node:child_process';
9
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
10
+ import { dirname, join, resolve } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { tmpdir } from 'node:os';
13
+
14
+ const here = dirname(fileURLToPath(import.meta.url));
15
+ const args = process.argv.slice(2);
16
+ const flag = (name, dflt) => {
17
+ const i = args.indexOf(`--${name}`);
18
+ return i >= 0 ? args.splice(i, 2)[1] : dflt;
19
+ };
20
+ const runs = Number(flag('runs', '1'));
21
+ const parallel = Number(flag('parallel', '4'));
22
+ const model = flag('model', '');
23
+ const arms = flag('arms', 'clast,grep').split(',');
24
+ const only = flag('only', '');
25
+ const out = flag('out', '');
26
+ const noDefer = args.includes('--no-defer') ? (args.splice(args.indexOf('--no-defer'), 1), true) : false;
27
+ const hint = flag('hint', '');
28
+ const alwaysLoad = args.includes('--always-load') ? (args.splice(args.indexOf('--always-load'), 1), true) : false;
29
+ const server = flag('server', join(here, '..', 'dist', 'cli.js'));
30
+ const [repoArg, questionsArg] = args;
31
+ if (!repoArg || !questionsArg) {
32
+ console.error('usage: node bench/ab.mjs <repo-dir> <questions.json> [--runs n] [--parallel n] [--model id]');
33
+ process.exit(2);
34
+ }
35
+ const repo = resolve(repoArg);
36
+ let questions = JSON.parse(readFileSync(questionsArg, 'utf8'));
37
+ if (only) questions = questions.filter(q => only.split(',').includes(q.id));
38
+
39
+ const work = join(tmpdir(), `clast-ab-${process.pid}`);
40
+ mkdirSync(work, { recursive: true });
41
+ const mcpClast = join(work, 'mcp-clast.json');
42
+ const mcpNone = join(work, 'mcp-none.json');
43
+ writeFileSync(mcpClast, JSON.stringify({ mcpServers: { clast: { command: process.execPath, args: [resolve(server)] } } }));
44
+ const mcpLoaded = join(work, 'mcp-loaded.json');
45
+ writeFileSync(mcpLoaded, JSON.stringify({ mcpServers: { clast: { command: process.execPath, args: [resolve(server)], alwaysLoad: true } } }));
46
+ writeFileSync(mcpNone, JSON.stringify({ mcpServers: {} }));
47
+
48
+ const BLOCKED = 'Bash,PowerShell,NotebookEdit,Agent,Task,WebSearch,WebFetch,Skill';
49
+ const PROMPT = q =>
50
+ `Answer this question about the code in the current directory. Be brief: name the symbols and ` +
51
+ `file paths, no long explanations. Do not modify anything.\n\n${q}`;
52
+ // Implementation tasks ({"task": true} in the questions file): the agent edits the checkout, the
53
+ // diff is scored against the expected strings, and the checkout is reset. Such runs must not share
54
+ // a checkout, so they are forced to run one at a time.
55
+ const TASK_PROMPT = q =>
56
+ `Implement the following change in the repository in the current directory. Find the right place ` +
57
+ `and the existing base classes, registries and conventions first, and follow them. Keep the change ` +
58
+ `focused. You cannot build or run anything here, so do not try. Finish with a two-line summary of ` +
59
+ `the files you changed.\n\n${q}`;
60
+ const git = (...a) => spawnSync('git', a, { cwd: repo, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
61
+ function collectDiff() {
62
+ git('add', '-A', '-N');
63
+ return git('diff').stdout || '';
64
+ }
65
+ function resetRepo() {
66
+ git('reset', '-q', '--hard', 'HEAD');
67
+ git('clean', '-fdq');
68
+ }
69
+
70
+ function runOne(question, arm) {
71
+ return new Promise(done => {
72
+ const cli = [
73
+ '-p', '--output-format', 'stream-json', '--verbose',
74
+ '--strict-mcp-config', '--mcp-config', arm === 'clast' ? mcpClast : arm === 'clast-loaded' ? mcpLoaded : mcpNone,
75
+ '--allowedTools',
76
+ (question.task ? 'Read,Grep,Glob,Edit,Write' : 'Read,Grep,Glob') + (arm === 'grep' ? '' : ',mcp__clast'),
77
+ '--disallowedTools', question.task ? BLOCKED : `${BLOCKED},Edit,Write`,
78
+ '--max-turns', question.task ? '150' : '60',
79
+ ];
80
+ if (model) cli.push('--model', model);
81
+ if (hint && arm !== 'grep') cli.push('--append-system-prompt', JSON.stringify(hint));
82
+ const env = { ...process.env };
83
+ for (const k of Object.keys(env)) if (/^CLAUDE_?CODE|^CLAUDECODE/i.test(k)) delete env[k];
84
+ if (noDefer) env.ENABLE_TOOL_SEARCH = 'false';
85
+ const started = Date.now();
86
+ const child = spawn('claude', cli, { cwd: repo, env, shell: process.platform === 'win32' });
87
+ let buf = '';
88
+ let err = '';
89
+ const rec = { id: question.id, arm, tools: {}, toolCalls: 0, resultChars: 0, mcpOk: null };
90
+ const raw = process.env.AB_RAW ? [] : null;
91
+ const onLine = line => {
92
+ if (!line.trim()) return;
93
+ if (raw) raw.push(line);
94
+ let msg;
95
+ try { msg = JSON.parse(line); } catch { return; }
96
+ if (msg.type === 'system' && msg.subtype === 'init') {
97
+ rec.model = msg.model;
98
+ const s = (msg.mcp_servers || []).find(x => x.name === 'clast');
99
+ rec.mcpOk = s ? s.status : null;
100
+ } else if (msg.type === 'assistant') {
101
+ for (const part of msg.message?.content || []) {
102
+ if (part.type === 'tool_use') {
103
+ rec.toolCalls++;
104
+ const name = part.name.replace(/^mcp__clast__/, '');
105
+ rec.tools[name] = (rec.tools[name] || 0) + 1;
106
+ }
107
+ }
108
+ } else if (msg.type === 'user') {
109
+ for (const part of msg.message?.content || []) {
110
+ if (part.type !== 'tool_result') continue;
111
+ const c = part.content;
112
+ rec.resultChars += typeof c === 'string' ? c.length : JSON.stringify(c ?? '').length;
113
+ }
114
+ } else if (msg.type === 'result') {
115
+ const u = msg.usage || {};
116
+ rec.answer = msg.result || '';
117
+ rec.error = msg.is_error ? msg.subtype : undefined;
118
+ rec.turns = msg.num_turns;
119
+ rec.apiMs = msg.duration_api_ms;
120
+ rec.cost = msg.total_cost_usd;
121
+ rec.inputTokens = (u.input_tokens || 0) + (u.cache_creation_input_tokens || 0) + (u.cache_read_input_tokens || 0);
122
+ rec.freshInput = (u.input_tokens || 0) + (u.cache_creation_input_tokens || 0);
123
+ rec.outputTokens = u.output_tokens || 0;
124
+ }
125
+ };
126
+ child.stdout.on('data', d => {
127
+ buf += d;
128
+ let i;
129
+ while ((i = buf.indexOf('\n')) >= 0) { onLine(buf.slice(0, i)); buf = buf.slice(i + 1); }
130
+ });
131
+ child.stderr.on('data', d => { err += d; });
132
+ child.stdin.end(question.task ? TASK_PROMPT(question.q) : PROMPT(question.q));
133
+ child.on('close', code => {
134
+ onLine(buf);
135
+ rec.wallMs = Date.now() - started;
136
+ if (raw) writeFileSync(join(process.env.AB_RAW, `${question.id}-${arm}-${started}.jsonl`), raw.join('\n'));
137
+ if (rec.answer === undefined) rec.error = `exit ${code}: ${err.slice(-300)}`;
138
+ if (question.task) {
139
+ rec.diff = collectDiff();
140
+ rec.diffLines = rec.diff.split('\n').filter(l => /^[+-][^+-]/.test(l)).length;
141
+ rec.files = [...rec.diff.matchAll(/^diff --git a\/(\S+)/gm)].map(m => m[1]);
142
+ // "mustNotRemain": a pattern that a complete change leaves nowhere (the old name, the old call shape).
143
+ if (question.mustNotRemain) {
144
+ const left = git('grep', '-I', '-E', '-c', question.mustNotRemain, '--', ...(question.paths || ['.'])).stdout || '';
145
+ rec.remaining = left.split('\n').filter(Boolean).reduce((n, l) => n + Number(l.slice(l.lastIndexOf(':') + 1)), 0);
146
+ rec.remainingFiles = left.split('\n').filter(Boolean).length;
147
+ }
148
+ resetRepo();
149
+ }
150
+ const answer = (question.task ? rec.diff : rec.answer || '').toLowerCase().replaceAll('\\', '/');
151
+ const hits = question.expect.filter(e => answer.includes(e.toLowerCase()));
152
+ rec.missing = question.expect.filter(e => !hits.includes(e));
153
+ if (question.mustNotRemain) {
154
+ // Half for the expected strings, half for completeness (nothing of the old form left).
155
+ rec.score = 0.5 * (hits.length / question.expect.length) + 0.5 * (rec.remaining === 0 ? 1 : 0);
156
+ if (rec.remaining) rec.missing.push(`${rec.remaining} left in ${rec.remainingFiles} files`);
157
+ } else {
158
+ rec.score = hits.length / question.expect.length;
159
+ }
160
+ done(rec);
161
+ });
162
+ });
163
+ }
164
+
165
+ const jobs = [];
166
+ for (let r = 0; r < runs; r++) for (const q of questions) for (const arm of arms) jobs.push({ q, arm, r });
167
+ const results = [];
168
+ let next = 0;
169
+ async function worker() {
170
+ while (next < jobs.length) {
171
+ const job = jobs[next++];
172
+ const rec = await runOne(job.q, job.arm);
173
+ rec.run = job.r;
174
+ results.push(rec);
175
+ console.error(
176
+ `[${results.length}/${jobs.length}] ${rec.arm.padEnd(12)} ${rec.id.padEnd(30)} calls ${String(rec.toolCalls).padStart(3)} ` +
177
+ `in ${String(rec.inputTokens ?? '?').padStart(8)} out ${String(rec.outputTokens ?? '?').padStart(6)} ` +
178
+ `${(rec.wallMs / 1000).toFixed(0).padStart(4)} s score ${rec.score.toFixed(2)}${rec.error ? ' ERROR ' + rec.error : ''}`
179
+ );
180
+ }
181
+ }
182
+ const lanes = questions.some(q => q.task) ? 1 : Math.min(parallel, jobs.length);
183
+ if (questions.some(q => q.task)) {
184
+ if (git('status', '--porcelain').stdout.trim()) {
185
+ console.error(`task runs reset the checkout after every session; ${repo} has uncommitted changes, refusing.`);
186
+ process.exit(2);
187
+ }
188
+ }
189
+ await Promise.all(Array.from({ length: lanes }, worker));
190
+
191
+ const sum = (rows, f) => rows.reduce((a, r) => a + (f(r) || 0), 0);
192
+ const median = xs => { const s = [...xs].sort((a, b) => a - b); return s.length ? s[Math.floor((s.length - 1) / 2)] : 0; };
193
+ const summary = {};
194
+ for (const arm of arms) {
195
+ const rows = results.filter(r => r.arm === arm && !r.error);
196
+ const n = rows.length || 1;
197
+ summary[arm] = {
198
+ sessions: rows.length,
199
+ errors: results.filter(r => r.arm === arm && r.error).length,
200
+ toolCalls: +(sum(rows, r => r.toolCalls) / n).toFixed(1),
201
+ toolResultChars: Math.round(sum(rows, r => r.resultChars) / n),
202
+ inputTokens: Math.round(sum(rows, r => r.inputTokens) / n),
203
+ freshInputTokens: Math.round(sum(rows, r => r.freshInput) / n),
204
+ outputTokens: Math.round(sum(rows, r => r.outputTokens) / n),
205
+ costUsd: +(sum(rows, r => r.cost) / n).toFixed(4),
206
+ wallSecMean: +(sum(rows, r => r.wallMs) / n / 1000).toFixed(1),
207
+ wallSecMedian: +(median(rows.map(r => r.wallMs)) / 1000).toFixed(1),
208
+ score: +(sum(rows, r => r.score) / n).toFixed(3),
209
+ fullyCorrect: rows.filter(r => r.score === 1).length,
210
+ };
211
+ }
212
+ console.log(JSON.stringify({ repo, model: results[0]?.model, runs, questions: questions.length, summary }, null, 2));
213
+ if (out) writeFileSync(out, JSON.stringify({ repo, runs, summary, results }, null, 2));