akm-cli 0.7.0-rc1 → 0.7.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 (314) hide show
  1. package/dist/{src/cli.js → cli.js} +100 -16
  2. package/dist/{src/commands → commands}/config-cli.js +42 -0
  3. package/dist/{src/commands → commands}/history.js +78 -7
  4. package/dist/{src/commands → commands}/registry-search.js +69 -6
  5. package/dist/{src/commands → commands}/search.js +30 -3
  6. package/dist/{src/commands → commands}/show.js +29 -0
  7. package/dist/{src/commands → commands}/source-add.js +5 -1
  8. package/dist/{src/commands → commands}/source-manage.js +7 -1
  9. package/dist/{src/core → core}/config.js +28 -0
  10. package/dist/{src/indexer → indexer}/db-search.js +1 -0
  11. package/dist/{src/indexer → indexer}/indexer.js +16 -2
  12. package/dist/{src/indexer → indexer}/matchers.js +1 -1
  13. package/dist/{src/indexer → indexer}/search-source.js +4 -2
  14. package/dist/{src/integrations → integrations}/agent/profiles.js +1 -1
  15. package/dist/{src/integrations → integrations}/agent/spawn.js +67 -16
  16. package/dist/{src/integrations → integrations}/github.js +9 -3
  17. package/dist/{src/llm → llm}/embedders/remote.js +37 -3
  18. package/dist/{src/output → output}/cli-hints.js +15 -2
  19. package/dist/{src/output → output}/renderers.js +3 -1
  20. package/dist/{src/output → output}/shapes.js +8 -1
  21. package/dist/{src/output → output}/text.js +156 -3
  22. package/dist/{src/registry → registry}/build-index.js +5 -4
  23. package/dist/{src/registry → registry}/providers/static-index.js +3 -1
  24. package/dist/{src/setup → setup}/setup.js +9 -0
  25. package/dist/{src/wiki → wiki}/wiki.js +54 -6
  26. package/dist/{src/workflows → workflows}/runs.js +37 -3
  27. package/package.json +8 -8
  28. package/dist/tests/add-website-source.test.js +0 -119
  29. package/dist/tests/agent/agent-config-loader.test.js +0 -70
  30. package/dist/tests/agent/agent-config.test.js +0 -221
  31. package/dist/tests/agent/agent-detect.test.js +0 -100
  32. package/dist/tests/agent/agent-spawn.test.js +0 -234
  33. package/dist/tests/agent-output.test.js +0 -186
  34. package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +0 -103
  35. package/dist/tests/architecture/agent-spawn-seam.test.js +0 -193
  36. package/dist/tests/architecture/llm-stateless-seam.test.js +0 -112
  37. package/dist/tests/asset-ref.test.js +0 -192
  38. package/dist/tests/asset-registry.test.js +0 -103
  39. package/dist/tests/asset-spec.test.js +0 -241
  40. package/dist/tests/bench/attribution.test.js +0 -995
  41. package/dist/tests/bench/cleanup-sigint.test.js +0 -83
  42. package/dist/tests/bench/cleanup.js +0 -203
  43. package/dist/tests/bench/cleanup.test.js +0 -166
  44. package/dist/tests/bench/cli.js +0 -683
  45. package/dist/tests/bench/cli.test.js +0 -177
  46. package/dist/tests/bench/compare.test.js +0 -556
  47. package/dist/tests/bench/corpus.js +0 -314
  48. package/dist/tests/bench/corpus.test.js +0 -258
  49. package/dist/tests/bench/driver.js +0 -346
  50. package/dist/tests/bench/driver.test.js +0 -443
  51. package/dist/tests/bench/evolve-metrics.js +0 -179
  52. package/dist/tests/bench/evolve-metrics.test.js +0 -187
  53. package/dist/tests/bench/evolve.js +0 -580
  54. package/dist/tests/bench/evolve.test.js +0 -616
  55. package/dist/tests/bench/failure-modes.test.js +0 -300
  56. package/dist/tests/bench/feedback-integrity.test.js +0 -456
  57. package/dist/tests/bench/leakage.test.js +0 -125
  58. package/dist/tests/bench/learning-curve.test.js +0 -133
  59. package/dist/tests/bench/metrics.js +0 -2319
  60. package/dist/tests/bench/metrics.test.js +0 -1144
  61. package/dist/tests/bench/no-os-tmpdir-invariant.test.js +0 -43
  62. package/dist/tests/bench/report.js +0 -1821
  63. package/dist/tests/bench/report.test.js +0 -989
  64. package/dist/tests/bench/runner.js +0 -536
  65. package/dist/tests/bench/runner.test.js +0 -958
  66. package/dist/tests/bench/search-bridge.test.js +0 -331
  67. package/dist/tests/bench/tmp.js +0 -41
  68. package/dist/tests/bench/trajectory.js +0 -116
  69. package/dist/tests/bench/trajectory.test.js +0 -127
  70. package/dist/tests/bench/verifier.js +0 -109
  71. package/dist/tests/bench/verifier.test.js +0 -118
  72. package/dist/tests/bench/workflow-evaluator.js +0 -557
  73. package/dist/tests/bench/workflow-evaluator.test.js +0 -421
  74. package/dist/tests/bench/workflow-spec.js +0 -358
  75. package/dist/tests/bench/workflow-spec.test.js +0 -363
  76. package/dist/tests/bench/workflow-trace.js +0 -438
  77. package/dist/tests/bench/workflow-trace.test.js +0 -254
  78. package/dist/tests/benchmark-search-quality.js +0 -536
  79. package/dist/tests/benchmark-suite.js +0 -1441
  80. package/dist/tests/capture-cli.test.js +0 -112
  81. package/dist/tests/cli-errors.test.js +0 -203
  82. package/dist/tests/commands/events.test.js +0 -370
  83. package/dist/tests/commands/history.test.js +0 -223
  84. package/dist/tests/commands/import.test.js +0 -103
  85. package/dist/tests/commands/proposal-cli.test.js +0 -209
  86. package/dist/tests/commands/reflect-propose-cli.test.js +0 -333
  87. package/dist/tests/commands/remember.test.js +0 -97
  88. package/dist/tests/commands/scope-flags.test.js +0 -300
  89. package/dist/tests/commands/search.test.js +0 -537
  90. package/dist/tests/commands/show-indexer-parity.test.js +0 -117
  91. package/dist/tests/commands/show.test.js +0 -294
  92. package/dist/tests/common.test.js +0 -266
  93. package/dist/tests/completions.test.js +0 -142
  94. package/dist/tests/config-cli.test.js +0 -193
  95. package/dist/tests/config-llm-features.test.js +0 -139
  96. package/dist/tests/config.test.js +0 -544
  97. package/dist/tests/contracts/migration-baseline.test.js +0 -43
  98. package/dist/tests/contracts/reflect-propose-envelope.test.js +0 -139
  99. package/dist/tests/contracts/spec-helpers.js +0 -46
  100. package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +0 -228
  101. package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +0 -56
  102. package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +0 -34
  103. package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +0 -94
  104. package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +0 -39
  105. package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +0 -44
  106. package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +0 -47
  107. package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +0 -40
  108. package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +0 -58
  109. package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +0 -34
  110. package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +0 -75
  111. package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +0 -36
  112. package/dist/tests/core/write-source.test.js +0 -366
  113. package/dist/tests/curate-command.test.js +0 -87
  114. package/dist/tests/db-scoring.test.js +0 -201
  115. package/dist/tests/db.test.js +0 -654
  116. package/dist/tests/distill-cli-flag.test.js +0 -208
  117. package/dist/tests/distill.test.js +0 -515
  118. package/dist/tests/docker-install.test.js +0 -120
  119. package/dist/tests/e2e.test.js +0 -1398
  120. package/dist/tests/embedder.test.js +0 -340
  121. package/dist/tests/embedding-model-config.test.js +0 -379
  122. package/dist/tests/feedback-command.test.js +0 -172
  123. package/dist/tests/file-context.test.js +0 -552
  124. package/dist/tests/fixtures/scripts/git/summarize-diff.js +0 -9
  125. package/dist/tests/fixtures/scripts/lint/eslint-check.js +0 -7
  126. package/dist/tests/fixtures/stashes/load.js +0 -166
  127. package/dist/tests/fixtures/stashes/load.test.js +0 -88
  128. package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +0 -12
  129. package/dist/tests/frontmatter.test.js +0 -190
  130. package/dist/tests/fts-field-weighting.test.js +0 -254
  131. package/dist/tests/fuzzy-search.test.js +0 -230
  132. package/dist/tests/git-provider-clone.test.js +0 -45
  133. package/dist/tests/github.test.js +0 -161
  134. package/dist/tests/graph-boost-ranking.test.js +0 -305
  135. package/dist/tests/graph-extraction.test.js +0 -282
  136. package/dist/tests/helpers/usage-events.js +0 -8
  137. package/dist/tests/index-pass-llm.test.js +0 -161
  138. package/dist/tests/indexer.test.js +0 -559
  139. package/dist/tests/info-command.test.js +0 -166
  140. package/dist/tests/init.test.js +0 -69
  141. package/dist/tests/install-script.test.js +0 -246
  142. package/dist/tests/integration/agent-real-profile.test.js +0 -94
  143. package/dist/tests/issue-36-repro.test.js +0 -304
  144. package/dist/tests/issues-191-194.test.js +0 -160
  145. package/dist/tests/lesson-lint.test.js +0 -111
  146. package/dist/tests/llm-client.test.js +0 -115
  147. package/dist/tests/llm-feature-gate.test.js +0 -151
  148. package/dist/tests/llm.test.js +0 -139
  149. package/dist/tests/lockfile.test.js +0 -216
  150. package/dist/tests/manifest.test.js +0 -205
  151. package/dist/tests/markdown.test.js +0 -126
  152. package/dist/tests/matchers-unit.test.js +0 -189
  153. package/dist/tests/memory-inference.test.js +0 -299
  154. package/dist/tests/merge-scoring.test.js +0 -136
  155. package/dist/tests/metadata.test.js +0 -313
  156. package/dist/tests/migration-help.test.js +0 -89
  157. package/dist/tests/origin-resolve.test.js +0 -124
  158. package/dist/tests/output-baseline.test.js +0 -217
  159. package/dist/tests/output-shapes-unit.test.js +0 -476
  160. package/dist/tests/parallel-search.test.js +0 -272
  161. package/dist/tests/parameter-metadata.test.js +0 -365
  162. package/dist/tests/paths.test.js +0 -177
  163. package/dist/tests/progressive-disclosure.test.js +0 -280
  164. package/dist/tests/proposals.test.js +0 -279
  165. package/dist/tests/proposed-quality.test.js +0 -271
  166. package/dist/tests/provider-registry.test.js +0 -32
  167. package/dist/tests/ranking-regression.test.js +0 -548
  168. package/dist/tests/reflect-propose.test.js +0 -455
  169. package/dist/tests/registry-build-index.test.js +0 -378
  170. package/dist/tests/registry-cli.test.js +0 -290
  171. package/dist/tests/registry-index-v2.test.js +0 -430
  172. package/dist/tests/registry-install.test.js +0 -728
  173. package/dist/tests/registry-providers/parity.test.js +0 -189
  174. package/dist/tests/registry-providers/skills-sh.test.js +0 -309
  175. package/dist/tests/registry-providers/static-index.test.js +0 -204
  176. package/dist/tests/registry-resolve.test.js +0 -126
  177. package/dist/tests/registry-search.test.js +0 -723
  178. package/dist/tests/remember-frontmatter.test.js +0 -380
  179. package/dist/tests/remember-unit.test.js +0 -123
  180. package/dist/tests/ripgrep-install.test.js +0 -251
  181. package/dist/tests/ripgrep-resolve.test.js +0 -108
  182. package/dist/tests/ripgrep.test.js +0 -163
  183. package/dist/tests/save-command.test.js +0 -94
  184. package/dist/tests/save-trust-qa-fixes.test.js +0 -270
  185. package/dist/tests/scoring-pipeline.test.js +0 -648
  186. package/dist/tests/search-include-proposed-cli.test.js +0 -118
  187. package/dist/tests/self-update.test.js +0 -442
  188. package/dist/tests/semantic-search-e2e.test.js +0 -512
  189. package/dist/tests/semantic-status.test.js +0 -471
  190. package/dist/tests/setup-run.integration.js +0 -877
  191. package/dist/tests/setup-wizard.test.js +0 -198
  192. package/dist/tests/setup.test.js +0 -131
  193. package/dist/tests/source-add.test.js +0 -11
  194. package/dist/tests/source-clone.test.js +0 -254
  195. package/dist/tests/source-manage.test.js +0 -366
  196. package/dist/tests/source-providers/filesystem.test.js +0 -82
  197. package/dist/tests/source-providers/git.test.js +0 -252
  198. package/dist/tests/source-providers/website.test.js +0 -128
  199. package/dist/tests/source-qa-fixes.test.js +0 -268
  200. package/dist/tests/source-registry.test.js +0 -350
  201. package/dist/tests/source-resolve.test.js +0 -100
  202. package/dist/tests/source-source.test.js +0 -221
  203. package/dist/tests/source.test.js +0 -533
  204. package/dist/tests/tar-utils-scan.test.js +0 -73
  205. package/dist/tests/toggle-components.test.js +0 -73
  206. package/dist/tests/usage-telemetry.test.js +0 -265
  207. package/dist/tests/utility-scoring.test.js +0 -558
  208. package/dist/tests/vault-load-error.test.js +0 -78
  209. package/dist/tests/vault-qa-fixes.test.js +0 -194
  210. package/dist/tests/vault.test.js +0 -429
  211. package/dist/tests/vector-search.test.js +0 -608
  212. package/dist/tests/walker.test.js +0 -252
  213. package/dist/tests/wave2-cluster-bc.test.js +0 -228
  214. package/dist/tests/wave2-cluster-d.test.js +0 -180
  215. package/dist/tests/wave2-cluster-e.test.js +0 -179
  216. package/dist/tests/wiki-qa-fixes.test.js +0 -270
  217. package/dist/tests/wiki.test.js +0 -529
  218. package/dist/tests/workflow-cli.test.js +0 -271
  219. package/dist/tests/workflow-markdown.test.js +0 -171
  220. package/dist/tests/workflow-path-escape.test.js +0 -132
  221. package/dist/tests/workflow-qa-fixes.test.js +0 -377
  222. package/dist/tests/workflows/indexer-rejection.test.js +0 -213
  223. /package/dist/{src/commands → commands}/completions.js +0 -0
  224. /package/dist/{src/commands → commands}/curate.js +0 -0
  225. /package/dist/{src/commands → commands}/distill.js +0 -0
  226. /package/dist/{src/commands → commands}/events.js +0 -0
  227. /package/dist/{src/commands → commands}/info.js +0 -0
  228. /package/dist/{src/commands → commands}/init.js +0 -0
  229. /package/dist/{src/commands → commands}/install-audit.js +0 -0
  230. /package/dist/{src/commands → commands}/installed-stashes.js +0 -0
  231. /package/dist/{src/commands → commands}/migration-help.js +0 -0
  232. /package/dist/{src/commands → commands}/proposal.js +0 -0
  233. /package/dist/{src/commands → commands}/propose.js +0 -0
  234. /package/dist/{src/commands → commands}/reflect.js +0 -0
  235. /package/dist/{src/commands → commands}/remember.js +0 -0
  236. /package/dist/{src/commands → commands}/self-update.js +0 -0
  237. /package/dist/{src/commands → commands}/source-clone.js +0 -0
  238. /package/dist/{src/commands → commands}/vault.js +0 -0
  239. /package/dist/{src/core → core}/asset-ref.js +0 -0
  240. /package/dist/{src/core → core}/asset-registry.js +0 -0
  241. /package/dist/{src/core → core}/asset-spec.js +0 -0
  242. /package/dist/{src/core → core}/common.js +0 -0
  243. /package/dist/{src/core → core}/errors.js +0 -0
  244. /package/dist/{src/core → core}/events.js +0 -0
  245. /package/dist/{src/core → core}/frontmatter.js +0 -0
  246. /package/dist/{src/core → core}/lesson-lint.js +0 -0
  247. /package/dist/{src/core → core}/markdown.js +0 -0
  248. /package/dist/{src/core → core}/paths.js +0 -0
  249. /package/dist/{src/core → core}/proposals.js +0 -0
  250. /package/dist/{src/core → core}/warn.js +0 -0
  251. /package/dist/{src/core → core}/write-source.js +0 -0
  252. /package/dist/{src/indexer → indexer}/db.js +0 -0
  253. /package/dist/{src/indexer → indexer}/file-context.js +0 -0
  254. /package/dist/{src/indexer → indexer}/graph-boost.js +0 -0
  255. /package/dist/{src/indexer → indexer}/graph-extraction.js +0 -0
  256. /package/dist/{src/indexer → indexer}/manifest.js +0 -0
  257. /package/dist/{src/indexer → indexer}/memory-inference.js +0 -0
  258. /package/dist/{src/indexer → indexer}/metadata.js +0 -0
  259. /package/dist/{src/indexer → indexer}/search-fields.js +0 -0
  260. /package/dist/{src/indexer → indexer}/semantic-status.js +0 -0
  261. /package/dist/{src/indexer → indexer}/usage-events.js +0 -0
  262. /package/dist/{src/indexer → indexer}/walker.js +0 -0
  263. /package/dist/{src/integrations → integrations}/agent/config.js +0 -0
  264. /package/dist/{src/integrations → integrations}/agent/detect.js +0 -0
  265. /package/dist/{src/integrations → integrations}/agent/index.js +0 -0
  266. /package/dist/{src/integrations → integrations}/agent/prompts.js +0 -0
  267. /package/dist/{src/integrations → integrations}/lockfile.js +0 -0
  268. /package/dist/{src/llm → llm}/client.js +0 -0
  269. /package/dist/{src/llm → llm}/embedder.js +0 -0
  270. /package/dist/{src/llm → llm}/embedders/cache.js +0 -0
  271. /package/dist/{src/llm → llm}/embedders/local.js +0 -0
  272. /package/dist/{src/llm → llm}/embedders/types.js +0 -0
  273. /package/dist/{src/llm → llm}/feature-gate.js +0 -0
  274. /package/dist/{src/llm → llm}/graph-extract.js +0 -0
  275. /package/dist/{src/llm → llm}/index-passes.js +0 -0
  276. /package/dist/{src/llm → llm}/memory-infer.js +0 -0
  277. /package/dist/{src/llm → llm}/metadata-enhance.js +0 -0
  278. /package/dist/{src/output → output}/context.js +0 -0
  279. /package/dist/{src/registry → registry}/create-provider-registry.js +0 -0
  280. /package/dist/{src/registry → registry}/factory.js +0 -0
  281. /package/dist/{src/registry → registry}/origin-resolve.js +0 -0
  282. /package/dist/{src/registry → registry}/providers/index.js +0 -0
  283. /package/dist/{src/registry → registry}/providers/skills-sh.js +0 -0
  284. /package/dist/{src/registry → registry}/providers/types.js +0 -0
  285. /package/dist/{src/registry → registry}/resolve.js +0 -0
  286. /package/dist/{src/registry → registry}/types.js +0 -0
  287. /package/dist/{src/setup → setup}/detect.js +0 -0
  288. /package/dist/{src/setup → setup}/ripgrep-install.js +0 -0
  289. /package/dist/{src/setup → setup}/ripgrep-resolve.js +0 -0
  290. /package/dist/{src/setup → setup}/steps.js +0 -0
  291. /package/dist/{src/sources → sources}/include.js +0 -0
  292. /package/dist/{src/sources → sources}/provider-factory.js +0 -0
  293. /package/dist/{src/sources → sources}/provider.js +0 -0
  294. /package/dist/{src/sources → sources}/providers/filesystem.js +0 -0
  295. /package/dist/{src/sources → sources}/providers/git.js +0 -0
  296. /package/dist/{src/sources → sources}/providers/index.js +0 -0
  297. /package/dist/{src/sources → sources}/providers/install-types.js +0 -0
  298. /package/dist/{src/sources → sources}/providers/npm.js +0 -0
  299. /package/dist/{src/sources → sources}/providers/provider-utils.js +0 -0
  300. /package/dist/{src/sources → sources}/providers/sync-from-ref.js +0 -0
  301. /package/dist/{src/sources → sources}/providers/tar-utils.js +0 -0
  302. /package/dist/{src/sources → sources}/providers/website.js +0 -0
  303. /package/dist/{src/sources → sources}/resolve.js +0 -0
  304. /package/dist/{src/sources → sources}/types.js +0 -0
  305. /package/dist/{src/templates → templates}/wiki-templates.js +0 -0
  306. /package/dist/{src/version.js → version.js} +0 -0
  307. /package/dist/{src/workflows → workflows}/authoring.js +0 -0
  308. /package/dist/{src/workflows → workflows}/cli.js +0 -0
  309. /package/dist/{src/workflows → workflows}/db.js +0 -0
  310. /package/dist/{src/workflows → workflows}/document-cache.js +0 -0
  311. /package/dist/{src/workflows → workflows}/parser.js +0 -0
  312. /package/dist/{src/workflows → workflows}/renderer.js +0 -0
  313. /package/dist/{src/workflows → workflows}/schema.js +0 -0
  314. /package/dist/{src/workflows → workflows}/validator.js +0 -0
@@ -1,83 +0,0 @@
1
- /**
2
- * Subprocess test: real SIGINT delivery cleans up registered fns before
3
- * exit (#267).
4
- *
5
- * The real handler calls `process.exit(130)` — fatal inside the test
6
- * runner. So we drive it from a child Bun process and assert via side
7
- * effects (touchstones written to a tmpdir) that every cleanup fn ran.
8
- */
9
- import { afterAll, describe, expect, test } from "bun:test";
10
- import { spawnSync } from "node:child_process";
11
- import fs from "node:fs";
12
- import path from "node:path";
13
- import { benchMkdtemp } from "./tmp";
14
- const tempDirs = [];
15
- function makeTempDir(prefix = "akm-bench-cleanup-sigint-") {
16
- const dir = benchMkdtemp(prefix);
17
- tempDirs.push(dir);
18
- return dir;
19
- }
20
- afterAll(() => {
21
- for (const dir of tempDirs) {
22
- fs.rmSync(dir, { recursive: true, force: true });
23
- }
24
- });
25
- const repoRoot = path.resolve(import.meta.dir, "..", "..");
26
- describe("SIGINT delivery → registered cleanups run (#267)", () => {
27
- test("subprocess: SIGINT runs every registered cleanup fn before exit", () => {
28
- const sigDir = makeTempDir();
29
- const a = path.join(sigDir, "a.touchstone");
30
- const b = path.join(sigDir, "b.touchstone");
31
- const c = path.join(sigDir, "c.touchstone");
32
- // Inline driver script. It registers three cleanup fns that each touch
33
- // a unique file, then `process.kill(pid, 'SIGINT')` and waits long
34
- // enough for the handler to fire `process.exit(130)`.
35
- const driverScript = `
36
- import { registerCleanup } from ${JSON.stringify(path.join(repoRoot, "tests", "bench", "cleanup.ts"))};
37
- import fs from "node:fs";
38
-
39
- registerCleanup(() => fs.writeFileSync(${JSON.stringify(a)}, "a"));
40
- registerCleanup(() => fs.writeFileSync(${JSON.stringify(b)}, "b"));
41
- registerCleanup(async () => {
42
- await new Promise((r) => setTimeout(r, 5));
43
- fs.writeFileSync(${JSON.stringify(c)}, "c");
44
- });
45
-
46
- process.kill(process.pid, "SIGINT");
47
- // Stay alive until the signal handler fires + exits.
48
- await new Promise((r) => setTimeout(r, 2000));
49
- `;
50
- const scriptPath = path.join(sigDir, "driver.mjs");
51
- fs.writeFileSync(scriptPath, driverScript);
52
- const result = spawnSync("bun", ["run", scriptPath], {
53
- encoding: "utf8",
54
- timeout: 10_000,
55
- });
56
- // Exit code 130 = signalled exit (POSIX convention 128 + SIGINT(2)).
57
- expect(result.status).toBe(130);
58
- // All three cleanup fns ran before exit.
59
- expect(fs.existsSync(a)).toBe(true);
60
- expect(fs.existsSync(b)).toBe(true);
61
- expect(fs.existsSync(c)).toBe(true);
62
- });
63
- test("subprocess: SIGTERM also triggers cleanup", () => {
64
- const sigDir = makeTempDir();
65
- const a = path.join(sigDir, "term.touchstone");
66
- const driverScript = `
67
- import { registerCleanup } from ${JSON.stringify(path.join(repoRoot, "tests", "bench", "cleanup.ts"))};
68
- import fs from "node:fs";
69
-
70
- registerCleanup(() => fs.writeFileSync(${JSON.stringify(a)}, "term"));
71
- process.kill(process.pid, "SIGTERM");
72
- await new Promise((r) => setTimeout(r, 2000));
73
- `;
74
- const scriptPath = path.join(sigDir, "driver-term.mjs");
75
- fs.writeFileSync(scriptPath, driverScript);
76
- const result = spawnSync("bun", ["run", scriptPath], {
77
- encoding: "utf8",
78
- timeout: 10_000,
79
- });
80
- expect(result.status).toBe(130);
81
- expect(fs.existsSync(a)).toBe(true);
82
- });
83
- });
@@ -1,203 +0,0 @@
1
- /**
2
- * Shared cleanup registry for the bench harness (#267).
3
- *
4
- * The bench creates many tmp directories — per (task, arm, seed) workspace,
5
- * per-task fixture stash, per-fixture evolveStash + preStash. Each of these
6
- * is wrapped in a try/finally so happy-path runs leave nothing behind. But
7
- * an external SIGINT/SIGTERM (operator hits Ctrl-C, CI cancels the job)
8
- * bypasses `finally` blocks entirely on Bun, leaving orphan tmp dirs under
9
- * the bench tmp root (#276 redirected this from the OS temp dir to
10
- * `${AKM_CACHE_DIR}/bench/`) that nothing reaps.
11
- *
12
- * `registerCleanup(fn)` captures the cleanup intent on a process-wide
13
- * registry and returns a deregister function. The first `registerCleanup`
14
- * call also installs ONE pair of SIGINT/SIGTERM handlers — subsequent calls
15
- * never re-install. On signal we walk every registered fn (swallowing
16
- * errors), remove our own listeners (so a second Ctrl-C force-exits), and
17
- * `process.exit(130)`.
18
- *
19
- * The handler is idempotent: re-entrant signals while cleanup is in flight
20
- * are dropped. Per-tmp `try/finally` callers should:
21
- * 1. Register the cleanup at the top of `try`.
22
- * 2. Deregister it in `finally` *before* running cleanup themselves so the
23
- * handler doesn't double-fire.
24
- *
25
- * Garbage-collection of orphan dirs (#276): the FIRST `registerCleanup` call
26
- * also sweeps `${AKM_CACHE_DIR}/bench/*` entries older than 6h. This catches
27
- * orphans from prior crashed runs that bypassed `finally`. Subsequent calls
28
- * never re-sweep — the GC is install-once.
29
- */
30
- import * as fs from "node:fs";
31
- import * as path from "node:path";
32
- import { warn } from "../../src/core/warn";
33
- import { benchTmpRoot } from "./tmp";
34
- const registry = {
35
- fns: new Set(),
36
- installed: false,
37
- running: false,
38
- };
39
- /**
40
- * Register a cleanup function. Returns a deregister thunk that removes the
41
- * function from the registry. Calling deregister after the function has
42
- * already run is a no-op.
43
- */
44
- export function registerCleanup(fn) {
45
- registry.fns.add(fn);
46
- installSignalHandlers();
47
- return () => {
48
- registry.fns.delete(fn);
49
- };
50
- }
51
- /** GC threshold for orphan bench tmp dirs: 6 hours in milliseconds. */
52
- const BENCH_TMP_GC_MAX_AGE_MS = 6 * 60 * 60 * 1000;
53
- /**
54
- * Sweep `${AKM_CACHE_DIR}/bench/*` entries whose mtime is older than 6h.
55
- * Best-effort: any individual rmSync failure is swallowed (warned in
56
- * verbose mode) so a permission-bound entry does not kill the install.
57
- *
58
- * Idempotent because it only runs from the first-installer path in
59
- * `installSignalHandlers` — gated by `registry.installed`.
60
- */
61
- function gcOrphanBenchTmp() {
62
- let root;
63
- try {
64
- root = benchTmpRoot();
65
- }
66
- catch {
67
- // If the cache dir cannot be resolved (e.g. HOME unset in a sandboxed
68
- // CI shell), skip GC silently — the bench will fail later on its own.
69
- return;
70
- }
71
- let entries;
72
- try {
73
- entries = fs.readdirSync(root);
74
- }
75
- catch {
76
- // Root does not yet exist or is unreadable — nothing to reap.
77
- return;
78
- }
79
- const cutoff = Date.now() - BENCH_TMP_GC_MAX_AGE_MS;
80
- for (const name of entries) {
81
- const full = path.join(root, name);
82
- let stat;
83
- try {
84
- stat = fs.lstatSync(full);
85
- }
86
- catch {
87
- continue;
88
- }
89
- if (stat.mtimeMs > cutoff)
90
- continue;
91
- try {
92
- fs.rmSync(full, { recursive: true, force: true });
93
- }
94
- catch (err) {
95
- warn(`bench tmp GC: could not remove ${full}: ${err.message}`);
96
- }
97
- }
98
- }
99
- function installSignalHandlers() {
100
- if (registry.installed)
101
- return;
102
- registry.installed = true;
103
- // First-installer GC sweep: reap orphan bench tmp dirs older than 6h.
104
- // Subsequent registerCleanup() calls never re-trigger this — the
105
- // `registry.installed` guard above ensures install-once semantics.
106
- gcOrphanBenchTmp();
107
- const handler = () => {
108
- // Re-entrant signals are dropped — a second Ctrl-C will hit our
109
- // already-removed listeners and the runtime's default handler will
110
- // force-exit. That is the documented escape hatch.
111
- if (registry.running)
112
- return;
113
- registry.running = true;
114
- // Snapshot then drop registrations. We invoke synchronously where
115
- // possible; async fns get fired-and-forget but we still await them so
116
- // the exit doesn't beat the rmdir on slow filesystems.
117
- const fns = [...registry.fns];
118
- registry.fns.clear();
119
- void runAllAndExit(fns);
120
- };
121
- registry.handlerSigint = handler;
122
- registry.handlerSigterm = handler;
123
- process.on("SIGINT", handler);
124
- process.on("SIGTERM", handler);
125
- }
126
- async function runAllAndExit(fns) {
127
- // BUG-H5: wrap the body in try/finally so a synchronous throw outside the
128
- // per-fn try/catch (e.g. an exception thrown by `process.off` on a
129
- // pathological listener list) does not leave `registry.running = true`.
130
- // Without this guard, a subsequent registerCleanup() call would re-install
131
- // listeners but the new handler would short-circuit on the stale flag and
132
- // skip cleanup on the next signal.
133
- try {
134
- for (const fn of fns) {
135
- try {
136
- await fn();
137
- }
138
- catch {
139
- // Best-effort: cleanup must never throw out of the signal path.
140
- }
141
- }
142
- // Remove our listeners so a second Ctrl-C force-exits via the default.
143
- if (registry.handlerSigint)
144
- process.off("SIGINT", registry.handlerSigint);
145
- if (registry.handlerSigterm)
146
- process.off("SIGTERM", registry.handlerSigterm);
147
- registry.installed = false;
148
- registry.handlerSigint = undefined;
149
- registry.handlerSigterm = undefined;
150
- }
151
- finally {
152
- registry.running = false;
153
- // 128 + SIGINT(2) — POSIX convention for signal-induced exits.
154
- process.exit(130);
155
- }
156
- }
157
- // ── Test-only seam ──────────────────────────────────────────────────────────
158
- /**
159
- * Test-only: drive the cleanup path as if a signal arrived, *without*
160
- * calling `process.exit`. Returns a promise that resolves once every
161
- * registered fn has settled. Used by the unit test to assert ordering
162
- * without killing the test process.
163
- *
164
- * Resets the registry to an uninstalled state on completion so subsequent
165
- * tests can re-install handlers cleanly.
166
- */
167
- export async function _drainForTest() {
168
- const fns = [...registry.fns];
169
- registry.fns.clear();
170
- registry.running = true;
171
- for (const fn of fns) {
172
- try {
173
- await fn();
174
- }
175
- catch {
176
- /* swallow */
177
- }
178
- }
179
- if (registry.handlerSigint)
180
- process.off("SIGINT", registry.handlerSigint);
181
- if (registry.handlerSigterm)
182
- process.off("SIGTERM", registry.handlerSigterm);
183
- registry.installed = false;
184
- registry.running = false;
185
- registry.handlerSigint = undefined;
186
- registry.handlerSigterm = undefined;
187
- }
188
- /** Test-only: reset the registry without firing cleanups (for unit setup). */
189
- export function _resetForTest() {
190
- registry.fns.clear();
191
- if (registry.handlerSigint)
192
- process.off("SIGINT", registry.handlerSigint);
193
- if (registry.handlerSigterm)
194
- process.off("SIGTERM", registry.handlerSigterm);
195
- registry.installed = false;
196
- registry.running = false;
197
- registry.handlerSigint = undefined;
198
- registry.handlerSigterm = undefined;
199
- }
200
- /** Test-only: peek at the current registration count. */
201
- export function _registeredCountForTest() {
202
- return registry.fns.size;
203
- }
@@ -1,166 +0,0 @@
1
- /**
2
- * Unit tests for the bench cleanup registry (#267).
3
- *
4
- * The shared registry installs ONE pair of SIGINT/SIGTERM handlers on first
5
- * registration and runs every registered fn when a signal fires. We use the
6
- * `_drainForTest` test seam so the assertions don't have to actually kill
7
- * the test process — but we also exercise the real handler installation
8
- * path to make sure it's idempotent.
9
- */
10
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
11
- import fs from "node:fs";
12
- import { _drainForTest, _registeredCountForTest, _resetForTest, registerCleanup } from "./cleanup";
13
- import { benchMkdtemp, benchTmpRoot } from "./tmp";
14
- beforeEach(() => {
15
- _resetForTest();
16
- });
17
- afterEach(() => {
18
- _resetForTest();
19
- });
20
- describe("registerCleanup (#267)", () => {
21
- test("registers a cleanup fn and increments the count", () => {
22
- expect(_registeredCountForTest()).toBe(0);
23
- registerCleanup(() => { });
24
- expect(_registeredCountForTest()).toBe(1);
25
- });
26
- test("returns a deregister thunk that drops the registration", () => {
27
- const deregister = registerCleanup(() => { });
28
- expect(_registeredCountForTest()).toBe(1);
29
- deregister();
30
- expect(_registeredCountForTest()).toBe(0);
31
- });
32
- test("drainForTest runs every registered cleanup once", async () => {
33
- const calls = [];
34
- registerCleanup(() => {
35
- calls.push("a");
36
- });
37
- registerCleanup(() => {
38
- calls.push("b");
39
- });
40
- registerCleanup(() => {
41
- calls.push("c");
42
- });
43
- await _drainForTest();
44
- expect(calls.sort()).toEqual(["a", "b", "c"]);
45
- // Registry is empty after drain.
46
- expect(_registeredCountForTest()).toBe(0);
47
- });
48
- test("drainForTest swallows errors so one bad fn doesn't block the rest", async () => {
49
- const calls = [];
50
- registerCleanup(() => {
51
- calls.push("first");
52
- });
53
- registerCleanup(() => {
54
- throw new Error("boom");
55
- });
56
- registerCleanup(() => {
57
- calls.push("third");
58
- });
59
- await _drainForTest();
60
- expect(calls.sort()).toEqual(["first", "third"]);
61
- });
62
- test("drainForTest awaits async cleanup fns", async () => {
63
- const calls = [];
64
- registerCleanup(async () => {
65
- await new Promise((resolve) => setTimeout(resolve, 10));
66
- calls.push("async-done");
67
- });
68
- await _drainForTest();
69
- expect(calls).toEqual(["async-done"]);
70
- });
71
- test("idempotent installer: repeated registers do not multiply listeners", () => {
72
- const initialSigint = process.listenerCount("SIGINT");
73
- const initialSigterm = process.listenerCount("SIGTERM");
74
- registerCleanup(() => { });
75
- registerCleanup(() => { });
76
- registerCleanup(() => { });
77
- // Exactly one listener each, no matter how many cleanup fns we add.
78
- expect(process.listenerCount("SIGINT")).toBe(initialSigint + 1);
79
- expect(process.listenerCount("SIGTERM")).toBe(initialSigterm + 1);
80
- });
81
- test("deregister-all leaves the listeners installed (registry is sticky)", () => {
82
- // The contract is: once installed, the handlers stay installed for the
83
- // process lifetime. Subsequent register calls reuse them.
84
- registerCleanup(() => { })();
85
- expect(_registeredCountForTest()).toBe(0);
86
- // Re-register: this MUST NOT add a second pair of listeners.
87
- const initialSigint = process.listenerCount("SIGINT");
88
- registerCleanup(() => { });
89
- expect(process.listenerCount("SIGINT")).toBe(initialSigint);
90
- });
91
- test("deregistered fns do not run on drain", async () => {
92
- const calls = [];
93
- const dereg = registerCleanup(() => {
94
- calls.push("kept");
95
- });
96
- const _wasted = registerCleanup(() => {
97
- calls.push("dropped");
98
- });
99
- _wasted();
100
- void dereg; // keep referenced
101
- await _drainForTest();
102
- expect(calls).toEqual(["kept"]);
103
- });
104
- test("simulated SIGINT path: handler runs cleanup before exit (via drain seam)", async () => {
105
- // We can't actually `process.exit(130)` inside a unit test, so we use the
106
- // drain seam (which mirrors the runAllAndExit path minus the exit call).
107
- // This verifies the OBSERVABLE behaviour the brief asked for: signal →
108
- // every registered cleanup fn ran.
109
- const ran = [];
110
- registerCleanup(() => {
111
- ran.push("rmsync(workspace)");
112
- });
113
- registerCleanup(() => {
114
- ran.push("rmsync(stash)");
115
- });
116
- await _drainForTest();
117
- expect(ran.length).toBe(2);
118
- expect(ran).toContain("rmsync(workspace)");
119
- expect(ran).toContain("rmsync(stash)");
120
- });
121
- test("first registerCleanup sweeps bench tmp entries older than 6h (#276)", () => {
122
- // Ensure root exists before populating.
123
- benchTmpRoot();
124
- // Stale entry: mtime backdated 7h.
125
- const stale = benchMkdtemp("akm-bench-gc-stale-");
126
- const sevenHoursAgo = (Date.now() - 7 * 60 * 60 * 1000) / 1000;
127
- fs.utimesSync(stale, sevenHoursAgo, sevenHoursAgo);
128
- // Fresh entry: untouched mtime (now).
129
- const fresh = benchMkdtemp("akm-bench-gc-fresh-");
130
- expect(fs.existsSync(stale)).toBe(true);
131
- expect(fs.existsSync(fresh)).toBe(true);
132
- // Trigger first-installer GC.
133
- registerCleanup(() => { });
134
- expect(fs.existsSync(stale)).toBe(false);
135
- expect(fs.existsSync(fresh)).toBe(true);
136
- // Cleanup the fresh entry ourselves.
137
- fs.rmSync(fresh, { recursive: true, force: true });
138
- });
139
- test("GC is idempotent — second registerCleanup does not re-sweep", () => {
140
- // Install once with a sentinel registered.
141
- registerCleanup(() => { });
142
- // Now create a stale entry AFTER install. Second registerCleanup must
143
- // NOT sweep it because the GC only runs on first install.
144
- const stale = benchMkdtemp("akm-bench-gc-postinstall-");
145
- const sevenHoursAgo = (Date.now() - 7 * 60 * 60 * 1000) / 1000;
146
- fs.utimesSync(stale, sevenHoursAgo, sevenHoursAgo);
147
- registerCleanup(() => { });
148
- expect(fs.existsSync(stale)).toBe(true);
149
- fs.rmSync(stale, { recursive: true, force: true });
150
- });
151
- test("re-entrant signals during running cleanup are dropped", async () => {
152
- // Drive the registered handler twice in sequence. The second drain
153
- // should observe the cleared registry (running flag flipped) and run
154
- // nothing extra. This protects against a Ctrl-C double-press
155
- // interrupting cleanup mid-run.
156
- const calls = [];
157
- registerCleanup(() => {
158
- calls.push("first");
159
- });
160
- await _drainForTest();
161
- expect(calls).toEqual(["first"]);
162
- // No further fns registered → second drain is a no-op.
163
- await _drainForTest();
164
- expect(calls).toEqual(["first"]);
165
- });
166
- });