akm-cli 0.7.0 → 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 (327) hide show
  1. package/package.json +8 -8
  2. package/dist/tests/add-website-source.test.js +0 -119
  3. package/dist/tests/agent/agent-config-loader.test.js +0 -70
  4. package/dist/tests/agent/agent-config.test.js +0 -221
  5. package/dist/tests/agent/agent-detect.test.js +0 -100
  6. package/dist/tests/agent/agent-spawn.test.js +0 -234
  7. package/dist/tests/agent-output.test.js +0 -186
  8. package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +0 -103
  9. package/dist/tests/architecture/agent-spawn-seam.test.js +0 -193
  10. package/dist/tests/architecture/llm-stateless-seam.test.js +0 -112
  11. package/dist/tests/asset-ref.test.js +0 -192
  12. package/dist/tests/asset-registry.test.js +0 -103
  13. package/dist/tests/asset-spec.test.js +0 -241
  14. package/dist/tests/bench/attribution.test.js +0 -996
  15. package/dist/tests/bench/cleanup-sigint.test.js +0 -83
  16. package/dist/tests/bench/cleanup.js +0 -234
  17. package/dist/tests/bench/cleanup.test.js +0 -166
  18. package/dist/tests/bench/cli.js +0 -1018
  19. package/dist/tests/bench/cli.test.js +0 -445
  20. package/dist/tests/bench/compare.test.js +0 -556
  21. package/dist/tests/bench/corpus.js +0 -317
  22. package/dist/tests/bench/corpus.test.js +0 -258
  23. package/dist/tests/bench/doctor.js +0 -525
  24. package/dist/tests/bench/driver.js +0 -401
  25. package/dist/tests/bench/driver.test.js +0 -584
  26. package/dist/tests/bench/environment.js +0 -233
  27. package/dist/tests/bench/environment.test.js +0 -199
  28. package/dist/tests/bench/evolve-metrics.js +0 -179
  29. package/dist/tests/bench/evolve-metrics.test.js +0 -187
  30. package/dist/tests/bench/evolve.js +0 -647
  31. package/dist/tests/bench/evolve.test.js +0 -624
  32. package/dist/tests/bench/failure-modes.test.js +0 -349
  33. package/dist/tests/bench/feedback-integrity.test.js +0 -457
  34. package/dist/tests/bench/leakage.test.js +0 -228
  35. package/dist/tests/bench/learning-curve.test.js +0 -134
  36. package/dist/tests/bench/metrics.js +0 -2395
  37. package/dist/tests/bench/metrics.test.js +0 -1150
  38. package/dist/tests/bench/no-os-tmpdir-invariant.test.js +0 -43
  39. package/dist/tests/bench/opencode-config.js +0 -194
  40. package/dist/tests/bench/opencode-config.test.js +0 -370
  41. package/dist/tests/bench/report.js +0 -1885
  42. package/dist/tests/bench/report.test.js +0 -1038
  43. package/dist/tests/bench/run-config.js +0 -355
  44. package/dist/tests/bench/run-config.test.js +0 -298
  45. package/dist/tests/bench/run-curate-test.js +0 -32
  46. package/dist/tests/bench/run-failing-tasks.js +0 -56
  47. package/dist/tests/bench/run-full-bench.js +0 -51
  48. package/dist/tests/bench/run-items36-targeted.js +0 -69
  49. package/dist/tests/bench/run-nano-quick.js +0 -42
  50. package/dist/tests/bench/run-waveg-targeted.js +0 -62
  51. package/dist/tests/bench/runner.js +0 -699
  52. package/dist/tests/bench/runner.test.js +0 -958
  53. package/dist/tests/bench/search-bridge.test.js +0 -331
  54. package/dist/tests/bench/tmp.js +0 -131
  55. package/dist/tests/bench/trajectory.js +0 -116
  56. package/dist/tests/bench/trajectory.test.js +0 -127
  57. package/dist/tests/bench/verifier.js +0 -114
  58. package/dist/tests/bench/verifier.test.js +0 -118
  59. package/dist/tests/bench/workflow-evaluator.js +0 -557
  60. package/dist/tests/bench/workflow-evaluator.test.js +0 -421
  61. package/dist/tests/bench/workflow-spec.js +0 -345
  62. package/dist/tests/bench/workflow-spec.test.js +0 -363
  63. package/dist/tests/bench/workflow-trace.js +0 -472
  64. package/dist/tests/bench/workflow-trace.test.js +0 -254
  65. package/dist/tests/benchmark-search-quality.js +0 -536
  66. package/dist/tests/benchmark-suite.js +0 -1441
  67. package/dist/tests/capture-cli.test.js +0 -112
  68. package/dist/tests/cli-errors.test.js +0 -204
  69. package/dist/tests/commands/events.test.js +0 -370
  70. package/dist/tests/commands/history.test.js +0 -418
  71. package/dist/tests/commands/import.test.js +0 -103
  72. package/dist/tests/commands/proposal-cli.test.js +0 -209
  73. package/dist/tests/commands/reflect-propose-cli.test.js +0 -333
  74. package/dist/tests/commands/remember.test.js +0 -97
  75. package/dist/tests/commands/scope-flags.test.js +0 -300
  76. package/dist/tests/commands/search.test.js +0 -537
  77. package/dist/tests/commands/show-indexer-parity.test.js +0 -117
  78. package/dist/tests/commands/show.test.js +0 -294
  79. package/dist/tests/common.test.js +0 -266
  80. package/dist/tests/completions.test.js +0 -142
  81. package/dist/tests/config-cli.test.js +0 -193
  82. package/dist/tests/config-llm-features.test.js +0 -139
  83. package/dist/tests/config.test.js +0 -569
  84. package/dist/tests/contracts/migration-baseline.test.js +0 -43
  85. package/dist/tests/contracts/reflect-propose-envelope.test.js +0 -139
  86. package/dist/tests/contracts/spec-helpers.js +0 -46
  87. package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +0 -228
  88. package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +0 -56
  89. package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +0 -34
  90. package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +0 -94
  91. package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +0 -39
  92. package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +0 -44
  93. package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +0 -47
  94. package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +0 -40
  95. package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +0 -58
  96. package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +0 -34
  97. package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +0 -75
  98. package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +0 -36
  99. package/dist/tests/core/write-source.test.js +0 -366
  100. package/dist/tests/curate-command.test.js +0 -87
  101. package/dist/tests/db-scoring.test.js +0 -201
  102. package/dist/tests/db.test.js +0 -654
  103. package/dist/tests/distill-cli-flag.test.js +0 -208
  104. package/dist/tests/distill.test.js +0 -515
  105. package/dist/tests/docker-install.test.js +0 -120
  106. package/dist/tests/e2e.test.js +0 -1419
  107. package/dist/tests/embedder.test.js +0 -340
  108. package/dist/tests/embedding-model-config.test.js +0 -379
  109. package/dist/tests/feedback-command.test.js +0 -172
  110. package/dist/tests/file-context.test.js +0 -552
  111. package/dist/tests/fixtures/scripts/git/summarize-diff.js +0 -9
  112. package/dist/tests/fixtures/scripts/lint/eslint-check.js +0 -7
  113. package/dist/tests/fixtures/stashes/load.js +0 -166
  114. package/dist/tests/fixtures/stashes/load.test.js +0 -97
  115. package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +0 -12
  116. package/dist/tests/frontmatter.test.js +0 -190
  117. package/dist/tests/fts-field-weighting.test.js +0 -254
  118. package/dist/tests/fuzzy-search.test.js +0 -230
  119. package/dist/tests/git-provider-clone.test.js +0 -45
  120. package/dist/tests/github.test.js +0 -161
  121. package/dist/tests/graph-boost-ranking.test.js +0 -305
  122. package/dist/tests/graph-extraction.test.js +0 -282
  123. package/dist/tests/helpers/usage-events.js +0 -8
  124. package/dist/tests/index-pass-llm.test.js +0 -161
  125. package/dist/tests/indexer.test.js +0 -570
  126. package/dist/tests/info-command.test.js +0 -166
  127. package/dist/tests/init.test.js +0 -69
  128. package/dist/tests/install-script.test.js +0 -246
  129. package/dist/tests/integration/agent-real-profile.test.js +0 -94
  130. package/dist/tests/issue-36-repro.test.js +0 -304
  131. package/dist/tests/issues-191-194.test.js +0 -160
  132. package/dist/tests/lesson-lint.test.js +0 -111
  133. package/dist/tests/llm-client.test.js +0 -115
  134. package/dist/tests/llm-feature-gate.test.js +0 -151
  135. package/dist/tests/llm.test.js +0 -139
  136. package/dist/tests/lockfile.test.js +0 -216
  137. package/dist/tests/manifest.test.js +0 -205
  138. package/dist/tests/markdown.test.js +0 -126
  139. package/dist/tests/matchers-unit.test.js +0 -189
  140. package/dist/tests/memory-inference.test.js +0 -299
  141. package/dist/tests/merge-scoring.test.js +0 -136
  142. package/dist/tests/metadata.test.js +0 -313
  143. package/dist/tests/migration-help.test.js +0 -89
  144. package/dist/tests/origin-resolve.test.js +0 -124
  145. package/dist/tests/output-baseline.test.js +0 -218
  146. package/dist/tests/output-shapes-unit.test.js +0 -478
  147. package/dist/tests/parallel-search.test.js +0 -272
  148. package/dist/tests/parameter-metadata.test.js +0 -365
  149. package/dist/tests/paths.test.js +0 -177
  150. package/dist/tests/progressive-disclosure.test.js +0 -280
  151. package/dist/tests/proposals.test.js +0 -279
  152. package/dist/tests/proposed-quality.test.js +0 -271
  153. package/dist/tests/provider-registry.test.js +0 -32
  154. package/dist/tests/ranking-regression.test.js +0 -548
  155. package/dist/tests/reflect-propose.test.js +0 -455
  156. package/dist/tests/registry-build-index.test.js +0 -394
  157. package/dist/tests/registry-cli.test.js +0 -290
  158. package/dist/tests/registry-index-v2.test.js +0 -430
  159. package/dist/tests/registry-install.test.js +0 -728
  160. package/dist/tests/registry-providers/parity.test.js +0 -189
  161. package/dist/tests/registry-providers/skills-sh.test.js +0 -309
  162. package/dist/tests/registry-providers/static-index.test.js +0 -238
  163. package/dist/tests/registry-resolve.test.js +0 -126
  164. package/dist/tests/registry-search.test.js +0 -923
  165. package/dist/tests/remember-frontmatter.test.js +0 -378
  166. package/dist/tests/remember-unit.test.js +0 -123
  167. package/dist/tests/ripgrep-install.test.js +0 -251
  168. package/dist/tests/ripgrep-resolve.test.js +0 -108
  169. package/dist/tests/ripgrep.test.js +0 -163
  170. package/dist/tests/save-command.test.js +0 -94
  171. package/dist/tests/save-trust-qa-fixes.test.js +0 -270
  172. package/dist/tests/scoring-pipeline.test.js +0 -648
  173. package/dist/tests/search-include-proposed-cli.test.js +0 -118
  174. package/dist/tests/self-update.test.js +0 -442
  175. package/dist/tests/semantic-search-e2e.test.js +0 -512
  176. package/dist/tests/semantic-status.test.js +0 -471
  177. package/dist/tests/setup-run.integration.js +0 -877
  178. package/dist/tests/setup-wizard.test.js +0 -198
  179. package/dist/tests/setup.test.js +0 -131
  180. package/dist/tests/source-add.test.js +0 -11
  181. package/dist/tests/source-clone.test.js +0 -254
  182. package/dist/tests/source-manage.test.js +0 -366
  183. package/dist/tests/source-providers/filesystem.test.js +0 -82
  184. package/dist/tests/source-providers/git.test.js +0 -252
  185. package/dist/tests/source-providers/website.test.js +0 -128
  186. package/dist/tests/source-qa-fixes.test.js +0 -286
  187. package/dist/tests/source-registry.test.js +0 -350
  188. package/dist/tests/source-resolve.test.js +0 -100
  189. package/dist/tests/source-source.test.js +0 -281
  190. package/dist/tests/source.test.js +0 -533
  191. package/dist/tests/tar-utils-scan.test.js +0 -73
  192. package/dist/tests/toggle-components.test.js +0 -73
  193. package/dist/tests/usage-telemetry.test.js +0 -265
  194. package/dist/tests/utility-scoring.test.js +0 -558
  195. package/dist/tests/vault-load-error.test.js +0 -78
  196. package/dist/tests/vault-qa-fixes.test.js +0 -194
  197. package/dist/tests/vault.test.js +0 -429
  198. package/dist/tests/vector-search.test.js +0 -608
  199. package/dist/tests/walker.test.js +0 -252
  200. package/dist/tests/wave2-cluster-bc.test.js +0 -228
  201. package/dist/tests/wave2-cluster-d.test.js +0 -180
  202. package/dist/tests/wave2-cluster-e.test.js +0 -179
  203. package/dist/tests/wiki-qa-fixes.test.js +0 -270
  204. package/dist/tests/wiki.test.js +0 -529
  205. package/dist/tests/workflow-cli.test.js +0 -271
  206. package/dist/tests/workflow-markdown.test.js +0 -171
  207. package/dist/tests/workflow-path-escape.test.js +0 -132
  208. package/dist/tests/workflow-qa-fixes.test.js +0 -395
  209. package/dist/tests/workflows/indexer-rejection.test.js +0 -213
  210. /package/dist/{src/cli.js → cli.js} +0 -0
  211. /package/dist/{src/commands → commands}/completions.js +0 -0
  212. /package/dist/{src/commands → commands}/config-cli.js +0 -0
  213. /package/dist/{src/commands → commands}/curate.js +0 -0
  214. /package/dist/{src/commands → commands}/distill.js +0 -0
  215. /package/dist/{src/commands → commands}/events.js +0 -0
  216. /package/dist/{src/commands → commands}/history.js +0 -0
  217. /package/dist/{src/commands → commands}/info.js +0 -0
  218. /package/dist/{src/commands → commands}/init.js +0 -0
  219. /package/dist/{src/commands → commands}/install-audit.js +0 -0
  220. /package/dist/{src/commands → commands}/installed-stashes.js +0 -0
  221. /package/dist/{src/commands → commands}/migration-help.js +0 -0
  222. /package/dist/{src/commands → commands}/proposal.js +0 -0
  223. /package/dist/{src/commands → commands}/propose.js +0 -0
  224. /package/dist/{src/commands → commands}/reflect.js +0 -0
  225. /package/dist/{src/commands → commands}/registry-search.js +0 -0
  226. /package/dist/{src/commands → commands}/remember.js +0 -0
  227. /package/dist/{src/commands → commands}/search.js +0 -0
  228. /package/dist/{src/commands → commands}/self-update.js +0 -0
  229. /package/dist/{src/commands → commands}/show.js +0 -0
  230. /package/dist/{src/commands → commands}/source-add.js +0 -0
  231. /package/dist/{src/commands → commands}/source-clone.js +0 -0
  232. /package/dist/{src/commands → commands}/source-manage.js +0 -0
  233. /package/dist/{src/commands → commands}/vault.js +0 -0
  234. /package/dist/{src/core → core}/asset-ref.js +0 -0
  235. /package/dist/{src/core → core}/asset-registry.js +0 -0
  236. /package/dist/{src/core → core}/asset-spec.js +0 -0
  237. /package/dist/{src/core → core}/common.js +0 -0
  238. /package/dist/{src/core → core}/config.js +0 -0
  239. /package/dist/{src/core → core}/errors.js +0 -0
  240. /package/dist/{src/core → core}/events.js +0 -0
  241. /package/dist/{src/core → core}/frontmatter.js +0 -0
  242. /package/dist/{src/core → core}/lesson-lint.js +0 -0
  243. /package/dist/{src/core → core}/markdown.js +0 -0
  244. /package/dist/{src/core → core}/paths.js +0 -0
  245. /package/dist/{src/core → core}/proposals.js +0 -0
  246. /package/dist/{src/core → core}/warn.js +0 -0
  247. /package/dist/{src/core → core}/write-source.js +0 -0
  248. /package/dist/{src/indexer → indexer}/db-search.js +0 -0
  249. /package/dist/{src/indexer → indexer}/db.js +0 -0
  250. /package/dist/{src/indexer → indexer}/file-context.js +0 -0
  251. /package/dist/{src/indexer → indexer}/graph-boost.js +0 -0
  252. /package/dist/{src/indexer → indexer}/graph-extraction.js +0 -0
  253. /package/dist/{src/indexer → indexer}/indexer.js +0 -0
  254. /package/dist/{src/indexer → indexer}/manifest.js +0 -0
  255. /package/dist/{src/indexer → indexer}/matchers.js +0 -0
  256. /package/dist/{src/indexer → indexer}/memory-inference.js +0 -0
  257. /package/dist/{src/indexer → indexer}/metadata.js +0 -0
  258. /package/dist/{src/indexer → indexer}/search-fields.js +0 -0
  259. /package/dist/{src/indexer → indexer}/search-source.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/profiles.js +0 -0
  267. /package/dist/{src/integrations → integrations}/agent/prompts.js +0 -0
  268. /package/dist/{src/integrations → integrations}/agent/spawn.js +0 -0
  269. /package/dist/{src/integrations → integrations}/github.js +0 -0
  270. /package/dist/{src/integrations → integrations}/lockfile.js +0 -0
  271. /package/dist/{src/llm → llm}/client.js +0 -0
  272. /package/dist/{src/llm → llm}/embedder.js +0 -0
  273. /package/dist/{src/llm → llm}/embedders/cache.js +0 -0
  274. /package/dist/{src/llm → llm}/embedders/local.js +0 -0
  275. /package/dist/{src/llm → llm}/embedders/remote.js +0 -0
  276. /package/dist/{src/llm → llm}/embedders/types.js +0 -0
  277. /package/dist/{src/llm → llm}/feature-gate.js +0 -0
  278. /package/dist/{src/llm → llm}/graph-extract.js +0 -0
  279. /package/dist/{src/llm → llm}/index-passes.js +0 -0
  280. /package/dist/{src/llm → llm}/memory-infer.js +0 -0
  281. /package/dist/{src/llm → llm}/metadata-enhance.js +0 -0
  282. /package/dist/{src/output → output}/cli-hints.js +0 -0
  283. /package/dist/{src/output → output}/context.js +0 -0
  284. /package/dist/{src/output → output}/renderers.js +0 -0
  285. /package/dist/{src/output → output}/shapes.js +0 -0
  286. /package/dist/{src/output → output}/text.js +0 -0
  287. /package/dist/{src/registry → registry}/build-index.js +0 -0
  288. /package/dist/{src/registry → registry}/create-provider-registry.js +0 -0
  289. /package/dist/{src/registry → registry}/factory.js +0 -0
  290. /package/dist/{src/registry → registry}/origin-resolve.js +0 -0
  291. /package/dist/{src/registry → registry}/providers/index.js +0 -0
  292. /package/dist/{src/registry → registry}/providers/skills-sh.js +0 -0
  293. /package/dist/{src/registry → registry}/providers/static-index.js +0 -0
  294. /package/dist/{src/registry → registry}/providers/types.js +0 -0
  295. /package/dist/{src/registry → registry}/resolve.js +0 -0
  296. /package/dist/{src/registry → registry}/types.js +0 -0
  297. /package/dist/{src/setup → setup}/detect.js +0 -0
  298. /package/dist/{src/setup → setup}/ripgrep-install.js +0 -0
  299. /package/dist/{src/setup → setup}/ripgrep-resolve.js +0 -0
  300. /package/dist/{src/setup → setup}/setup.js +0 -0
  301. /package/dist/{src/setup → setup}/steps.js +0 -0
  302. /package/dist/{src/sources → sources}/include.js +0 -0
  303. /package/dist/{src/sources → sources}/provider-factory.js +0 -0
  304. /package/dist/{src/sources → sources}/provider.js +0 -0
  305. /package/dist/{src/sources → sources}/providers/filesystem.js +0 -0
  306. /package/dist/{src/sources → sources}/providers/git.js +0 -0
  307. /package/dist/{src/sources → sources}/providers/index.js +0 -0
  308. /package/dist/{src/sources → sources}/providers/install-types.js +0 -0
  309. /package/dist/{src/sources → sources}/providers/npm.js +0 -0
  310. /package/dist/{src/sources → sources}/providers/provider-utils.js +0 -0
  311. /package/dist/{src/sources → sources}/providers/sync-from-ref.js +0 -0
  312. /package/dist/{src/sources → sources}/providers/tar-utils.js +0 -0
  313. /package/dist/{src/sources → sources}/providers/website.js +0 -0
  314. /package/dist/{src/sources → sources}/resolve.js +0 -0
  315. /package/dist/{src/sources → sources}/types.js +0 -0
  316. /package/dist/{src/templates → templates}/wiki-templates.js +0 -0
  317. /package/dist/{src/version.js → version.js} +0 -0
  318. /package/dist/{src/wiki → wiki}/wiki.js +0 -0
  319. /package/dist/{src/workflows → workflows}/authoring.js +0 -0
  320. /package/dist/{src/workflows → workflows}/cli.js +0 -0
  321. /package/dist/{src/workflows → workflows}/db.js +0 -0
  322. /package/dist/{src/workflows → workflows}/document-cache.js +0 -0
  323. /package/dist/{src/workflows → workflows}/parser.js +0 -0
  324. /package/dist/{src/workflows → workflows}/renderer.js +0 -0
  325. /package/dist/{src/workflows → workflows}/runs.js +0 -0
  326. /package/dist/{src/workflows → workflows}/schema.js +0 -0
  327. /package/dist/{src/workflows → workflows}/validator.js +0 -0
@@ -1,378 +0,0 @@
1
- /**
2
- * Tests for `akm remember` frontmatter support (issue #169).
3
- *
4
- * Covers:
5
- * - CLI arg round-trip (--tag, --expires, --source)
6
- * - --auto heuristics (code, subjective, source, observed_at)
7
- * - --enrich with mocked chatCompletion (success + failure)
8
- * - Required-field rejection before any file write
9
- * - --expires duration → ISO date computation
10
- * - Zero-flag remember still works (no frontmatter written)
11
- * - memoryMdRenderer.extractMetadata populates StashEntry fields
12
- */
13
- import { afterEach, describe, expect, test } from "bun:test";
14
- import { spawnSync } from "node:child_process";
15
- import fs from "node:fs";
16
- import os from "node:os";
17
- import path from "node:path";
18
- import { parseFrontmatter } from "../src/core/frontmatter";
19
- import { buildFileContext, buildRenderContext } from "../src/indexer/file-context";
20
- import { memoryMdRenderer } from "../src/output/renderers";
21
- // ── CLI harness ──────────────────────────────────────────────────────────────
22
- const CLI = path.join(__dirname, "..", "src", "cli.ts");
23
- const tempDirs = [];
24
- function makeTempDir(prefix) {
25
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
26
- tempDirs.push(dir);
27
- return dir;
28
- }
29
- function runCli(args, options) {
30
- const stashDir = options?.stashDir ?? makeTempDir("akm-rmfm-stash-");
31
- const xdgCache = makeTempDir("akm-rmfm-cache-");
32
- const xdgConfig = makeTempDir("akm-rmfm-config-");
33
- const result = spawnSync("bun", [CLI, ...args], {
34
- encoding: "utf8",
35
- timeout: 30_000,
36
- input: options?.input,
37
- env: {
38
- ...process.env,
39
- AKM_STASH_DIR: stashDir,
40
- XDG_CACHE_HOME: xdgCache,
41
- XDG_CONFIG_HOME: xdgConfig,
42
- },
43
- });
44
- return { stashDir, result };
45
- }
46
- afterEach(() => {
47
- for (const dir of tempDirs.splice(0)) {
48
- fs.rmSync(dir, { recursive: true, force: true });
49
- }
50
- });
51
- // ── Zero-flag path (backward compatibility) ──────────────────────────────────
52
- describe("zero-flag remember", () => {
53
- test("writes bare memory with no frontmatter", () => {
54
- const { stashDir, result } = runCli(["remember", "Deployment needs VPN access"]);
55
- expect(result.status).toBe(0);
56
- const json = JSON.parse(result.stdout);
57
- const content = fs.readFileSync(json.path, "utf8");
58
- // No frontmatter delimiter present
59
- expect(content.startsWith("---")).toBe(false);
60
- expect(content).toContain("Deployment needs VPN access");
61
- expect(stashDir).toBeTruthy();
62
- });
63
- test("writes bare memory when reading from stdin", () => {
64
- const { result } = runCli(["remember"], { input: "VPN needed for staging deploys" });
65
- expect(result.status).toBe(0);
66
- const json = JSON.parse(result.stdout);
67
- const content = fs.readFileSync(json.path, "utf8");
68
- expect(content.startsWith("---")).toBe(false);
69
- });
70
- test("reads stdin when --format json is present", () => {
71
- const { result } = runCli(["remember", "--name", "from-stdin", "--format", "json"], { input: "stdin body" });
72
- expect(result.status).toBe(0);
73
- const json = JSON.parse(result.stdout);
74
- expect(fs.readFileSync(json.path, "utf8")).toContain("stdin body");
75
- expect(fs.readFileSync(json.path, "utf8")).not.toContain("\njson");
76
- });
77
- });
78
- // ── CLI args (Mode 1) ────────────────────────────────────────────────────────
79
- describe("remember --tag", () => {
80
- test("single --tag writes frontmatter with tags array", () => {
81
- const { result } = runCli(["remember", "VPN required for staging", "--tag", "ops"]);
82
- expect(result.status).toBe(0);
83
- const json = JSON.parse(result.stdout);
84
- const content = fs.readFileSync(json.path, "utf8");
85
- const parsed = parseFrontmatter(content);
86
- expect(parsed.data.tags).toEqual(["ops"]);
87
- expect(parsed.content).toContain("VPN required for staging");
88
- });
89
- test("multiple --tag flags write all tags", () => {
90
- const { result } = runCli(["remember", "VPN required for staging", "--tag", "ops", "--tag", "networking"]);
91
- expect(result.status).toBe(0);
92
- const json = JSON.parse(result.stdout);
93
- const content = fs.readFileSync(json.path, "utf8");
94
- const parsed = parseFrontmatter(content);
95
- expect(parsed.data.tags).toEqual(["ops", "networking"]);
96
- });
97
- });
98
- describe("remember --source", () => {
99
- test("--source stores a URL as-is", () => {
100
- const { result } = runCli([
101
- "remember",
102
- "Read the deployment guide",
103
- "--tag",
104
- "docs",
105
- "--source",
106
- "https://example.com/deploy",
107
- ]);
108
- expect(result.status).toBe(0);
109
- const json = JSON.parse(result.stdout);
110
- const content = fs.readFileSync(json.path, "utf8");
111
- const parsed = parseFrontmatter(content);
112
- expect(parsed.data.source).toBe("https://example.com/deploy");
113
- });
114
- test("--source stores an asset ref", () => {
115
- const { result } = runCli(["remember", "Deploy skill requires VPN", "--tag", "ops", "--source", "skill:deploy"]);
116
- expect(result.status).toBe(0);
117
- const json = JSON.parse(result.stdout);
118
- const content = fs.readFileSync(json.path, "utf8");
119
- const parsed = parseFrontmatter(content);
120
- expect(parsed.data.source).toBe("skill:deploy");
121
- });
122
- });
123
- describe("remember --expires", () => {
124
- test("--expires 30d resolves to a future ISO date ~30 days from now", () => {
125
- const before = new Date();
126
- const { result } = runCli(["remember", "Temp access token valid 30 days", "--tag", "security", "--expires", "30d"]);
127
- expect(result.status).toBe(0);
128
- const json = JSON.parse(result.stdout);
129
- const content = fs.readFileSync(json.path, "utf8");
130
- const parsed = parseFrontmatter(content);
131
- const expires = parsed.data.expires;
132
- expect(expires).toMatch(/^\d{4}-\d{2}-\d{2}$/);
133
- // Should be approximately 30 days from now (within 1-day margin)
134
- const expiresDate = new Date(expires);
135
- const expectedMin = new Date(before.getTime() + 29 * 24 * 60 * 60 * 1000);
136
- const expectedMax = new Date(before.getTime() + 31 * 24 * 60 * 60 * 1000);
137
- expect(expiresDate >= expectedMin).toBe(true);
138
- expect(expiresDate <= expectedMax).toBe(true);
139
- });
140
- test("--expires 12h resolves to a future ISO date ~12h from now", () => {
141
- const { result } = runCli(["remember", "Short-lived credential", "--tag", "security", "--expires", "12h"]);
142
- expect(result.status).toBe(0);
143
- const json = JSON.parse(result.stdout);
144
- const content = fs.readFileSync(json.path, "utf8");
145
- const parsed = parseFrontmatter(content);
146
- expect(parsed.data.expires).toMatch(/^\d{4}-\d{2}-\d{2}$/);
147
- });
148
- test("--expires 6m resolves to a future ISO date ~6 months from now", () => {
149
- const { result } = runCli(["remember", "Long-term access", "--tag", "access", "--expires", "6m"]);
150
- expect(result.status).toBe(0);
151
- const json = JSON.parse(result.stdout);
152
- const content = fs.readFileSync(json.path, "utf8");
153
- const parsed = parseFrontmatter(content);
154
- const expires = parsed.data.expires;
155
- expect(expires).toMatch(/^\d{4}-\d{2}-\d{2}$/);
156
- const expiresDate = new Date(expires);
157
- const expectedMin = new Date(Date.now() + 170 * 24 * 60 * 60 * 1000); // ~5.7 months
158
- const expectedMax = new Date(Date.now() + 185 * 24 * 60 * 60 * 1000); // ~6.2 months
159
- expect(expiresDate >= expectedMin).toBe(true);
160
- expect(expiresDate <= expectedMax).toBe(true);
161
- });
162
- test("invalid --expires format produces an error", () => {
163
- const { result } = runCli(["remember", "Some note", "--tag", "misc", "--expires", "invalid"]);
164
- expect(result.status).toBe(2);
165
- const json = JSON.parse(result.stderr);
166
- expect(json.error).toContain("Invalid --expires format");
167
- });
168
- });
169
- // ── Required-field rejection (before file write) ─────────────────────────────
170
- describe("required-field rejection", () => {
171
- test("--source without --tag rejects with missing-fields error before writing", () => {
172
- const { stashDir, result } = runCli(["remember", "Some note", "--source", "https://example.com"]);
173
- expect(result.status).toBe(2);
174
- const json = JSON.parse(result.stderr);
175
- expect(json.error).toContain("tags");
176
- expect(json.error).toContain("--tag");
177
- // Confirm no file was written
178
- const memoriesDir = path.join(stashDir, "memories");
179
- const written = fs.existsSync(memoriesDir) && fs.readdirSync(memoriesDir).length > 0;
180
- expect(written).toBe(false);
181
- });
182
- test("--expires without --tag rejects with missing-fields error before writing", () => {
183
- const { stashDir, result } = runCli(["remember", "Some note", "--expires", "30d"]);
184
- expect(result.status).toBe(2);
185
- const json = JSON.parse(result.stderr);
186
- expect(json.error).toContain("tags");
187
- const memoriesDir = path.join(stashDir, "memories");
188
- const written = fs.existsSync(memoriesDir) && fs.readdirSync(memoriesDir).length > 0;
189
- expect(written).toBe(false);
190
- });
191
- });
192
- // ── --auto heuristics (Mode 2) ───────────────────────────────────────────────
193
- describe("remember --auto", () => {
194
- test("body with fenced code block gets tag 'code'", () => {
195
- const body = "Remember this pattern:\n```ts\nconst x = 1;\n```";
196
- const { result } = runCli(["remember", body, "--auto"]);
197
- expect(result.status).toBe(0);
198
- const json = JSON.parse(result.stdout);
199
- const content = fs.readFileSync(json.path, "utf8");
200
- const parsed = parseFrontmatter(content);
201
- const tags = parsed.data.tags;
202
- expect(tags).toContain("code");
203
- });
204
- test("body with URL gets source set automatically", () => {
205
- const body = "Found this resource https://example.com/guide useful for ops";
206
- const { result } = runCli(["remember", body, "--auto", "--tag", "docs"]);
207
- expect(result.status).toBe(0);
208
- const json = JSON.parse(result.stdout);
209
- const content = fs.readFileSync(json.path, "utf8");
210
- const parsed = parseFrontmatter(content);
211
- expect(parsed.data.source).toBe("https://example.com/guide");
212
- });
213
- test("body with first-person pronoun gets subjective: true", () => {
214
- // Must supply --tag because heuristics add subjective but not tags for plain text.
215
- const body = "I noticed that staging requires VPN every time";
216
- const { result } = runCli(["remember", body, "--auto", "--tag", "ops"]);
217
- expect(result.status).toBe(0);
218
- const json = JSON.parse(result.stdout);
219
- const content = fs.readFileSync(json.path, "utf8");
220
- const parsed = parseFrontmatter(content);
221
- expect(parsed.data.subjective).toBe(true);
222
- expect(parsed.data.tags).toContain("ops");
223
- });
224
- test("body with ISO date gets observed_at set", () => {
225
- const body = "The outage happened on 2026-01-15 and we fixed it quickly";
226
- const { result } = runCli(["remember", body, "--auto"]);
227
- // Will fail required-field check if no tags derived from the body
228
- // Force a tag to ensure we get through
229
- const { result: r2 } = runCli(["remember", body, "--auto", "--tag", "ops"]);
230
- expect(r2.status).toBe(0);
231
- const json = JSON.parse(r2.stdout);
232
- const content = fs.readFileSync(json.path, "utf8");
233
- const parsed = parseFrontmatter(content);
234
- expect(parsed.data.observed_at).toBe("2026-01-15");
235
- void result; // suppress unused variable warning
236
- });
237
- test("--auto without any tags from heuristics or CLI still writes the memory", () => {
238
- // Plain text body — no code block, no URL. Heuristics won't derive any tags.
239
- const { result } = runCli(["remember", "Plain text note without any tags derivable", "--auto"]);
240
- expect(result.status).toBe(0);
241
- const json = JSON.parse(result.stdout);
242
- expect(fs.existsSync(json.path)).toBe(true);
243
- });
244
- test("--auto + explicit --tag satisfies required-field check", () => {
245
- const body = "No special content here";
246
- const { result } = runCli(["remember", body, "--auto", "--tag", "misc"]);
247
- expect(result.status).toBe(0);
248
- const json = JSON.parse(result.stdout);
249
- const content = fs.readFileSync(json.path, "utf8");
250
- const parsed = parseFrontmatter(content);
251
- expect(parsed.data.tags).toContain("misc");
252
- });
253
- test("--source CLI arg takes priority over auto-detected URL", () => {
254
- const body = "See https://example.com/docs for reference";
255
- const { result } = runCli(["remember", body, "--auto", "--tag", "docs", "--source", "explicit:source"]);
256
- expect(result.status).toBe(0);
257
- const json = JSON.parse(result.stdout);
258
- const content = fs.readFileSync(json.path, "utf8");
259
- const parsed = parseFrontmatter(content);
260
- // CLI --source wins over auto-detected URL
261
- expect(parsed.data.source).toBe("explicit:source");
262
- });
263
- });
264
- // ── memoryMdRenderer.extractMetadata ─────────────────────────────────────────
265
- /** A static MatchResult for memory-md (avoids calling runMatchers and null assertions). */
266
- const MEMORY_MATCH = { type: "memory", specificity: 10, renderer: "memory-md" };
267
- describe("memoryMdRenderer.extractMetadata", () => {
268
- const createdTmpDirs = [];
269
- afterEach(() => {
270
- for (const dir of createdTmpDirs.splice(0)) {
271
- fs.rmSync(dir, { recursive: true, force: true });
272
- }
273
- });
274
- function writeTmpMemory(content) {
275
- const stashRoot = fs.mkdtempSync(path.join(os.tmpdir(), "akm-mem-renderer-"));
276
- createdTmpDirs.push(stashRoot);
277
- const memoriesDir = path.join(stashRoot, "memories");
278
- fs.mkdirSync(memoriesDir, { recursive: true });
279
- const filePath = path.join(memoriesDir, "test-memory.md");
280
- fs.writeFileSync(filePath, content, "utf8");
281
- return { filePath, stashRoot };
282
- }
283
- test("populates tags from frontmatter", () => {
284
- const { filePath, stashRoot } = writeTmpMemory("---\ntags: [ops, networking]\n---\nDeployment needs VPN access\n");
285
- const ctx = buildFileContext(stashRoot, filePath);
286
- const entry = { name: "test-memory", type: "memory" };
287
- const renderCtx = buildRenderContext(ctx, MEMORY_MATCH, [stashRoot]);
288
- memoryMdRenderer.extractMetadata?.(entry, renderCtx);
289
- expect(entry.tags).toContain("ops");
290
- expect(entry.tags).toContain("networking");
291
- });
292
- test("populates description from frontmatter", () => {
293
- const { filePath, stashRoot } = writeTmpMemory("---\ndescription: VPN required for staging deploys\ntags: [ops]\n---\nBody content\n");
294
- const ctx = buildFileContext(stashRoot, filePath);
295
- const entry = { name: "test-memory", type: "memory" };
296
- const renderCtx = buildRenderContext(ctx, MEMORY_MATCH, [stashRoot]);
297
- memoryMdRenderer.extractMetadata?.(entry, renderCtx);
298
- expect(entry.description).toBe("VPN required for staging deploys");
299
- });
300
- test("populates searchHints with source, observed_at, expires, subjective", () => {
301
- const { filePath, stashRoot } = writeTmpMemory("---\ntags: [ops]\nsource: skill:deploy\nobserved_at: 2026-01-15\nexpires: 2026-04-15\nsubjective: true\n---\nVPN needed\n");
302
- const ctx = buildFileContext(stashRoot, filePath);
303
- const entry = { name: "test-memory", type: "memory" };
304
- const renderCtx = buildRenderContext(ctx, MEMORY_MATCH, [stashRoot]);
305
- memoryMdRenderer.extractMetadata?.(entry, renderCtx);
306
- expect(entry.searchHints).toBeDefined();
307
- expect(entry.searchHints).toContain("skill:deploy");
308
- expect(entry.searchHints).toContain("observed_at:2026-01-15");
309
- expect(entry.searchHints).toContain("expires:2026-04-15");
310
- expect(entry.searchHints).toContain("subjective");
311
- });
312
- test("observed_at falls back to file mtime when not in frontmatter", () => {
313
- const { filePath, stashRoot } = writeTmpMemory("---\ntags: [ops]\n---\nSome memory without observed_at\n");
314
- const ctx = buildFileContext(stashRoot, filePath);
315
- const entry = { name: "test-memory", type: "memory" };
316
- const renderCtx = buildRenderContext(ctx, MEMORY_MATCH, [stashRoot]);
317
- memoryMdRenderer.extractMetadata?.(entry, renderCtx);
318
- // Should have an observed_at hint derived from mtime
319
- const mtimeHint = (entry.searchHints ?? []).find((h) => h.startsWith("observed_at:"));
320
- expect(mtimeHint).toBeDefined();
321
- // The mtime-based date should be a valid ISO date
322
- const dateStr = mtimeHint?.slice("observed_at:".length);
323
- expect(dateStr).toMatch(/^\d{4}-\d{2}-\d{2}$/);
324
- });
325
- test("works for bare memory with no frontmatter (no crash)", () => {
326
- const { filePath, stashRoot } = writeTmpMemory("Just a plain memory without any frontmatter.\n");
327
- const ctx = buildFileContext(stashRoot, filePath);
328
- const entry = { name: "test-memory", type: "memory" };
329
- const renderCtx = buildRenderContext(ctx, MEMORY_MATCH, [stashRoot]);
330
- // Should not throw
331
- expect(() => memoryMdRenderer.extractMetadata?.(entry, renderCtx)).not.toThrow();
332
- // mtime fallback should still fire
333
- const mtimeHint = (entry.searchHints ?? []).find((h) => h.startsWith("observed_at:"));
334
- expect(mtimeHint).toBeDefined();
335
- });
336
- test("block-sequence tags in frontmatter are parsed correctly", () => {
337
- const { filePath, stashRoot } = writeTmpMemory("---\ntags:\n- ops\n- networking\n- deploy\n---\nVPN required\n");
338
- const ctx = buildFileContext(stashRoot, filePath);
339
- const entry = { name: "test-memory", type: "memory" };
340
- const renderCtx = buildRenderContext(ctx, MEMORY_MATCH, [stashRoot]);
341
- memoryMdRenderer.extractMetadata?.(entry, renderCtx);
342
- expect(entry.tags).toContain("ops");
343
- expect(entry.tags).toContain("networking");
344
- expect(entry.tags).toContain("deploy");
345
- });
346
- });
347
- // ── --enrich (Mode 3) — mocked ───────────────────────────────────────────────
348
- // These tests directly exercise the heuristic and enrichment helpers by calling
349
- // the CLI with a mock LLM config. Since we cannot easily intercept the dynamic
350
- // import inside the CLI process, we test the LLM enrichment path via integration
351
- // against a non-existent endpoint and verify the graceful-degradation behaviour.
352
- describe("remember --enrich graceful degradation", () => {
353
- test("when no LLM is configured, --enrich emits warning but still fails if no tags", () => {
354
- // No LLM configured in the temp config dir — should warn and return empty tags
355
- const { result } = runCli(["remember", "Some note about ops", "--enrich"]);
356
- // Will fail because enrichment produces no tags and no CLI tags given.
357
- // stderr may contain a warning line followed by a multi-line JSON error block.
358
- if (result.status !== 0) {
359
- // Extract the JSON portion (from first '{' to end of stderr)
360
- const jsonStart = result.stderr.indexOf("{");
361
- expect(jsonStart).toBeGreaterThanOrEqual(0);
362
- const jsonStr = result.stderr.slice(jsonStart);
363
- const json = JSON.parse(jsonStr);
364
- expect(json.error).toContain("tags");
365
- }
366
- // Either path is acceptable: rejection (no tags) or success (if enrichment happened to work)
367
- });
368
- test("--enrich with --tag satisfies required-field check even if LLM fails", () => {
369
- // Providing --tag means we don't depend on LLM for the required field
370
- const { result } = runCli(["remember", "Some note", "--enrich", "--tag", "misc"]);
371
- expect(result.status).toBe(0);
372
- const json = JSON.parse(result.stdout);
373
- const content = fs.readFileSync(json.path, "utf8");
374
- const parsed = parseFrontmatter(content);
375
- // At minimum, the --tag value must be present
376
- expect(parsed.data.tags).toContain("misc");
377
- });
378
- });
@@ -1,123 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { parse as yamlParse } from "yaml";
3
- import { buildMemoryFrontmatter, parseDuration, runAutoHeuristics } from "../src/commands/remember";
4
- describe("parseDuration", () => {
5
- test("parses days", () => {
6
- expect(parseDuration("30d")).toBe(30 * 24 * 60 * 60 * 1000);
7
- expect(parseDuration("1d")).toBe(24 * 60 * 60 * 1000);
8
- });
9
- test("parses hours", () => {
10
- expect(parseDuration("12h")).toBe(12 * 60 * 60 * 1000);
11
- });
12
- test("parses months as 30-day approximation", () => {
13
- expect(parseDuration("6m")).toBe(6 * 30 * 24 * 60 * 60 * 1000);
14
- });
15
- test("rejects invalid format", () => {
16
- expect(() => parseDuration("forever")).toThrow(/Invalid --expires/);
17
- expect(() => parseDuration("30")).toThrow(/Invalid --expires/);
18
- expect(() => parseDuration("d30")).toThrow(/Invalid --expires/);
19
- });
20
- test("trims whitespace and accepts uppercase units", () => {
21
- expect(parseDuration(" 7D ")).toBe(7 * 24 * 60 * 60 * 1000);
22
- });
23
- });
24
- describe("buildMemoryFrontmatter — YAML injection guard", () => {
25
- test("emits a parseable, well-formed YAML block for a normal record", () => {
26
- const out = buildMemoryFrontmatter({
27
- description: "VPN required for staging deploys",
28
- tags: ["ops", "networking"],
29
- source: "skill:deploy",
30
- observed_at: "2026-04-24",
31
- expires: "2026-07-23",
32
- subjective: false,
33
- });
34
- expect(out.startsWith("---\n")).toBe(true);
35
- expect(out.endsWith("\n---")).toBe(true);
36
- const inner = out.replace(/^---\n/, "").replace(/\n---$/, "");
37
- const parsed = yamlParse(inner);
38
- expect(parsed.description).toBe("VPN required for staging deploys");
39
- expect(parsed.tags).toEqual(["ops", "networking"]);
40
- expect(parsed.source).toBe("skill:deploy");
41
- expect(parsed.observed_at).toBe("2026-04-24");
42
- expect(parsed.expires).toBe("2026-07-23");
43
- expect(parsed.subjective).toBeUndefined();
44
- });
45
- test("preserves subjective: true when set", () => {
46
- const out = buildMemoryFrontmatter({ tags: ["x"], subjective: true });
47
- const parsed = yamlParse(out.replace(/^---\n/, "").replace(/\n---$/, ""));
48
- expect(parsed.subjective).toBe(true);
49
- });
50
- test("description containing newlines + forged tags cannot inject extra keys", () => {
51
- // Pre-fix this string would have been emitted as:
52
- // description: nice
53
- // tags: [pwned]
54
- // …producing two real frontmatter keys. With yaml.stringify it is
55
- // safely quoted as a single string value.
56
- const malicious = "nice\ntags: [pwned]";
57
- const out = buildMemoryFrontmatter({ description: malicious, tags: ["expected"] });
58
- const parsed = yamlParse(out.replace(/^---\n/, "").replace(/\n---$/, ""));
59
- expect(parsed.tags).toEqual(["expected"]);
60
- expect(parsed.description).toBe(malicious);
61
- expect(parsed.pwned).toBeUndefined();
62
- });
63
- test("source containing YAML metacharacters round-trips intact", () => {
64
- const tricky = "https://example.com/path?q=#anchor: { x: y }";
65
- const out = buildMemoryFrontmatter({ tags: ["ops"], source: tricky });
66
- const parsed = yamlParse(out.replace(/^---\n/, "").replace(/\n---$/, ""));
67
- expect(parsed.source).toBe(tricky);
68
- });
69
- test("omits empty fields", () => {
70
- const out = buildMemoryFrontmatter({ tags: [] });
71
- expect(out).toBe("---\n---");
72
- });
73
- test("omits whitespace-only string fields", () => {
74
- const out = buildMemoryFrontmatter({ description: " ", tags: ["x"] });
75
- const parsed = yamlParse(out.replace(/^---\n/, "").replace(/\n---$/, ""));
76
- expect(parsed.description).toBeUndefined();
77
- expect(parsed.tags).toEqual(["x"]);
78
- });
79
- });
80
- describe("runAutoHeuristics", () => {
81
- test("detects a fenced code block as the `code` tag", () => {
82
- const result = runAutoHeuristics("Found this:\n```sh\necho hi\n```");
83
- expect(result.tags).toContain("code");
84
- });
85
- test("does not add `code` tag when no fenced block present", () => {
86
- const result = runAutoHeuristics("plain prose, no code");
87
- expect(result.tags).not.toContain("code");
88
- });
89
- test("flags first-person pronouns as subjective (lowercase + capital I)", () => {
90
- expect(runAutoHeuristics("I think we should ship").subjective).toBe(true);
91
- expect(runAutoHeuristics("we shipped my favorite feature").subjective).toBe(true);
92
- expect(runAutoHeuristics("our team agreed").subjective).toBe(true);
93
- });
94
- test("non-first-person prose is not flagged subjective", () => {
95
- expect(runAutoHeuristics("The cluster restarted at 3am.").subjective).toBeUndefined();
96
- // Capitalised My/Our at sentence start currently isn't matched —
97
- // documented as case-sensitive. If we widen this in a future
98
- // patch, update this test to reflect the new behaviour.
99
- expect(runAutoHeuristics("My take is...").subjective).toBeUndefined();
100
- });
101
- test("captures the first URL as source", () => {
102
- const result = runAutoHeuristics("see https://example.com/docs and also https://example.org");
103
- expect(result.source).toBe("https://example.com/docs");
104
- });
105
- test("captures an explicit ISO date as observed_at", () => {
106
- const result = runAutoHeuristics("Incident on 2026-04-24, resolved.");
107
- expect(result.observed_at).toBe("2026-04-24");
108
- });
109
- test("interprets `today` as observed_at", () => {
110
- const result = runAutoHeuristics("today the deploy failed");
111
- expect(result.observed_at).toMatch(/^\d{4}-\d{2}-\d{2}$/);
112
- // Should be today's date (loose check — no timezone drift assertions)
113
- const today = new Date().toISOString().slice(0, 10);
114
- expect(result.observed_at).toBe(today);
115
- });
116
- test("handles plain prose without any signals", () => {
117
- const result = runAutoHeuristics("Just a regular note about something boring.");
118
- expect(result.tags).toEqual([]);
119
- expect(result.source).toBeUndefined();
120
- expect(result.observed_at).toBeUndefined();
121
- expect(result.subjective).toBeUndefined();
122
- });
123
- });