akm-cli 0.9.17-alpha.2 → 0.9.17-alpha.4

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 (343) hide show
  1. package/CHANGELOG.md +756 -0
  2. package/dist/akm +94 -196
  3. package/dist/cli/shared.js +6 -2
  4. package/dist/cli.js +22 -9
  5. package/dist/commands/agent/agent-dispatch.js +1 -1
  6. package/dist/commands/command/command-execution.js +24 -62
  7. package/dist/commands/feedback-cli.js +0 -1
  8. package/dist/commands/health/accept-rate.js +2 -2
  9. package/dist/commands/health/checks.js +30 -75
  10. package/dist/commands/health/config-skew.js +38 -0
  11. package/dist/commands/health/egress.js +54 -0
  12. package/dist/commands/health/html-report.js +0 -38
  13. package/dist/commands/health/improve-metrics.js +123 -562
  14. package/dist/commands/health/plugin-staleness.js +53 -3
  15. package/dist/commands/health/renderers.js +12 -4
  16. package/dist/commands/health/report-view-model.js +11 -106
  17. package/dist/commands/health/types-improve.js +4 -19
  18. package/dist/commands/health/windows.js +64 -73
  19. package/dist/commands/health.js +122 -143
  20. package/dist/commands/improve/consolidate/chunking.js +25 -100
  21. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  22. package/dist/commands/improve/consolidate.js +538 -1075
  23. package/dist/commands/improve/content-hash.js +16 -24
  24. package/dist/commands/improve/distill/content-repair.js +18 -100
  25. package/dist/commands/improve/distill-guards.js +20 -81
  26. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  27. package/dist/commands/improve/distill.js +608 -1075
  28. package/dist/commands/improve/eligibility.js +126 -400
  29. package/dist/commands/improve/execution.js +3 -5
  30. package/dist/commands/improve/extract.js +487 -1046
  31. package/dist/commands/improve/feedback-valence.js +0 -25
  32. package/dist/commands/improve/improve-cli.js +29 -166
  33. package/dist/commands/improve/improve-result-file.js +10 -66
  34. package/dist/commands/improve/improve-strategies.js +12 -7
  35. package/dist/commands/improve/improve-usage-report.js +18 -64
  36. package/dist/commands/improve/improve.js +443 -1063
  37. package/dist/commands/improve/ledger.js +114 -0
  38. package/dist/commands/improve/locks.js +2 -8
  39. package/dist/commands/improve/loop-stages.js +459 -1172
  40. package/dist/commands/improve/memory/derived-ref.js +12 -77
  41. package/dist/commands/improve/memory/memory-belief.js +14 -118
  42. package/dist/commands/improve/memory/memory-improve.js +4 -3
  43. package/dist/commands/improve/outcome-loop.js +28 -156
  44. package/dist/commands/improve/planner.js +5 -10
  45. package/dist/commands/improve/preparation.js +851 -2339
  46. package/dist/commands/improve/proactive-maintenance.js +34 -101
  47. package/dist/commands/improve/reflect-noise.js +104 -280
  48. package/dist/commands/improve/reflect.js +621 -1367
  49. package/dist/commands/improve/salience.js +46 -232
  50. package/dist/commands/improve/session-asset.js +19 -100
  51. package/dist/commands/improve/stage.js +323 -0
  52. package/dist/commands/proposal/drain.js +251 -644
  53. package/dist/commands/proposal/proposal-cli.js +3 -18
  54. package/dist/commands/proposal/proposal-types.js +20 -41
  55. package/dist/commands/proposal/proposal.js +1 -2
  56. package/dist/commands/proposal/propose.js +134 -160
  57. package/dist/commands/proposal/repository.js +502 -1487
  58. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  59. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  60. package/dist/commands/proposal/validators/proposals.js +13 -89
  61. package/dist/commands/read/curate.js +63 -413
  62. package/dist/commands/read/search-cli.js +16 -33
  63. package/dist/commands/read/search.js +17 -23
  64. package/dist/commands/read/show.js +2 -13
  65. package/dist/commands/sources/bundle-cli.js +25 -2
  66. package/dist/commands/sources/bundle-config-ops.js +7 -0
  67. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  68. package/dist/commands/sources/info.js +2 -11
  69. package/dist/commands/sources/installed-stashes.js +197 -746
  70. package/dist/commands/sources/schema-repair.js +98 -129
  71. package/dist/commands/sources/source-add.js +62 -12
  72. package/dist/commands/sources/stash-cli.js +1 -1
  73. package/dist/commands/tasks/explain.js +10 -13
  74. package/dist/commands/tasks/tasks-cli.js +9 -8
  75. package/dist/commands/tasks/tasks.js +326 -930
  76. package/dist/commands/tasks/validate.js +42 -21
  77. package/dist/commands/workflow/plan.js +22 -29
  78. package/dist/commands/workflow-cli.js +4 -4
  79. package/dist/core/adapter/adapters/akm-adapter.js +0 -1
  80. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  81. package/dist/core/adapter/adapters/akm-metadata.js +11 -12
  82. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  83. package/dist/core/adapter/execution-source.js +17 -29
  84. package/dist/core/asset/resolve-ref.js +1 -1
  85. package/dist/core/bundle-id.js +42 -5
  86. package/dist/core/bundle-rename.js +291 -0
  87. package/dist/core/config/config-io.js +1 -2
  88. package/dist/core/config/config-schema.js +1 -33
  89. package/dist/core/config/config-walker.js +1 -1
  90. package/dist/core/config/config.js +163 -68
  91. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  92. package/dist/core/config/schema/embedding.js +20 -5
  93. package/dist/core/config/schema/engines.js +5 -0
  94. package/dist/core/config/schema/execution.js +1 -1
  95. package/dist/core/config/schema/experimental.js +1 -1
  96. package/dist/core/config/schema/improve-processes.js +21 -95
  97. package/dist/core/config/schema/improve.js +4 -42
  98. package/dist/core/config/schema/scheduler.js +12 -12
  99. package/dist/core/config/schema/search.js +6 -22
  100. package/dist/core/env-secret-ref.js +0 -1
  101. package/dist/core/errors.js +8 -9
  102. package/dist/core/file-lock.js +76 -173
  103. package/dist/core/logs-db.js +2 -2
  104. package/dist/core/paths.js +0 -27
  105. package/dist/core/redaction.js +109 -2
  106. package/dist/core/run-lock.js +2 -5
  107. package/dist/core/spawn-env.js +1 -1
  108. package/dist/core/state/migrations.js +108 -61
  109. package/dist/core/state-db-scope.js +2 -4
  110. package/dist/core/state-db.js +126 -692
  111. package/dist/core/type-presentation.js +1 -9
  112. package/dist/core/write-source.js +293 -1012
  113. package/dist/execution/input-contract.js +1 -1
  114. package/dist/execution/resolved-request.js +135 -689
  115. package/dist/execution/source.js +63 -257
  116. package/dist/execution/target-ref.js +1 -1
  117. package/dist/indexer/bundle-identity-guard.js +2 -2
  118. package/dist/indexer/db/graph-db.js +106 -46
  119. package/dist/indexer/ensure-index.js +44 -85
  120. package/dist/indexer/graph/graph-extraction.js +340 -562
  121. package/dist/indexer/graph/graph-related.js +130 -0
  122. package/dist/indexer/index-rebuild-lock.js +3 -11
  123. package/dist/indexer/index-writer-lock.js +8 -17
  124. package/dist/indexer/index-written-assets.js +139 -151
  125. package/dist/indexer/indexer.js +524 -846
  126. package/dist/indexer/materialize-embeddings.js +60 -397
  127. package/dist/indexer/passes/memory-inference.js +81 -90
  128. package/dist/indexer/passes/metadata.js +132 -200
  129. package/dist/indexer/read-preflight.js +0 -7
  130. package/dist/indexer/scan/doc-to-entry.js +1 -3
  131. package/dist/indexer/scan/drain-dir.js +1 -1
  132. package/dist/indexer/search/db-search.js +181 -590
  133. package/dist/indexer/search/fts-query.js +30 -41
  134. package/dist/indexer/search/ranking.js +28 -154
  135. package/dist/indexer/search/search-attribution.js +12 -32
  136. package/dist/indexer/search/search-fields.js +11 -15
  137. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  138. package/dist/indexer/search/search-source.js +1 -4
  139. package/dist/indexer/usage/usage-events.js +2 -7
  140. package/dist/integrations/agent/engine-fallback.js +23 -40
  141. package/dist/integrations/agent/engine-resolution.js +93 -183
  142. package/dist/integrations/agent/execution.js +507 -0
  143. package/dist/integrations/agent/model-map.js +28 -156
  144. package/dist/integrations/agent/request-lowering.js +66 -141
  145. package/dist/integrations/agent/runner-dispatch.js +143 -321
  146. package/dist/integrations/agent/runner.js +54 -14
  147. package/dist/integrations/lockfile.js +53 -101
  148. package/dist/llm/embedders/deterministic.js +2 -3
  149. package/dist/llm/embedders/profile.js +71 -0
  150. package/dist/llm/embedders/remote.js +10 -15
  151. package/dist/llm/graph-extract.js +3 -12
  152. package/dist/llm/index-passes.js +3 -5
  153. package/dist/llm/memory-infer.js +1 -2
  154. package/dist/llm/metadata-enhance.js +1 -2
  155. package/dist/llm/structured-call.js +5 -24
  156. package/dist/output/generic-render.js +23 -11
  157. package/dist/output/html-render.js +13 -10
  158. package/dist/output/render-registry.js +3 -32
  159. package/dist/output/shapes/helpers.js +2 -34
  160. package/dist/output/shapes/passthrough.js +1 -9
  161. package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
  162. package/dist/output/text/command-format.js +60 -23
  163. package/dist/output/text/helpers.js +1 -1
  164. package/dist/output/text/migrate.js +5 -14
  165. package/dist/output/text/proposal-format.js +1 -2
  166. package/dist/output/text/workflow-format.js +0 -32
  167. package/dist/output/text.js +2 -0
  168. package/dist/registry/factory.js +4 -19
  169. package/dist/registry/network.js +66 -220
  170. package/dist/registry/providers/index.js +0 -2
  171. package/dist/registry/providers/skills-sh.js +3 -14
  172. package/dist/registry/providers/static-index.js +24 -26
  173. package/dist/registry/resolve.js +55 -131
  174. package/dist/scripts/akm-migrate-node.js +43937 -93313
  175. package/dist/scripts/akm-migrate.js +43697 -93071
  176. package/dist/setup/registry-stash-loader.js +4 -13
  177. package/dist/setup/semantic-assets.js +3 -44
  178. package/dist/setup/setup.js +1 -1
  179. package/dist/setup/steps/tasks.js +25 -15
  180. package/dist/sources/provider-factory.js +17 -18
  181. package/dist/sources/providers/filesystem.js +2 -3
  182. package/dist/sources/providers/git-install.js +7 -1
  183. package/dist/sources/providers/git-provider.js +0 -3
  184. package/dist/sources/providers/git-stash.js +0 -17
  185. package/dist/sources/providers/npm.js +2 -4
  186. package/dist/sources/providers/provider-utils.js +5 -10
  187. package/dist/sources/providers/website.js +0 -2
  188. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  189. package/dist/sources/website-url.js +2 -2
  190. package/dist/storage/database.js +9 -35
  191. package/dist/storage/repositories/improve-ledger-repository.js +168 -0
  192. package/dist/storage/repositories/index-connection.js +34 -70
  193. package/dist/storage/repositories/index-entries-repository.js +69 -111
  194. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  195. package/dist/storage/repositories/index-entry-schema.js +83 -269
  196. package/dist/storage/repositories/index-fts-repository.js +86 -256
  197. package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
  198. package/dist/storage/repositories/index-meta-repository.js +6 -4
  199. package/dist/storage/repositories/index-schema.js +192 -220
  200. package/dist/storage/repositories/index-utility-repository.js +8 -29
  201. package/dist/storage/repositories/index-vec-repository.js +133 -414
  202. package/dist/storage/repositories/outcome-repository.js +2 -1
  203. package/dist/storage/repositories/proposals-repository.js +35 -0
  204. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  205. package/dist/storage/repositories/task-history-repository.js +26 -4
  206. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  207. package/dist/storage/sqlite-migrations.js +136 -0
  208. package/dist/storage/sqlite-pragmas.js +11 -9
  209. package/dist/storage/sqlite-transaction.js +170 -0
  210. package/dist/storage/state-db-integrity.js +34 -27
  211. package/dist/tasks/activation-config.js +134 -62
  212. package/dist/tasks/backends/cron.js +129 -277
  213. package/dist/tasks/backends/exec-utils.js +2 -5
  214. package/dist/tasks/backends/launchd.js +125 -745
  215. package/dist/tasks/backends/schtasks.js +101 -620
  216. package/dist/tasks/prepare/prepare-support.js +5 -15
  217. package/dist/tasks/prepare/prepare.js +0 -2
  218. package/dist/tasks/resolve-akm-bin.js +20 -79
  219. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  220. package/dist/tasks/scheduler-binding.js +18 -238
  221. package/dist/tasks/scheduler-invocation.js +52 -52
  222. package/dist/tasks/scheduler-lock.js +53 -0
  223. package/dist/tasks/scheduler-sync.js +363 -679
  224. package/dist/tasks/source/parse-task-source.js +160 -10
  225. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  226. package/dist/tasks/source/task-to-v4.js +2 -2
  227. package/dist/workflows/authoring/authoring.js +3 -12
  228. package/dist/workflows/compile.js +211 -0
  229. package/dist/workflows/concurrency-policy.js +13 -74
  230. package/dist/workflows/exec/child-invocation.js +3 -17
  231. package/dist/workflows/exec/child-workflow.js +32 -141
  232. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  233. package/dist/workflows/exec/environment.js +98 -0
  234. package/dist/workflows/exec/exec-unit.js +33 -140
  235. package/dist/workflows/exec/frozen-judge.js +7 -59
  236. package/dist/workflows/exec/native-executor.js +82 -341
  237. package/dist/workflows/exec/param-secrets.js +29 -47
  238. package/dist/workflows/exec/run-workflow.js +154 -387
  239. package/dist/workflows/exec/scheduler.js +9 -36
  240. package/dist/workflows/exec/step-work.js +127 -430
  241. package/dist/workflows/exec/unit-dispatch.js +11 -63
  242. package/dist/workflows/exec/unit-writer.js +8 -52
  243. package/dist/workflows/exec/worktree.js +39 -273
  244. package/dist/workflows/freeze/child-output-references.js +4 -15
  245. package/dist/workflows/freeze/environment.js +99 -92
  246. package/dist/workflows/freeze/freeze.js +172 -0
  247. package/dist/workflows/freeze/step-values.js +19 -21
  248. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  249. package/dist/workflows/freeze/targets/command.js +10 -33
  250. package/dist/workflows/freeze/targets/script.js +5 -12
  251. package/dist/workflows/freeze/targets/shell.js +3 -6
  252. package/dist/workflows/freeze/targets/task.js +25 -80
  253. package/dist/workflows/freeze/task-bindings.js +20 -67
  254. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  255. package/dist/workflows/ir/params.js +6 -51
  256. package/dist/workflows/ir/plan-hash.js +2 -34
  257. package/dist/workflows/parser.js +140 -43
  258. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  259. package/dist/workflows/renderer.js +36 -69
  260. package/dist/workflows/resource-limits.js +12 -120
  261. package/dist/workflows/runtime/agent-identity.js +8 -40
  262. package/dist/workflows/runtime/run-outputs.js +3 -6
  263. package/dist/workflows/runtime/run-plan.js +316 -0
  264. package/dist/workflows/runtime/runs.js +48 -200
  265. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  266. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  267. package/dist/workflows/validate-summary.js +2 -7
  268. package/docs/integration/bundling-akm.md +49 -42
  269. package/docs/migration/README.md +1 -0
  270. package/docs/migration/release-notes/0.9.17.md +41 -0
  271. package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
  272. package/docs/reference/cli.md +182 -125
  273. package/docs/reference/configuration.md +49 -56
  274. package/docs/reference/data-and-telemetry.md +19 -20
  275. package/docs/reference/tasks.md +86 -38
  276. package/docs/reference/workflow-schema.md +14 -18
  277. package/docs/reference/workflows.md +6 -9
  278. package/package.json +1 -1
  279. package/schemas/akm-config.json +87 -406
  280. package/dist/commands/health/advisories.js +0 -150
  281. package/dist/commands/health/metrics.js +0 -329
  282. package/dist/commands/health/surfaces.js +0 -102
  283. package/dist/commands/improve/anti-collapse.js +0 -83
  284. package/dist/commands/improve/collapse-detector.js +0 -432
  285. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  286. package/dist/commands/improve/consolidate/merge.js +0 -146
  287. package/dist/commands/improve/distill/promote-memory.js +0 -329
  288. package/dist/commands/improve/distill/quality-gate.js +0 -500
  289. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  290. package/dist/commands/improve/proposal-envelope.js +0 -31
  291. package/dist/commands/improve/run-context.js +0 -123
  292. package/dist/commands/improve/shared.js +0 -21
  293. package/dist/commands/improve/source-identity.js +0 -28
  294. package/dist/commands/improve/triage.js +0 -96
  295. package/dist/commands/proposal/drain-policies.js +0 -151
  296. package/dist/commands/sources/update-transaction.js +0 -220
  297. package/dist/core/action-contributors.js +0 -28
  298. package/dist/core/config/config-version-shim.js +0 -101
  299. package/dist/core/config/retired-experimental-keys-shim.js +0 -62
  300. package/dist/core/fs-txn.js +0 -405
  301. package/dist/core/lexical-score.js +0 -25
  302. package/dist/core/maintenance-barrier.js +0 -167
  303. package/dist/execution/executable-identity.js +0 -105
  304. package/dist/execution/guarded-source.js +0 -427
  305. package/dist/indexer/graph/graph-boost.js +0 -427
  306. package/dist/indexer/graph/graph-dedup.js +0 -95
  307. package/dist/indexer/search/name-match.js +0 -35
  308. package/dist/indexer/search/ranking-contributors.js +0 -515
  309. package/dist/indexer/walk/project-context.js +0 -192
  310. package/dist/integrations/agent/execution-cascade.js +0 -566
  311. package/dist/integrations/agent/execution-definitions.js +0 -202
  312. package/dist/integrations/agent/execution-lowering.js +0 -841
  313. package/dist/integrations/agent/execution-preparation.js +0 -98
  314. package/dist/integrations/agent/inline-execution.js +0 -74
  315. package/dist/registry/create-provider-registry.js +0 -29
  316. package/dist/registry/pinned-request-helper.js +0 -247
  317. package/dist/registry/pinned-transport.js +0 -717
  318. package/dist/sources/providers/index.js +0 -14
  319. package/dist/storage/engines/sqlite-migrations.js +0 -271
  320. package/dist/storage/repositories/canaries-repository.js +0 -107
  321. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  322. package/dist/storage/repositories/registry-cache.js +0 -113
  323. package/dist/tasks/scheduler-sync-preview.js +0 -52
  324. package/dist/workflows/freeze/resolve-steps.js +0 -86
  325. package/dist/workflows/freeze/source-freeze.js +0 -64
  326. package/dist/workflows/ir/compile.js +0 -321
  327. package/dist/workflows/ir/environment-v4.js +0 -330
  328. package/dist/workflows/ir/freeze-v4.js +0 -153
  329. package/dist/workflows/ir/schema-v4.js +0 -745
  330. package/dist/workflows/ir/schema.js +0 -354
  331. package/dist/workflows/program/schema.js +0 -78
  332. package/dist/workflows/runtime/checkin.js +0 -57
  333. package/dist/workflows/runtime/plan-classifier.js +0 -196
  334. package/dist/workflows/runtime/unit-checkin.js +0 -45
  335. package/dist/workflows/runtime/unit-phases.js +0 -20
  336. package/dist/workflows/schema.js +0 -4
  337. package/dist/workflows/source-ir/compile.js +0 -200
  338. package/dist/workflows/source-ir/program.js +0 -50
  339. package/dist/workflows/source-ir/result.js +0 -26
  340. package/dist/workflows/source-ir/schema.js +0 -786
  341. package/dist/workflows/source-ir/triggers.js +0 -79
  342. package/dist/workflows/source-ir/uses.js +0 -40
  343. package/dist/workflows/validator.js +0 -60
@@ -4,71 +4,25 @@
4
4
  import { getAllEntries } from "../../storage/repositories/index-entries-repository.js";
5
5
  import { getUtilityScoresByIds } from "../../storage/repositories/index-utility-repository.js";
6
6
  import { WARM_START_CAP } from "./outcome-loop.js";
7
- // ── One day in ms ─────────────────────────────────────────────────────────────
8
7
  const DAY_MS = 86_400_000;
9
- // ── Recency decay half-life (mirrors the proactive-maintenance prototype) ─────
10
8
  const RECENCY_HALFLIFE_DAYS = 21;
11
- // ── Recency-floor half-life (R4 — SHY-style continuous downscaling) ──────────
12
- //
13
- // The recency floor itself decays on this (much longer) half-life so an
14
- // unreviewed-forever asset keeps drifting down instead of parking at the 0.1
15
- // floor. This replaces the deleted homeostatic demotion pass (which was
16
- // default-off and self-undoing — every salience recompute clobbered it);
17
- // folding the decay into the always-applied recency term makes it persist by
18
- // construction. At 180 days the floor halves; a 1-year-stale asset sits at
19
- // ~0.025 instead of 0.1.
9
+ /**
10
+ * The recency floor itself halves every 180 days, so an asset nobody uses keeps
11
+ * drifting down instead of parking at the floor.
12
+ */
20
13
  const RECENCY_FLOOR_HALFLIFE_DAYS = 180;
21
- // Absolute epsilon under the decaying floor. Keeps the frequency term ordinal
22
- // for assets whose last-use timestamp is unknown (utility_scores has no
23
- // last_used_at) — without it their retrieval salience collapses to exactly 0
24
- // and frequency ordering is lost for maintenance selection.
14
+ /** Keeps frequency ordinal for assets whose last use is unknown. */
25
15
  const RECENCY_EPSILON = 0.01;
26
- // ── Size proxy floor (avoids log10(0)) ────────────────────────────────────────
27
16
  const SIZE_FLOOR_BYTES = 200;
28
- // ── Projection weights ────────────────────────────────────────────────────────
29
- //
30
- // These constants are the DEFAULT ranking weights (R1 loop closure). Operators
31
- // can opt back out to the WS-1 parity weights (w_e=0.30, w_r=0.70, w_o=0) via
32
- // `improve.salience.outcomeWeightEnabled: false`.
33
- //
34
- // WS-2 split (w_e=0.25, w_o=0.15, w_r=0.60, sum = 1.0):
35
- // [exp] Expert recommendation: encoding should be moderate so a type-importance
36
- // stub does not completely dominate; retrieval should be strong since it directly
37
- // measures use; outcome provides a quality signal proportional to usefulness.
38
- //
39
- // Re-tune via the Part-V measurement protocol if the throughput/quality gate
40
- // shows regression after enabling the outcome weight.
41
- export const W_ENCODING = 0.25; // WS-2 target encoding weight (w_e)
42
- export const W_OUTCOME = 0.15; // WS-2 target outcome weight (w_o)
43
- export const W_RETRIEVAL = 0.6; // WS-2 target retrieval weight (w_r)
44
- // Compile-time guard: weights must sum to 1.0 (±ε). The TS initializer runs
45
- // at module load, not build time, so this acts as a startup assertion.
46
- if (Math.abs(W_ENCODING + W_OUTCOME + W_RETRIEVAL - 1.0) > 1e-9) {
47
- throw new Error(`salience.ts: W_ENCODING + W_OUTCOME + W_RETRIEVAL must equal 1.0 (got ${W_ENCODING + W_OUTCOME + W_RETRIEVAL})`);
48
- }
49
- // ── WS-1 parity weights ───────────────────────────────────────────────────────
50
- //
51
- // These constants reflect the WS-1 parity weights used when the operator
52
- // explicitly opts out (`outcomeWeightEnabled: false`). They preserve the
53
- // WS-1 two-way split (w_e=0.30, w_r=0.70) with w_o=0 so outcome does not
54
- // affect rankScore in the opt-out mode.
55
- //
56
- // Named here (rather than inline literals in the else branch) so a future
57
- // re-tune has a single source of truth and the sum-to-1 guard below catches
58
- // any accidental mis-edit.
59
- export const W_ENCODING_PARITY = 0.3; // WS-1 parity encoding weight
60
- export const W_OUTCOME_PARITY = 0; // WS-1 parity outcome weight (0 = disabled)
61
- export const W_RETRIEVAL_PARITY = 0.7; // WS-1 parity retrieval weight
62
- // Startup guard: parity triple must also sum to 1.0 (±ε).
63
- if (Math.abs(W_ENCODING_PARITY + W_OUTCOME_PARITY + W_RETRIEVAL_PARITY - 1.0) > 1e-9) {
64
- throw new Error(`salience.ts: W_ENCODING_PARITY + W_OUTCOME_PARITY + W_RETRIEVAL_PARITY must equal 1.0 (got ${W_ENCODING_PARITY + W_OUTCOME_PARITY + W_RETRIEVAL_PARITY})`);
65
- }
66
- // ── Type-importance fallback weights (#608 landed) ────────────────────────────
67
- //
68
- // The real encoding salience estimator is `scoreEncodingSalience` in
69
- // `encoding-salience.ts` (#608). These weights are the fallback used when
70
- // `SalienceInputs.encodingSalience` is absent (pre-#608 assets without a
71
- // frontmatter `salience:` field or a state.db row seeded by distill).
17
+ /** Default weights: the outcome term is on (opt out with `improve.salience.outcomeWeightEnabled: false`). */
18
+ export const W_ENCODING = 0.25;
19
+ export const W_OUTCOME = 0.15;
20
+ export const W_RETRIEVAL = 0.6;
21
+ /** Weights with the outcome term off. */
22
+ export const W_ENCODING_PARITY = 0.3;
23
+ export const W_OUTCOME_PARITY = 0;
24
+ export const W_RETRIEVAL_PARITY = 0.7;
25
+ /** Encoding salience by type, for assets with no content-derived score yet. */
72
26
  export const DEFAULT_TYPE_ENCODING_WEIGHTS = Object.freeze({
73
27
  skill: 0.9,
74
28
  agent: 0.9,
@@ -79,218 +33,78 @@ export const DEFAULT_TYPE_ENCODING_WEIGHTS = Object.freeze({
79
33
  script: 0.6,
80
34
  memory: 0.5,
81
35
  });
82
- /** Default encoding salience for types not in the table above. */
83
36
  export const DEFAULT_ENCODING_SALIENCE = 0.5;
84
- // ── Core computation ─────────────────────────────────────────────────────────
85
- /**
86
- * Compute the salience vector for one asset.
87
- *
88
- * Pure function — no I/O. All inputs are pre-fetched by the caller.
89
- */
37
+ const clamp01 = (value) => Math.min(1, Math.max(0, value));
38
+ /** The salience vector for one asset (pure). */
90
39
  export function computeSalience(inputs) {
91
40
  const now = inputs.now ?? Date.now();
92
- // ── Encoding salience ────────────────────────────────────────────────────────
93
- //
94
- // When `inputs.encodingSalience` is provided (computed by `scoreEncodingSalience`
95
- // in encoding-salience.ts at extract/distill time, #608), use it directly.
96
- // Fall back to the type-importance stub only when the caller has not yet
97
- // computed a content-based score (e.g. on older assets before the first
98
- // staleness refresh runs).
99
41
  const encodingSource = inputs.encodingSalience !== undefined ? "content" : "type-stub";
100
42
  const encoding = inputs.encodingSalience !== undefined
101
- ? Math.min(1, Math.max(0, inputs.encodingSalience))
43
+ ? clamp01(inputs.encodingSalience)
102
44
  : (DEFAULT_TYPE_ENCODING_WEIGHTS[inputs.type] ?? DEFAULT_ENCODING_SALIENCE);
103
- // ── Outcome salience (WS-2 active) ────────────────────────────────────────
104
- //
105
- // When `inputs.outcomeSalience` is provided (WS-2 has populated asset_outcome
106
- // for this ref), use it directly — it has already been normalised by
107
- // `outcomeScoreToSalience` in outcome-loop.ts (value in [DIVERSITY_FLOOR, 1]).
108
- //
109
- // When absent (new asset, no WS-2 row yet): fall back to the warm-start seed
110
- // from `utilityScore` clipped to [0, WARM_START_CAP], matching the seed
111
- // value that `updateAssetOutcome` writes on first row creation. This ensures
112
- // `outcomeSalience` is non-zero at launch for assets with utility history
113
- // (avoiding the starvation problem described in the plan §WS-2 warm start).
114
- let outcome;
115
- if (inputs.outcomeSalience !== undefined) {
116
- // Direct pass-through — caller already normalised via outcomeScoreToSalience.
117
- outcome = Math.min(1, Math.max(0, inputs.outcomeSalience));
118
- }
119
- else {
120
- // Warm-start fallback: clip utility to [0, WARM_START_CAP] so the
121
- // outcomeSalience term contributes a modest non-zero baseline.
122
- outcome = Math.min(WARM_START_CAP, Math.max(0, inputs.utilityScore ?? 0));
123
- }
124
- // ── Retrieval salience ─────────────────────────────────────────────────────
125
- //
126
- // Formula: log(1 + freq) × recencyDecay
127
- // log(1+freq): sub-linear frequency term (same as proactive-maintenance prototype).
128
- // recencyDecay: max(ε, 0.1·0.5^(useAgeDays/180) + 0.5^(useAgeDays/21)) —
129
- // the fast term halves every 21 days; the 0.1 floor itself halves every
130
- // 180 days (R4: SHY-style continuous downscaling — an unreviewed-forever
131
- // asset keeps drifting down instead of parking at the floor). The ε=0.01
132
- // epsilon keeps the frequency term ordinal for unknown-last-use assets.
133
- // lastUseMs=0/undefined → useAgeDays=9999 → recencyDecay=ε.
134
- //
135
- // The recency term is MANDATORY (plan requirement §WS-1 step 2). Without it
136
- // retrievalSalience degenerates to a non-decaying frequency count. This
137
- // always-applied decay replaces the deleted homeostatic demotion pass.
45
+ // Without an outcome row, utility (capped) seeds the outcome term — the same
46
+ // seed the first outcome row gets — so it is not zero at launch.
47
+ const outcome = inputs.outcomeSalience !== undefined
48
+ ? clamp01(inputs.outcomeSalience)
49
+ : Math.min(WARM_START_CAP, Math.max(0, inputs.utilityScore ?? 0));
50
+ // log(1 + freq) × recency, where recency halves every 21 days above a floor
51
+ // that itself halves every 180 days; soft-capped to [0, 1).
138
52
  const lastUseMs = inputs.lastUseMs ?? 0;
139
53
  const useAgeDays = lastUseMs > 0 ? (now - lastUseMs) / DAY_MS : 9999;
140
54
  const recencyDecay = Math.max(RECENCY_EPSILON, 0.1 * 0.5 ** (useAgeDays / RECENCY_FLOOR_HALFLIFE_DAYS) + 0.5 ** (useAgeDays / RECENCY_HALFLIFE_DAYS));
141
55
  const rawRetrieval = Math.log(1 + inputs.retrievalFreq) * recencyDecay;
142
- // ── Size penalty ─────────────────────────────────────────────────────────────
143
- // 1/log10(size): larger assets are slightly deprioritized (same as proactive prototype).
144
- const sizeProxy = Math.max(SIZE_FLOOR_BYTES, inputs.sizeBytes ?? 0);
145
- const sizePenalty = 1 / Math.log10(sizeProxy);
146
- // ── Projection → rankScore ────────────────────────────────────────────────
147
- //
148
- // Raw projection may be > 1 (log retrieval terms can exceed 1 for high freq + fresh use).
149
- // Normalize by the theoretical maximum of the retrieval component:
150
- // max retrievalRaw = log(1 + Infinity) × (0.1 + 1.0) = Infinity, so we
151
- // cap instead — rankScore is clamped to [0,1] after applying the size penalty.
152
- //
153
- // Normalization approach: we scale the combined linear sum to [0,1] by clamping,
154
- // after applying the size penalty. The encoding term is already in [0,1]; the
155
- // retrieval term is open-ended but bounded in practice by log(1+N)×1.1 where N
156
- // is the retrieval count. We normalize `retrieval` to [0,1] using a soft cap:
157
- // retrieval_normalized = rawRetrieval / (rawRetrieval + 1)
158
- // which asymptotes to 1 and equals 0.5 at rawRetrieval=1. This is the same
159
- // formula used for MemRL utility updates.
160
56
  const retrieval = rawRetrieval / (rawRetrieval + 1);
161
- // ── Weight selection (R1 — outcome loop closed by default) ───────────────
162
- //
163
- // When `outcomeWeightEnabled` is true/absent (DEFAULT ON since the G2
164
- // saturation cap landed): use WS-2 weights (w_e=0.25, w_o=0.15, w_r=0.60)
165
- // so the prediction-error outcome signal actually shapes rankScore — this
166
- // is the R1 loop-closure.
167
- //
168
- // When `outcomeWeightEnabled` is explicitly false (operator opt-out via
169
- // `improve.salience.outcomeWeightEnabled: false`): fall back to the WS-1
170
- // parity weights (w_e=0.30, w_r=0.70, w_o=0). The `outcome` sub-score is
171
- // still computed and stored for observability in that mode.
172
- let we;
173
- let wo;
174
- let wr;
175
- if (inputs.outcomeWeightEnabled !== false) {
176
- // WS-2 active (default): three-way split.
177
- we = W_ENCODING; // 0.25
178
- wo = W_OUTCOME; // 0.15
179
- wr = W_RETRIEVAL; // 0.60
180
- }
181
- else {
182
- // WS-1 parity (opt-out): w_o=0, redistribute to WS-1 proportions.
183
- // Original WS-1 split was w_e=0.30, w_r=0.70.
184
- we = W_ENCODING_PARITY;
185
- wo = W_OUTCOME_PARITY;
186
- wr = W_RETRIEVAL_PARITY;
187
- }
188
- const rawRankScore = (we * encoding + wo * outcome + wr * retrieval) * sizePenalty;
189
- const rankScore = Math.min(1, Math.max(0, rawRankScore));
57
+ // Larger assets rank slightly lower.
58
+ const sizePenalty = 1 / Math.log10(Math.max(SIZE_FLOOR_BYTES, inputs.sizeBytes ?? 0));
59
+ const [we, wo, wr] = inputs.outcomeWeightEnabled !== false
60
+ ? [W_ENCODING, W_OUTCOME, W_RETRIEVAL]
61
+ : [W_ENCODING_PARITY, W_OUTCOME_PARITY, W_RETRIEVAL_PARITY];
62
+ const rankScore = clamp01((we * encoding + wo * outcome + wr * retrieval) * sizePenalty);
190
63
  return { encoding, outcome, retrieval, rankScore, encodingSource };
191
64
  }
192
- // ── state.db persistence ─────────────────────────────────────────────────────
193
- //
194
- // The three sub-scores live in state.db::asset_salience. Raw SQL now lives in
195
- // storage/repositories/salience-repository.ts (#672 part 2) — extracted
196
- // verbatim, only relocated behind the repository boundary. Re-exported here so
197
- // existing importers of this module resolve unchanged. Migrations live in
198
- // state-db.ts (migration 009).
199
65
  export { getAllRankScores, getAssetSalience, getConsecutiveNoOps, recordNoOp, resetConsecutiveNoOps, upsertAssetSalience, } from "../../storage/repositories/salience-repository.js";
200
- /**
201
- * Does this row carry a genuine content-derived `encoding_salience` (#644)?
202
- *
203
- * Unknown provenance is not treated as content-derived.
204
- */
66
+ /** Whether a stored row's encoding salience is content-derived (unknown provenance is not). */
205
67
  export function isContentEncodingRow(row) {
206
68
  return row.encoding_source === "content";
207
69
  }
208
- // ── Consolidation-selection dampener constants ────────────────────────────────
209
- //
210
- // Assets with consecutive_no_ops >= THRESHOLD are deprioritised in the
211
- // SELECTION ORDER only. The persisted rank_score is intentionally left
212
- // unchanged so stable assets remain fully retrievable by other callers.
213
- //
214
- // Tuning guidance:
215
- // THRESHOLD — how many consecutive no-op runs before dampening kicks in.
216
- // 3 means "skipped three times in a row", which signals the
217
- // LLM consistently has nothing to say about this asset.
218
- // FACTOR — multiplicative penalty on the effective selection score.
219
- // 0.5 halves the apparent score so a dampened asset sorts
220
- // after any peer with >= half its rankScore.
221
- export const SALIENCE_NO_OP_DAMPEN_THRESHOLD = 3;
222
- export const SALIENCE_NO_OP_DAMPEN_FACTOR = 0.5;
223
70
  /**
224
- * Emit the forgetting-safety rank-change distribution report.
225
- *
226
- * Compares the provided `newRanks` (Map<ref, position (1-indexed)>) against
227
- * the provided `oldRanks` and flags refs that were in the old top-200 but
228
- * are now below position 500 as "forgetting candidates".
229
- *
230
- * Caller is responsible for computing old/new rank positions before and after
231
- * the WS-1 formula cutover. Called once at cutover, not every run.
232
- *
233
- * @param oldRanks - Map<ref, 1-indexed rank position> under the OLD formula.
234
- * @param newRanks - Map<ref, 1-indexed rank position> under the NEW formula.
235
- * @param oldTopN - Assets in old top-N to guard (default: 200).
236
- * @param forgettingThreshold - New rank position below which a fall is flagged (default: 500).
71
+ * Consolidation selection only: an asset whose last three runs were no-ops
72
+ * sorts at half its score. The stored rank is untouched.
237
73
  */
74
+ export const SALIENCE_NO_OP_DAMPEN_THRESHOLD = 3;
75
+ export const SALIENCE_NO_OP_DAMPEN_FACTOR = 0.5;
76
+ /** Forgetting safety: compare 1-indexed rank positions between two rankings. */
238
77
  export function buildRankChangeReport(oldRanks, newRanks, oldTopN = 200, forgettingThreshold = 500) {
239
78
  const allChanges = [];
240
- const forgettingCandidates = [];
241
79
  for (const [ref, oldRank] of oldRanks) {
242
80
  const newRank = newRanks.get(ref);
243
- if (newRank === undefined)
244
- continue; // ref not in new ranking
245
- const rankDelta = newRank - oldRank; // positive = fell in rank
246
- allChanges.push({ ref, oldRank, newRank, rankDelta });
247
- if (oldRank <= oldTopN && newRank > forgettingThreshold) {
248
- forgettingCandidates.push({ ref, oldRank, newRank, rankDelta });
249
- }
81
+ if (newRank !== undefined)
82
+ allChanges.push({ ref, oldRank, newRank, rankDelta: newRank - oldRank });
250
83
  }
251
- // Sort by magnitude of rank drop (most dramatic first).
252
- forgettingCandidates.sort((a, b) => b.rankDelta - a.rankDelta);
84
+ const forgettingCandidates = allChanges
85
+ .filter((c) => c.oldRank <= oldTopN && c.newRank > forgettingThreshold)
86
+ .sort((a, b) => b.rankDelta - a.rankDelta);
253
87
  return { forgettingCandidates, allChanges };
254
88
  }
255
- // ── Last-use timestamp lookup helper ─────────────────────────────────────────
256
- //
257
- // Wraps the index DB query to retrieve the last-retrieval timestamp per ref,
258
- // so callers do not need to import the raw db helpers directly. Returns a Map
259
- // keyed by the same ref strings passed in.
260
- //
261
- // Source: `utility_scores.last_used_at` (ISO-8601 string) joined to entries
262
- // via entry_id. WS-2 may later supersede this with `asset_outcome.last_retrieved_at`.
263
- /**
264
- * Build a Map<ref, lastUseMs> from the index database's utility_scores table.
265
- *
266
- * Returns only refs that have a non-null `last_used_at`. Refs absent from the
267
- * map should be treated as never retrieved (lastUseMs = 0).
268
- *
269
- * @param indexDb - An open read-capable index database connection.
270
- * @param candidates - Planned refs carrying their canonical durable item_ref.
271
- */
89
+ /** `ref → last retrieval (ms)` from the index's utility scores; absent means never retrieved. */
272
90
  export function getLastUseMsByRef(indexDb, candidates) {
273
91
  const result = new Map();
274
92
  if (candidates.length === 0)
275
93
  return result;
276
94
  const refByItemRef = new Map(candidates.flatMap((candidate) => (candidate.itemRef ? [[candidate.itemRef, candidate.ref]] : [])));
277
- const allEntries = getAllEntries(indexDb);
278
95
  const idToRef = new Map();
279
- for (const indexed of allEntries) {
96
+ for (const indexed of getAllEntries(indexDb)) {
280
97
  const ref = refByItemRef.get(indexed.itemRef);
281
98
  if (ref)
282
99
  idToRef.set(indexed.id, ref);
283
100
  }
284
- const ids = [...idToRef.keys()];
285
- if (ids.length === 0)
101
+ if (idToRef.size === 0)
286
102
  return result;
287
- const { global: scores } = getUtilityScoresByIds(indexDb, ids);
103
+ const scores = getUtilityScoresByIds(indexDb, [...idToRef.keys()]);
288
104
  for (const [id, row] of scores) {
289
105
  const ref = idToRef.get(id);
290
- if (!ref)
291
- continue;
292
106
  const lastUsedAt = row.lastUsedAt;
293
- if (!lastUsedAt)
107
+ if (!ref || !lastUsedAt)
294
108
  continue;
295
109
  const ms = typeof lastUsedAt === "number" ? lastUsedAt : Date.parse(lastUsedAt);
296
110
  if (ms > 0)
@@ -2,37 +2,18 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  /**
5
- * Session asset generation for the `extract` pass (#561).
6
- *
7
- * After the extractor distills memory proposals from a session, it ALSO writes
8
- * the session itself to the stash as a first-class `session` asset so any agent
9
- * — on any harness — can discover prior work via `akm search` / `akm curate`.
10
- *
11
- * Design constraints (see #561):
12
- * - ADDITIVE + FAIL-OPEN + CONFIG-GATED. Disabled (or no LLM provider) →
13
- * extract behaves EXACTLY as before. Nothing is written.
14
- * - The LLM summary call routes through the injectable {@link SessionSummaryGenerator}
15
- * seam so tests never touch a real provider, and so production wraps the
16
- * call in the existing `tryLlmFeature` fail-open pattern.
17
- * - The `log_path` + `access` frontmatter fields are the durable correlation
18
- * key — they survive index rebuilds (the body is re-derived from disk).
19
- *
20
- * The asset is written to `sessions/<harness>/<session-id>.md`; the registered
21
- * `session` asset type (see `asset-spec.ts`) makes the normal index pass pick it
22
- * up for FTS + vector search with no special-casing.
5
+ * Session assets (#561): besides its memory proposals, extract writes each
6
+ * session to `sessions/<harness>/<session-id>.md` as a searchable `session`
7
+ * asset. Additive and fail-open — no summary means nothing is written — and
8
+ * `log_path` + `access` in the frontmatter tell any agent how to read the raw log.
23
9
  */
24
10
  import fs from "node:fs";
25
11
  import path from "node:path";
26
12
  import { stashDirFor } from "../../core/asset/asset-placement.js";
27
13
  import { assembleAsset } from "../../core/asset/asset-serialize.js";
28
14
  import { conceptIdFromTypeName } from "../../core/asset/resolve-ref.js";
15
+ import { parseEmbeddedJsonResponse } from "../../core/parse.js";
29
16
  import { recordWrittenPath } from "../../core/write-provenance.js";
30
- /**
31
- * JSON Schema for the session-summary LLM call. Strict so providers that
32
- * support schema enforcement constrain the output upstream; the parser only
33
- * has to handle the happy path. `additionalProperties: false` drops any
34
- * hallucinated keys before parsing.
35
- */
36
17
  export const SESSION_SUMMARY_JSON_SCHEMA = {
37
18
  type: "object",
38
19
  required: ["summary", "key_topics"],
@@ -43,11 +24,7 @@ export const SESSION_SUMMARY_JSON_SCHEMA = {
43
24
  tags: { type: "array", items: { type: "string" } },
44
25
  },
45
26
  };
46
- /**
47
- * Render a compact transcript snippet from session events for the summary
48
- * prompt. Mirrors the extract transcript format but caps total length so the
49
- * summary prompt stays bounded regardless of session size.
50
- */
27
+ /** The transcript for the summary prompt, capped at `maxChars`. */
51
28
  function renderTranscriptForSummary(events, maxChars = 12_000) {
52
29
  if (events.length === 0)
53
30
  return "(empty — no events)";
@@ -66,11 +43,6 @@ function renderTranscriptForSummary(events, maxChars = 12_000) {
66
43
  }
67
44
  return lines.join("\n\n") || "(empty — no textual events)";
68
45
  }
69
- /**
70
- * Build the user prompt for the session-summary LLM call. Pure — no IO. The
71
- * model is asked for a dense 2–4 sentence summary plus key topics, optimised
72
- * for semantic search recall.
73
- */
74
46
  export function buildSessionSummaryPrompt(data) {
75
47
  const ref = data.ref;
76
48
  const startedAt = isoOrUndefined(ref.startedAt) ?? "unknown";
@@ -92,33 +64,13 @@ export function buildSessionSummaryPrompt(data) {
92
64
  'Respond as JSON: {"summary": string, "key_topics": string[], "tags"?: string[]}.',
93
65
  ].join("\n");
94
66
  }
95
- /**
96
- * Parse the session-summary LLM response into a {@link SessionSummaryResult}.
97
- * Defensive: tolerates prose preamble/postamble around the JSON, and returns
98
- * `undefined` when nothing usable parses (fail-open: no asset is written).
99
- */
67
+ /** The summary JSON, tolerating prose around it; `undefined` when nothing usable parses. */
100
68
  export function parseSessionSummary(raw) {
101
69
  if (!raw || raw.trim().length === 0)
102
70
  return undefined;
103
- let parsed;
104
- try {
105
- parsed = JSON.parse(raw);
106
- }
107
- catch {
108
- const start = raw.indexOf("{");
109
- const end = raw.lastIndexOf("}");
110
- if (start === -1 || end <= start)
111
- return undefined;
112
- try {
113
- parsed = JSON.parse(raw.slice(start, end + 1));
114
- }
115
- catch {
116
- return undefined;
117
- }
118
- }
119
- if (!parsed || typeof parsed !== "object")
71
+ const obj = parseEmbeddedJsonResponse(raw);
72
+ if (!obj || typeof obj !== "object" || Array.isArray(obj))
120
73
  return undefined;
121
- const obj = parsed;
122
74
  const summary = typeof obj.summary === "string" ? obj.summary.trim() : "";
123
75
  if (summary.length === 0)
124
76
  return undefined;
@@ -130,62 +82,40 @@ export function parseSessionSummary(raw) {
130
82
  : undefined;
131
83
  return { summary, keyTopics, ...(tags && tags.length > 0 ? { tags } : {}) };
132
84
  }
133
- /**
134
- * Decide whether a session is long enough to index. `minDurationMinutes <= 0`
135
- * disables the gate. When either timestamp is missing we DON'T gate it out —
136
- * fail-open toward indexing, since a missing timestamp is not evidence of a
137
- * trivial session.
138
- */
85
+ /** Long enough to index (`<= 0` disables; a missing timestamp is no evidence of a trivial session). */
139
86
  export function sessionMeetsDurationGate(data, minDurationMinutes) {
140
87
  if (!Number.isFinite(minDurationMinutes) || minDurationMinutes <= 0)
141
88
  return true;
142
89
  const { startedAt, endedAt } = data.ref;
143
90
  if (typeof startedAt !== "number" || typeof endedAt !== "number")
144
91
  return true;
145
- const durationMinutes = (endedAt - startedAt) / 60_000;
146
- return durationMinutes >= minDurationMinutes;
92
+ return (endedAt - startedAt) / 60_000 >= minDurationMinutes;
147
93
  }
148
- /**
149
- * Build per-harness `access` instructions for reading the raw session log.
150
- *
151
- * Documented convention (#561, checklist item "Document `access` field
152
- * convention per harness"): the string tells a downstream agent exactly how to
153
- * read and parse the source at `log_path`. New harnesses fall back to a generic
154
- * `cat <log_path>` hint for file-backed logs.
155
- */
94
+ /** How an agent reads and parses the raw log at `log_path`, per harness (`cat` otherwise). */
156
95
  export function buildSessionAccessInstructions(harness, logPath, sessionId) {
157
- const canonical = harness;
158
- if (canonical === "claude") {
96
+ if (harness === "claude") {
159
97
  return [
160
98
  `Read with: cat ${logPath}`,
161
99
  `Parse messages: jq -r 'select(.type=="message") | .message.content[]? | select(.type=="text") | .text' ${logPath}`,
162
100
  ].join("\n");
163
101
  }
164
- if (canonical === "opencode") {
102
+ if (harness === "opencode") {
165
103
  return [
166
104
  `Open the SQLite database at ${JSON.stringify(logPath)} in read-only mode.`,
167
105
  "Query: SELECT m.data, p.data FROM message AS m JOIN part AS p ON p.message_id = m.id WHERE m.session_id = ? AND p.session_id = ? ORDER BY m.time_created, p.time_created;",
168
106
  `Bind both parameters to ${JSON.stringify(sessionId)}.`,
169
107
  ].join("\n");
170
108
  }
171
- // Generic fallback — file-backed logs are always readable with cat.
172
109
  return `Read with: cat ${logPath}`;
173
110
  }
174
- /** ISO-8601 (UTC) from a ms-epoch, or undefined when absent/non-finite. */
175
111
  function isoOrUndefined(ms) {
176
112
  return typeof ms === "number" && Number.isFinite(ms) ? new Date(ms).toISOString() : undefined;
177
113
  }
178
114
  /** Default session-name slug: `<harness>-session-<yyyy-mm-dd>-<shortId>`. */
179
115
  export function buildSessionAssetName(harness, sessionId, startedAtMs) {
180
- const canonical = harness;
181
- const datePart = isoOrUndefined(startedAtMs)?.slice(0, 10) ?? "unknown-date";
182
- const shortId = sessionId.slice(0, 8);
183
- return `${canonical}-session-${datePart}-${shortId}`;
116
+ return `${harness}-session-${isoOrUndefined(startedAtMs)?.slice(0, 10) ?? "unknown-date"}-${sessionId.slice(0, 8)}`;
184
117
  }
185
- /**
186
- * Assemble the full session asset (frontmatter + `## Summary` / `## Key topics`).
187
- * Pure — no IO. Returns the serialized markdown string.
188
- */
118
+ /** The session asset: frontmatter plus `## Summary` and `## Key topics`. */
189
119
  export function buildSessionAssetContent(data, summary) {
190
120
  const ref = data.ref;
191
121
  const harness = ref.harness;
@@ -213,8 +143,7 @@ export function buildSessionAssetContent(data, summary) {
213
143
  .map((t) => `- ${t.trim()}`)
214
144
  .join("\n");
215
145
  const body = `## Summary\n\n${summary.summary.trim()}\n\n## Key topics\n\n${topics || "- (none extracted)"}\n`;
216
- // `description` is duplicated into frontmatter so the metadata pass surfaces
217
- // it without re-reading the body — matches how other content types behave.
146
+ // The summary doubles as the description, as for other types.
218
147
  const content = assembleAsset({ ...frontmatter, description: summary.summary.trim() }, body);
219
148
  return { name, frontmatter, content };
220
149
  }
@@ -223,13 +152,7 @@ export function resolveSessionAssetPath(stashDir, harness, sessionId) {
223
152
  const dir = stashDirFor("session") ?? "sessions";
224
153
  return path.join(stashDir, dir, harness, `${sessionId}.md`);
225
154
  }
226
- /**
227
- * Generate (via the injected summarizer) and write a session asset to the stash.
228
- *
229
- * FAIL-OPEN: when the summarizer returns `undefined` (disabled / no LLM /
230
- * error), NOTHING is written and `{ written: false }` is returned. Any write
231
- * error is swallowed by the caller — session indexing must NEVER break extract.
232
- */
155
+ /** Summarize and write a session asset; nothing without a summary. The caller swallows write errors. */
233
156
  export async function writeSessionAsset(data, stashDir, generate) {
234
157
  const summary = await generate(data);
235
158
  if (!summary?.summary || summary.summary.trim().length === 0) {
@@ -241,15 +164,11 @@ export async function writeSessionAsset(data, stashDir, generate) {
241
164
  const filePath = resolveSessionAssetPath(stashDir, harness, sessionId);
242
165
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
243
166
  fs.writeFileSync(filePath, content, "utf8");
244
- // #652: extract's session asset is written outside the proposal queue —
245
- // journal it so the run's auto-sync stages it as one of its own writes.
167
+ // Written outside the proposal queue: journal it so auto-sync commits it (#652).
246
168
  recordWrittenPath(filePath);
247
169
  return {
248
170
  written: true,
249
171
  filePath,
250
- // Canonical 0.9.0 conceptId (`sessions/<harness>/<id>`, D-R3) — the same
251
- // spelling the xrefs / usage-event readers now expect. Historical
252
- // `session:<harness>/<id>` rows persist un-migrated and are tolerated.
253
172
  ref: conceptIdFromTypeName("session", `${harness}/${sessionId}`),
254
173
  logPath: data.ref.filePath,
255
174
  };