akm-cli 0.7.0 → 0.7.2

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 (332) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{src/cli.js → cli.js} +22 -8
  3. package/dist/{src/commands → commands}/installed-stashes.js +1 -1
  4. package/dist/{src/commands → commands}/source-add.js +1 -1
  5. package/dist/{src/core → core}/common.js +16 -1
  6. package/dist/{src/core → core}/config.js +5 -2
  7. package/dist/{src/indexer → indexer}/db-search.js +16 -1
  8. package/dist/{src/indexer → indexer}/graph-extraction.js +5 -3
  9. package/dist/{src/indexer → indexer}/indexer.js +27 -11
  10. package/dist/{src/indexer → indexer}/memory-inference.js +47 -58
  11. package/dist/{src/indexer → indexer}/search-source.js +1 -1
  12. package/dist/{src/llm → llm}/client.js +61 -1
  13. package/dist/{src/llm → llm}/embedder.js +8 -5
  14. package/dist/{src/llm → llm}/embedders/local.js +8 -2
  15. package/dist/{src/llm → llm}/embedders/remote.js +4 -2
  16. package/dist/{src/llm → llm}/graph-extract.js +4 -4
  17. package/dist/llm/memory-infer.js +114 -0
  18. package/dist/{src/llm → llm}/metadata-enhance.js +2 -2
  19. package/dist/{src/output → output}/cli-hints.js +2 -0
  20. package/dist/{src/setup → setup}/setup.js +30 -20
  21. package/dist/sources/providers/website.js +27 -0
  22. package/dist/{src/sources/providers/website.js → sources/website-ingest.js} +38 -51
  23. package/docs/README.md +7 -0
  24. package/docs/migration/release-notes/0.7.0.md +14 -0
  25. package/package.json +11 -8
  26. package/dist/src/llm/memory-infer.js +0 -86
  27. package/dist/tests/add-website-source.test.js +0 -119
  28. package/dist/tests/agent/agent-config-loader.test.js +0 -70
  29. package/dist/tests/agent/agent-config.test.js +0 -221
  30. package/dist/tests/agent/agent-detect.test.js +0 -100
  31. package/dist/tests/agent/agent-spawn.test.js +0 -234
  32. package/dist/tests/agent-output.test.js +0 -186
  33. package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +0 -103
  34. package/dist/tests/architecture/agent-spawn-seam.test.js +0 -193
  35. package/dist/tests/architecture/llm-stateless-seam.test.js +0 -112
  36. package/dist/tests/asset-ref.test.js +0 -192
  37. package/dist/tests/asset-registry.test.js +0 -103
  38. package/dist/tests/asset-spec.test.js +0 -241
  39. package/dist/tests/bench/attribution.test.js +0 -996
  40. package/dist/tests/bench/cleanup-sigint.test.js +0 -83
  41. package/dist/tests/bench/cleanup.js +0 -234
  42. package/dist/tests/bench/cleanup.test.js +0 -166
  43. package/dist/tests/bench/cli.js +0 -1018
  44. package/dist/tests/bench/cli.test.js +0 -445
  45. package/dist/tests/bench/compare.test.js +0 -556
  46. package/dist/tests/bench/corpus.js +0 -317
  47. package/dist/tests/bench/corpus.test.js +0 -258
  48. package/dist/tests/bench/doctor.js +0 -525
  49. package/dist/tests/bench/driver.js +0 -401
  50. package/dist/tests/bench/driver.test.js +0 -584
  51. package/dist/tests/bench/environment.js +0 -233
  52. package/dist/tests/bench/environment.test.js +0 -199
  53. package/dist/tests/bench/evolve-metrics.js +0 -179
  54. package/dist/tests/bench/evolve-metrics.test.js +0 -187
  55. package/dist/tests/bench/evolve.js +0 -647
  56. package/dist/tests/bench/evolve.test.js +0 -624
  57. package/dist/tests/bench/failure-modes.test.js +0 -349
  58. package/dist/tests/bench/feedback-integrity.test.js +0 -457
  59. package/dist/tests/bench/leakage.test.js +0 -228
  60. package/dist/tests/bench/learning-curve.test.js +0 -134
  61. package/dist/tests/bench/metrics.js +0 -2395
  62. package/dist/tests/bench/metrics.test.js +0 -1150
  63. package/dist/tests/bench/no-os-tmpdir-invariant.test.js +0 -43
  64. package/dist/tests/bench/opencode-config.js +0 -194
  65. package/dist/tests/bench/opencode-config.test.js +0 -370
  66. package/dist/tests/bench/report.js +0 -1885
  67. package/dist/tests/bench/report.test.js +0 -1038
  68. package/dist/tests/bench/run-config.js +0 -355
  69. package/dist/tests/bench/run-config.test.js +0 -298
  70. package/dist/tests/bench/run-curate-test.js +0 -32
  71. package/dist/tests/bench/run-failing-tasks.js +0 -56
  72. package/dist/tests/bench/run-full-bench.js +0 -51
  73. package/dist/tests/bench/run-items36-targeted.js +0 -69
  74. package/dist/tests/bench/run-nano-quick.js +0 -42
  75. package/dist/tests/bench/run-waveg-targeted.js +0 -62
  76. package/dist/tests/bench/runner.js +0 -699
  77. package/dist/tests/bench/runner.test.js +0 -958
  78. package/dist/tests/bench/search-bridge.test.js +0 -331
  79. package/dist/tests/bench/tmp.js +0 -131
  80. package/dist/tests/bench/trajectory.js +0 -116
  81. package/dist/tests/bench/trajectory.test.js +0 -127
  82. package/dist/tests/bench/verifier.js +0 -114
  83. package/dist/tests/bench/verifier.test.js +0 -118
  84. package/dist/tests/bench/workflow-evaluator.js +0 -557
  85. package/dist/tests/bench/workflow-evaluator.test.js +0 -421
  86. package/dist/tests/bench/workflow-spec.js +0 -345
  87. package/dist/tests/bench/workflow-spec.test.js +0 -363
  88. package/dist/tests/bench/workflow-trace.js +0 -472
  89. package/dist/tests/bench/workflow-trace.test.js +0 -254
  90. package/dist/tests/benchmark-search-quality.js +0 -536
  91. package/dist/tests/benchmark-suite.js +0 -1441
  92. package/dist/tests/capture-cli.test.js +0 -112
  93. package/dist/tests/cli-errors.test.js +0 -204
  94. package/dist/tests/commands/events.test.js +0 -370
  95. package/dist/tests/commands/history.test.js +0 -418
  96. package/dist/tests/commands/import.test.js +0 -103
  97. package/dist/tests/commands/proposal-cli.test.js +0 -209
  98. package/dist/tests/commands/reflect-propose-cli.test.js +0 -333
  99. package/dist/tests/commands/remember.test.js +0 -97
  100. package/dist/tests/commands/scope-flags.test.js +0 -300
  101. package/dist/tests/commands/search.test.js +0 -537
  102. package/dist/tests/commands/show-indexer-parity.test.js +0 -117
  103. package/dist/tests/commands/show.test.js +0 -294
  104. package/dist/tests/common.test.js +0 -266
  105. package/dist/tests/completions.test.js +0 -142
  106. package/dist/tests/config-cli.test.js +0 -193
  107. package/dist/tests/config-llm-features.test.js +0 -139
  108. package/dist/tests/config.test.js +0 -569
  109. package/dist/tests/contracts/migration-baseline.test.js +0 -43
  110. package/dist/tests/contracts/reflect-propose-envelope.test.js +0 -139
  111. package/dist/tests/contracts/spec-helpers.js +0 -46
  112. package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +0 -228
  113. package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +0 -56
  114. package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +0 -34
  115. package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +0 -94
  116. package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +0 -39
  117. package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +0 -44
  118. package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +0 -47
  119. package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +0 -40
  120. package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +0 -58
  121. package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +0 -34
  122. package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +0 -75
  123. package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +0 -36
  124. package/dist/tests/core/write-source.test.js +0 -366
  125. package/dist/tests/curate-command.test.js +0 -87
  126. package/dist/tests/db-scoring.test.js +0 -201
  127. package/dist/tests/db.test.js +0 -654
  128. package/dist/tests/distill-cli-flag.test.js +0 -208
  129. package/dist/tests/distill.test.js +0 -515
  130. package/dist/tests/docker-install.test.js +0 -120
  131. package/dist/tests/e2e.test.js +0 -1419
  132. package/dist/tests/embedder.test.js +0 -340
  133. package/dist/tests/embedding-model-config.test.js +0 -379
  134. package/dist/tests/feedback-command.test.js +0 -172
  135. package/dist/tests/file-context.test.js +0 -552
  136. package/dist/tests/fixtures/scripts/git/summarize-diff.js +0 -9
  137. package/dist/tests/fixtures/scripts/lint/eslint-check.js +0 -7
  138. package/dist/tests/fixtures/stashes/load.js +0 -166
  139. package/dist/tests/fixtures/stashes/load.test.js +0 -97
  140. package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +0 -12
  141. package/dist/tests/frontmatter.test.js +0 -190
  142. package/dist/tests/fts-field-weighting.test.js +0 -254
  143. package/dist/tests/fuzzy-search.test.js +0 -230
  144. package/dist/tests/git-provider-clone.test.js +0 -45
  145. package/dist/tests/github.test.js +0 -161
  146. package/dist/tests/graph-boost-ranking.test.js +0 -305
  147. package/dist/tests/graph-extraction.test.js +0 -282
  148. package/dist/tests/helpers/usage-events.js +0 -8
  149. package/dist/tests/index-pass-llm.test.js +0 -161
  150. package/dist/tests/indexer.test.js +0 -570
  151. package/dist/tests/info-command.test.js +0 -166
  152. package/dist/tests/init.test.js +0 -69
  153. package/dist/tests/install-script.test.js +0 -246
  154. package/dist/tests/integration/agent-real-profile.test.js +0 -94
  155. package/dist/tests/issue-36-repro.test.js +0 -304
  156. package/dist/tests/issues-191-194.test.js +0 -160
  157. package/dist/tests/lesson-lint.test.js +0 -111
  158. package/dist/tests/llm-client.test.js +0 -115
  159. package/dist/tests/llm-feature-gate.test.js +0 -151
  160. package/dist/tests/llm.test.js +0 -139
  161. package/dist/tests/lockfile.test.js +0 -216
  162. package/dist/tests/manifest.test.js +0 -205
  163. package/dist/tests/markdown.test.js +0 -126
  164. package/dist/tests/matchers-unit.test.js +0 -189
  165. package/dist/tests/memory-inference.test.js +0 -299
  166. package/dist/tests/merge-scoring.test.js +0 -136
  167. package/dist/tests/metadata.test.js +0 -313
  168. package/dist/tests/migration-help.test.js +0 -89
  169. package/dist/tests/origin-resolve.test.js +0 -124
  170. package/dist/tests/output-baseline.test.js +0 -218
  171. package/dist/tests/output-shapes-unit.test.js +0 -478
  172. package/dist/tests/parallel-search.test.js +0 -272
  173. package/dist/tests/parameter-metadata.test.js +0 -365
  174. package/dist/tests/paths.test.js +0 -177
  175. package/dist/tests/progressive-disclosure.test.js +0 -280
  176. package/dist/tests/proposals.test.js +0 -279
  177. package/dist/tests/proposed-quality.test.js +0 -271
  178. package/dist/tests/provider-registry.test.js +0 -32
  179. package/dist/tests/ranking-regression.test.js +0 -548
  180. package/dist/tests/reflect-propose.test.js +0 -455
  181. package/dist/tests/registry-build-index.test.js +0 -394
  182. package/dist/tests/registry-cli.test.js +0 -290
  183. package/dist/tests/registry-index-v2.test.js +0 -430
  184. package/dist/tests/registry-install.test.js +0 -728
  185. package/dist/tests/registry-providers/parity.test.js +0 -189
  186. package/dist/tests/registry-providers/skills-sh.test.js +0 -309
  187. package/dist/tests/registry-providers/static-index.test.js +0 -238
  188. package/dist/tests/registry-resolve.test.js +0 -126
  189. package/dist/tests/registry-search.test.js +0 -923
  190. package/dist/tests/remember-frontmatter.test.js +0 -378
  191. package/dist/tests/remember-unit.test.js +0 -123
  192. package/dist/tests/ripgrep-install.test.js +0 -251
  193. package/dist/tests/ripgrep-resolve.test.js +0 -108
  194. package/dist/tests/ripgrep.test.js +0 -163
  195. package/dist/tests/save-command.test.js +0 -94
  196. package/dist/tests/save-trust-qa-fixes.test.js +0 -270
  197. package/dist/tests/scoring-pipeline.test.js +0 -648
  198. package/dist/tests/search-include-proposed-cli.test.js +0 -118
  199. package/dist/tests/self-update.test.js +0 -442
  200. package/dist/tests/semantic-search-e2e.test.js +0 -512
  201. package/dist/tests/semantic-status.test.js +0 -471
  202. package/dist/tests/setup-run.integration.js +0 -877
  203. package/dist/tests/setup-wizard.test.js +0 -198
  204. package/dist/tests/setup.test.js +0 -131
  205. package/dist/tests/source-add.test.js +0 -11
  206. package/dist/tests/source-clone.test.js +0 -254
  207. package/dist/tests/source-manage.test.js +0 -366
  208. package/dist/tests/source-providers/filesystem.test.js +0 -82
  209. package/dist/tests/source-providers/git.test.js +0 -252
  210. package/dist/tests/source-providers/website.test.js +0 -128
  211. package/dist/tests/source-qa-fixes.test.js +0 -286
  212. package/dist/tests/source-registry.test.js +0 -350
  213. package/dist/tests/source-resolve.test.js +0 -100
  214. package/dist/tests/source-source.test.js +0 -281
  215. package/dist/tests/source.test.js +0 -533
  216. package/dist/tests/tar-utils-scan.test.js +0 -73
  217. package/dist/tests/toggle-components.test.js +0 -73
  218. package/dist/tests/usage-telemetry.test.js +0 -265
  219. package/dist/tests/utility-scoring.test.js +0 -558
  220. package/dist/tests/vault-load-error.test.js +0 -78
  221. package/dist/tests/vault-qa-fixes.test.js +0 -194
  222. package/dist/tests/vault.test.js +0 -429
  223. package/dist/tests/vector-search.test.js +0 -608
  224. package/dist/tests/walker.test.js +0 -252
  225. package/dist/tests/wave2-cluster-bc.test.js +0 -228
  226. package/dist/tests/wave2-cluster-d.test.js +0 -180
  227. package/dist/tests/wave2-cluster-e.test.js +0 -179
  228. package/dist/tests/wiki-qa-fixes.test.js +0 -270
  229. package/dist/tests/wiki.test.js +0 -529
  230. package/dist/tests/workflow-cli.test.js +0 -271
  231. package/dist/tests/workflow-markdown.test.js +0 -171
  232. package/dist/tests/workflow-path-escape.test.js +0 -132
  233. package/dist/tests/workflow-qa-fixes.test.js +0 -395
  234. package/dist/tests/workflows/indexer-rejection.test.js +0 -213
  235. /package/dist/{src/commands → commands}/completions.js +0 -0
  236. /package/dist/{src/commands → commands}/config-cli.js +0 -0
  237. /package/dist/{src/commands → commands}/curate.js +0 -0
  238. /package/dist/{src/commands → commands}/distill.js +0 -0
  239. /package/dist/{src/commands → commands}/events.js +0 -0
  240. /package/dist/{src/commands → commands}/history.js +0 -0
  241. /package/dist/{src/commands → commands}/info.js +0 -0
  242. /package/dist/{src/commands → commands}/init.js +0 -0
  243. /package/dist/{src/commands → commands}/install-audit.js +0 -0
  244. /package/dist/{src/commands → commands}/migration-help.js +0 -0
  245. /package/dist/{src/commands → commands}/proposal.js +0 -0
  246. /package/dist/{src/commands → commands}/propose.js +0 -0
  247. /package/dist/{src/commands → commands}/reflect.js +0 -0
  248. /package/dist/{src/commands → commands}/registry-search.js +0 -0
  249. /package/dist/{src/commands → commands}/remember.js +0 -0
  250. /package/dist/{src/commands → commands}/search.js +0 -0
  251. /package/dist/{src/commands → commands}/self-update.js +0 -0
  252. /package/dist/{src/commands → commands}/show.js +0 -0
  253. /package/dist/{src/commands → commands}/source-clone.js +0 -0
  254. /package/dist/{src/commands → commands}/source-manage.js +0 -0
  255. /package/dist/{src/commands → commands}/vault.js +0 -0
  256. /package/dist/{src/core → core}/asset-ref.js +0 -0
  257. /package/dist/{src/core → core}/asset-registry.js +0 -0
  258. /package/dist/{src/core → core}/asset-spec.js +0 -0
  259. /package/dist/{src/core → core}/errors.js +0 -0
  260. /package/dist/{src/core → core}/events.js +0 -0
  261. /package/dist/{src/core → core}/frontmatter.js +0 -0
  262. /package/dist/{src/core → core}/lesson-lint.js +0 -0
  263. /package/dist/{src/core → core}/markdown.js +0 -0
  264. /package/dist/{src/core → core}/paths.js +0 -0
  265. /package/dist/{src/core → core}/proposals.js +0 -0
  266. /package/dist/{src/core → core}/warn.js +0 -0
  267. /package/dist/{src/core → core}/write-source.js +0 -0
  268. /package/dist/{src/indexer → indexer}/db.js +0 -0
  269. /package/dist/{src/indexer → indexer}/file-context.js +0 -0
  270. /package/dist/{src/indexer → indexer}/graph-boost.js +0 -0
  271. /package/dist/{src/indexer → indexer}/manifest.js +0 -0
  272. /package/dist/{src/indexer → indexer}/matchers.js +0 -0
  273. /package/dist/{src/indexer → indexer}/metadata.js +0 -0
  274. /package/dist/{src/indexer → indexer}/search-fields.js +0 -0
  275. /package/dist/{src/indexer → indexer}/semantic-status.js +0 -0
  276. /package/dist/{src/indexer → indexer}/usage-events.js +0 -0
  277. /package/dist/{src/indexer → indexer}/walker.js +0 -0
  278. /package/dist/{src/integrations → integrations}/agent/config.js +0 -0
  279. /package/dist/{src/integrations → integrations}/agent/detect.js +0 -0
  280. /package/dist/{src/integrations → integrations}/agent/index.js +0 -0
  281. /package/dist/{src/integrations → integrations}/agent/profiles.js +0 -0
  282. /package/dist/{src/integrations → integrations}/agent/prompts.js +0 -0
  283. /package/dist/{src/integrations → integrations}/agent/spawn.js +0 -0
  284. /package/dist/{src/integrations → integrations}/github.js +0 -0
  285. /package/dist/{src/integrations → integrations}/lockfile.js +0 -0
  286. /package/dist/{src/llm → llm}/embedders/cache.js +0 -0
  287. /package/dist/{src/llm → llm}/embedders/types.js +0 -0
  288. /package/dist/{src/llm → llm}/feature-gate.js +0 -0
  289. /package/dist/{src/llm → llm}/index-passes.js +0 -0
  290. /package/dist/{src/output → output}/context.js +0 -0
  291. /package/dist/{src/output → output}/renderers.js +0 -0
  292. /package/dist/{src/output → output}/shapes.js +0 -0
  293. /package/dist/{src/output → output}/text.js +0 -0
  294. /package/dist/{src/registry → registry}/build-index.js +0 -0
  295. /package/dist/{src/registry → registry}/create-provider-registry.js +0 -0
  296. /package/dist/{src/registry → registry}/factory.js +0 -0
  297. /package/dist/{src/registry → registry}/origin-resolve.js +0 -0
  298. /package/dist/{src/registry → registry}/providers/index.js +0 -0
  299. /package/dist/{src/registry → registry}/providers/skills-sh.js +0 -0
  300. /package/dist/{src/registry → registry}/providers/static-index.js +0 -0
  301. /package/dist/{src/registry → registry}/providers/types.js +0 -0
  302. /package/dist/{src/registry → registry}/resolve.js +0 -0
  303. /package/dist/{src/registry → registry}/types.js +0 -0
  304. /package/dist/{src/setup → setup}/detect.js +0 -0
  305. /package/dist/{src/setup → setup}/ripgrep-install.js +0 -0
  306. /package/dist/{src/setup → setup}/ripgrep-resolve.js +0 -0
  307. /package/dist/{src/setup → setup}/steps.js +0 -0
  308. /package/dist/{src/sources → sources}/include.js +0 -0
  309. /package/dist/{src/sources → sources}/provider-factory.js +0 -0
  310. /package/dist/{src/sources → sources}/provider.js +0 -0
  311. /package/dist/{src/sources → sources}/providers/filesystem.js +0 -0
  312. /package/dist/{src/sources → sources}/providers/git.js +0 -0
  313. /package/dist/{src/sources → sources}/providers/index.js +0 -0
  314. /package/dist/{src/sources → sources}/providers/install-types.js +0 -0
  315. /package/dist/{src/sources → sources}/providers/npm.js +0 -0
  316. /package/dist/{src/sources → sources}/providers/provider-utils.js +0 -0
  317. /package/dist/{src/sources → sources}/providers/sync-from-ref.js +0 -0
  318. /package/dist/{src/sources → sources}/providers/tar-utils.js +0 -0
  319. /package/dist/{src/sources → sources}/resolve.js +0 -0
  320. /package/dist/{src/sources → sources}/types.js +0 -0
  321. /package/dist/{src/templates → templates}/wiki-templates.js +0 -0
  322. /package/dist/{src/version.js → version.js} +0 -0
  323. /package/dist/{src/wiki → wiki}/wiki.js +0 -0
  324. /package/dist/{src/workflows → workflows}/authoring.js +0 -0
  325. /package/dist/{src/workflows → workflows}/cli.js +0 -0
  326. /package/dist/{src/workflows → workflows}/db.js +0 -0
  327. /package/dist/{src/workflows → workflows}/document-cache.js +0 -0
  328. /package/dist/{src/workflows → workflows}/parser.js +0 -0
  329. /package/dist/{src/workflows → workflows}/renderer.js +0 -0
  330. /package/dist/{src/workflows → workflows}/runs.js +0 -0
  331. /package/dist/{src/workflows → workflows}/schema.js +0 -0
  332. /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,234 +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
- /**
35
- * Register a process-group kill for a spawned opencode PID.
36
- *
37
- * On SIGINT/SIGTERM the bench driver must kill the entire opencode process
38
- * group (not just the node wrapper) so .opencode children don't become orphans
39
- * that keep pipes open and block subsequent runs.
40
- *
41
- * Call this immediately after spawning opencode. Returns a deregister thunk
42
- * that should be called once the process has exited (in the run's finally
43
- * block).
44
- *
45
- * The SIGKILL is sent to the process group (`-pid`) if available, falling back
46
- * to the individual PID for environments where group-kill is unavailable.
47
- */
48
- export function registerProcessGroupCleanup(pid) {
49
- const fn = () => {
50
- try {
51
- process.kill(-pid, "SIGKILL");
52
- }
53
- catch {
54
- // Process group may not exist (process already exited or pid unavailable).
55
- try {
56
- process.kill(pid, "SIGKILL");
57
- }
58
- catch {
59
- /* already gone */
60
- }
61
- }
62
- };
63
- return registerCleanup(fn);
64
- }
65
- const registry = {
66
- fns: new Set(),
67
- installed: false,
68
- running: false,
69
- };
70
- /**
71
- * Register a cleanup function. Returns a deregister thunk that removes the
72
- * function from the registry. Calling deregister after the function has
73
- * already run is a no-op.
74
- */
75
- export function registerCleanup(fn) {
76
- registry.fns.add(fn);
77
- installSignalHandlers();
78
- return () => {
79
- registry.fns.delete(fn);
80
- };
81
- }
82
- /** GC threshold for orphan bench tmp dirs: 6 hours in milliseconds. */
83
- const BENCH_TMP_GC_MAX_AGE_MS = 6 * 60 * 60 * 1000;
84
- /**
85
- * Sweep `${AKM_CACHE_DIR}/bench/*` entries whose mtime is older than 6h.
86
- * Best-effort: any individual rmSync failure is swallowed (warned in
87
- * verbose mode) so a permission-bound entry does not kill the install.
88
- *
89
- * Idempotent because it only runs from the first-installer path in
90
- * `installSignalHandlers` — gated by `registry.installed`.
91
- */
92
- function gcOrphanBenchTmp() {
93
- let root;
94
- try {
95
- root = benchTmpRoot();
96
- }
97
- catch {
98
- // If the cache dir cannot be resolved (e.g. HOME unset in a sandboxed
99
- // CI shell), skip GC silently — the bench will fail later on its own.
100
- return;
101
- }
102
- let entries;
103
- try {
104
- entries = fs.readdirSync(root);
105
- }
106
- catch {
107
- // Root does not yet exist or is unreadable — nothing to reap.
108
- return;
109
- }
110
- const cutoff = Date.now() - BENCH_TMP_GC_MAX_AGE_MS;
111
- for (const name of entries) {
112
- const full = path.join(root, name);
113
- let stat;
114
- try {
115
- stat = fs.lstatSync(full);
116
- }
117
- catch {
118
- continue;
119
- }
120
- if (stat.mtimeMs > cutoff)
121
- continue;
122
- try {
123
- fs.rmSync(full, { recursive: true, force: true });
124
- }
125
- catch (err) {
126
- warn(`bench tmp GC: could not remove ${full}: ${err.message}`);
127
- }
128
- }
129
- }
130
- function installSignalHandlers() {
131
- if (registry.installed)
132
- return;
133
- registry.installed = true;
134
- // First-installer GC sweep: reap orphan bench tmp dirs older than 6h.
135
- // Subsequent registerCleanup() calls never re-trigger this — the
136
- // `registry.installed` guard above ensures install-once semantics.
137
- gcOrphanBenchTmp();
138
- const handler = () => {
139
- // Re-entrant signals are dropped — a second Ctrl-C will hit our
140
- // already-removed listeners and the runtime's default handler will
141
- // force-exit. That is the documented escape hatch.
142
- if (registry.running)
143
- return;
144
- registry.running = true;
145
- // Snapshot then drop registrations. We invoke synchronously where
146
- // possible; async fns get fired-and-forget but we still await them so
147
- // the exit doesn't beat the rmdir on slow filesystems.
148
- const fns = [...registry.fns];
149
- registry.fns.clear();
150
- void runAllAndExit(fns);
151
- };
152
- registry.handlerSigint = handler;
153
- registry.handlerSigterm = handler;
154
- process.on("SIGINT", handler);
155
- process.on("SIGTERM", handler);
156
- }
157
- async function runAllAndExit(fns) {
158
- // BUG-H5: wrap the body in try/finally so a synchronous throw outside the
159
- // per-fn try/catch (e.g. an exception thrown by `process.off` on a
160
- // pathological listener list) does not leave `registry.running = true`.
161
- // Without this guard, a subsequent registerCleanup() call would re-install
162
- // listeners but the new handler would short-circuit on the stale flag and
163
- // skip cleanup on the next signal.
164
- try {
165
- for (const fn of fns) {
166
- try {
167
- await fn();
168
- }
169
- catch {
170
- // Best-effort: cleanup must never throw out of the signal path.
171
- }
172
- }
173
- // Remove our listeners so a second Ctrl-C force-exits via the default.
174
- if (registry.handlerSigint)
175
- process.off("SIGINT", registry.handlerSigint);
176
- if (registry.handlerSigterm)
177
- process.off("SIGTERM", registry.handlerSigterm);
178
- registry.installed = false;
179
- registry.handlerSigint = undefined;
180
- registry.handlerSigterm = undefined;
181
- }
182
- finally {
183
- registry.running = false;
184
- // 128 + SIGINT(2) — POSIX convention for signal-induced exits.
185
- process.exit(130);
186
- }
187
- }
188
- // ── Test-only seam ──────────────────────────────────────────────────────────
189
- /**
190
- * Test-only: drive the cleanup path as if a signal arrived, *without*
191
- * calling `process.exit`. Returns a promise that resolves once every
192
- * registered fn has settled. Used by the unit test to assert ordering
193
- * without killing the test process.
194
- *
195
- * Resets the registry to an uninstalled state on completion so subsequent
196
- * tests can re-install handlers cleanly.
197
- */
198
- export async function _drainForTest() {
199
- const fns = [...registry.fns];
200
- registry.fns.clear();
201
- registry.running = true;
202
- for (const fn of fns) {
203
- try {
204
- await fn();
205
- }
206
- catch {
207
- /* swallow */
208
- }
209
- }
210
- if (registry.handlerSigint)
211
- process.off("SIGINT", registry.handlerSigint);
212
- if (registry.handlerSigterm)
213
- process.off("SIGTERM", registry.handlerSigterm);
214
- registry.installed = false;
215
- registry.running = false;
216
- registry.handlerSigint = undefined;
217
- registry.handlerSigterm = undefined;
218
- }
219
- /** Test-only: reset the registry without firing cleanups (for unit setup). */
220
- export function _resetForTest() {
221
- registry.fns.clear();
222
- if (registry.handlerSigint)
223
- process.off("SIGINT", registry.handlerSigint);
224
- if (registry.handlerSigterm)
225
- process.off("SIGTERM", registry.handlerSigterm);
226
- registry.installed = false;
227
- registry.running = false;
228
- registry.handlerSigint = undefined;
229
- registry.handlerSigterm = undefined;
230
- }
231
- /** Test-only: peek at the current registration count. */
232
- export function _registeredCountForTest() {
233
- return registry.fns.size;
234
- }
@@ -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
- });