akm-cli 0.9.0-rc.0 → 0.9.0-rc.13

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 (598) hide show
  1. package/CHANGELOG.md +1283 -22
  2. package/README.md +62 -37
  3. package/SECURITY.md +46 -31
  4. package/dist/akm +162 -38
  5. package/dist/akm-migrate +44 -0
  6. package/dist/assets/backends/schtasks-template.xml +2 -1
  7. package/dist/assets/hints/cli-hints-full.md +268 -118
  8. package/dist/assets/hints/cli-hints-short.md +87 -24
  9. package/dist/assets/{profiles → improve-strategies}/catchup.json +3 -1
  10. package/dist/assets/{profiles → improve-strategies}/consolidate.json +3 -1
  11. package/dist/assets/{profiles → improve-strategies}/default.json +6 -7
  12. package/dist/assets/improve-strategies/frequent.json +15 -0
  13. package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +4 -2
  14. package/dist/assets/{profiles → improve-strategies}/memory-focus.json +4 -1
  15. package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +5 -5
  16. package/dist/assets/{profiles → improve-strategies}/quick.json +4 -2
  17. package/dist/assets/improve-strategies/reflect-distill.json +30 -0
  18. package/dist/assets/{profiles → improve-strategies}/thorough.json +1 -1
  19. package/dist/assets/prompts/consolidate-system.md +5 -5
  20. package/dist/assets/prompts/extract-session.md +2 -6
  21. package/dist/assets/prompts/memory-infer-user.md +2 -3
  22. package/dist/assets/prompts/reflect-llm-framed-contract.md +11 -0
  23. package/dist/assets/prompts/reflect-llm-schema-contract.md +3 -0
  24. package/dist/assets/prompts/reflect-output-repair.md +3 -0
  25. package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
  26. package/dist/assets/stash-skeleton/README.md +38 -10
  27. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +8 -0
  28. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +8 -0
  29. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +14 -1
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +13 -1
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +9 -1
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +11 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +9 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +9 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +8 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +100 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/domains.md +64 -0
  38. package/dist/assets/stash-skeleton/facts/conventions/organization.md +136 -0
  39. package/dist/assets/tasks/core/extract.yml +3 -2
  40. package/dist/assets/tasks/core/improve.yml +2 -1
  41. package/dist/assets/tasks/core/index-refresh.yml +1 -0
  42. package/dist/assets/tasks/core/sync.yml +1 -0
  43. package/dist/assets/tasks/core/version-check.yml +2 -1
  44. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
  45. package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
  46. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
  47. package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
  48. package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
  49. package/dist/assets/templates/html/health.html +5 -4
  50. package/dist/assets/workflows/workflow-template.md +31 -15
  51. package/dist/cli/invocation.js +279 -0
  52. package/dist/cli/parse-args.js +5 -90
  53. package/dist/cli/retired-commands.js +78 -0
  54. package/dist/cli/shared.js +158 -48
  55. package/dist/cli-node.mjs +2 -1
  56. package/dist/cli.js +747 -293
  57. package/dist/commands/agent/agent-dispatch.js +19 -18
  58. package/dist/commands/agent/agent-support.js +0 -24
  59. package/dist/commands/agent/contribute-cli.js +43 -97
  60. package/dist/commands/completions.js +80 -23
  61. package/dist/commands/config-cli.js +44 -281
  62. package/dist/commands/env/env-binding.js +99 -0
  63. package/dist/commands/env/env-cli.js +84 -224
  64. package/dist/commands/env/env.js +12 -163
  65. package/dist/commands/env/marker-path.js +6 -0
  66. package/dist/commands/env/secret-cli.js +45 -61
  67. package/dist/commands/env/secret.js +32 -62
  68. package/dist/commands/feedback-cli.js +179 -85
  69. package/dist/commands/health/accept-rate.js +58 -0
  70. package/dist/commands/health/advisories.js +7 -8
  71. package/dist/commands/health/checks.js +279 -94
  72. package/dist/commands/health/html-report.js +197 -578
  73. package/dist/commands/health/improve-metrics.js +277 -246
  74. package/dist/commands/health/llm-usage.js +19 -19
  75. package/dist/commands/health/md-report.js +16 -7
  76. package/dist/commands/health/metrics.js +67 -32
  77. package/dist/commands/health/renderers.js +47 -0
  78. package/dist/commands/health/report-view-model.js +508 -0
  79. package/dist/commands/health/stash-exposure.js +1 -1
  80. package/dist/commands/health/surfaces.js +16 -56
  81. package/dist/commands/health/task-runs.js +3 -67
  82. package/dist/{migrate-storage-node.mjs → commands/health/types-checks.js} +1 -5
  83. package/dist/commands/health/types-improve.js +29 -0
  84. package/dist/{output/text/save.js → commands/health/types-metrics.js} +1 -2
  85. package/dist/commands/health/types-result.js +7 -0
  86. package/dist/commands/health/types-runs.js +4 -0
  87. package/dist/commands/health/types-session-log.js +4 -0
  88. package/dist/commands/health/types-windows.js +4 -0
  89. package/dist/commands/health/types.js +26 -21
  90. package/dist/commands/health/windows.js +2 -3
  91. package/dist/commands/health.js +296 -167
  92. package/dist/commands/improve/anti-collapse.js +5 -5
  93. package/dist/commands/improve/autonomy-gate.js +68 -0
  94. package/dist/commands/improve/collapse-detector.js +65 -52
  95. package/dist/commands/improve/consolidate/chunking.js +9 -7
  96. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  97. package/dist/commands/improve/consolidate/merge.js +4 -0
  98. package/dist/commands/improve/consolidate.js +454 -1354
  99. package/dist/commands/improve/content-hash.js +39 -0
  100. package/dist/commands/improve/distill/content-repair.js +4 -10
  101. package/dist/commands/improve/distill/promote-memory.js +89 -64
  102. package/dist/commands/improve/distill/quality-gate.js +118 -42
  103. package/dist/commands/improve/distill-guards.js +1 -1
  104. package/dist/commands/improve/distill-promotion-policy.js +33 -888
  105. package/dist/commands/improve/distill.js +607 -363
  106. package/dist/commands/improve/eligibility.js +165 -79
  107. package/dist/commands/improve/extract-cli.js +35 -126
  108. package/dist/commands/improve/extract-prompt.js +6 -35
  109. package/dist/commands/improve/extract.js +640 -391
  110. package/dist/commands/improve/feedback-valence.js +2 -12
  111. package/dist/commands/improve/improve-cli.js +134 -135
  112. package/dist/commands/improve/improve-result-file.js +30 -50
  113. package/dist/commands/improve/improve-run-types.js +4 -0
  114. package/dist/commands/improve/improve-strategies.js +135 -0
  115. package/dist/commands/improve/improve.js +904 -701
  116. package/dist/commands/improve/locks.js +64 -111
  117. package/dist/commands/improve/loop-stages.js +1110 -923
  118. package/dist/commands/improve/memory/derived-ref.js +124 -0
  119. package/dist/commands/improve/memory/memory-belief.js +79 -7
  120. package/dist/commands/improve/memory/memory-contradiction-detect.js +49 -52
  121. package/dist/commands/improve/memory/memory-improve.js +25 -37
  122. package/dist/commands/improve/outcome-loop.js +25 -88
  123. package/dist/commands/improve/preparation.js +1034 -813
  124. package/dist/commands/improve/proactive-maintenance.js +34 -9
  125. package/dist/commands/improve/proposal-envelope.js +31 -0
  126. package/dist/commands/improve/reflect.js +983 -794
  127. package/dist/commands/improve/run-context.js +119 -0
  128. package/dist/commands/improve/salience.js +24 -127
  129. package/dist/commands/improve/session-asset.js +7 -3
  130. package/dist/commands/improve/shared.js +14 -34
  131. package/dist/commands/improve/source-identity.js +28 -0
  132. package/dist/commands/improve/triage.js +20 -17
  133. package/dist/commands/lint/base-linter.js +340 -313
  134. package/dist/commands/lint/env-key-rules.js +31 -47
  135. package/dist/commands/lint/index.js +185 -30
  136. package/dist/commands/{events.js → log.js} +28 -38
  137. package/dist/commands/migrate-cli.js +54 -0
  138. package/dist/commands/migration-tool.js +55 -0
  139. package/dist/commands/observability-cli.js +70 -208
  140. package/dist/commands/proposal/diff-format.js +50 -0
  141. package/dist/commands/proposal/drain-policies.js +0 -6
  142. package/dist/commands/proposal/drain.js +91 -40
  143. package/dist/commands/proposal/proposal-cli.js +134 -132
  144. package/dist/commands/proposal/proposal-types.js +56 -0
  145. package/dist/commands/proposal/proposal.js +83 -65
  146. package/dist/commands/proposal/propose-cli.js +88 -0
  147. package/dist/commands/proposal/propose.js +105 -88
  148. package/dist/commands/proposal/repository.js +1303 -278
  149. package/dist/commands/proposal/validators/proposal-quality-validators.js +16 -6
  150. package/dist/commands/proposal/validators/proposal-validators.js +61 -12
  151. package/dist/commands/proposal/validators/proposals.js +6 -8
  152. package/dist/commands/read/curate.js +78 -73
  153. package/dist/commands/read/knowledge.js +510 -13
  154. package/dist/commands/read/registry-search.js +2 -2
  155. package/dist/commands/read/remember-cli.js +84 -15
  156. package/dist/commands/read/search-cli.js +203 -96
  157. package/dist/commands/read/search.js +126 -94
  158. package/dist/commands/read/show.js +226 -250
  159. package/dist/commands/registry-cli.js +34 -60
  160. package/dist/commands/remember.js +18 -57
  161. package/dist/commands/sources/add-cli.js +104 -49
  162. package/dist/commands/sources/bundle-cli.js +166 -0
  163. package/dist/commands/sources/bundle-config-ops.js +63 -0
  164. package/dist/commands/sources/info.js +27 -15
  165. package/dist/commands/sources/init.js +30 -40
  166. package/dist/commands/sources/installed-stashes.js +469 -172
  167. package/dist/commands/sources/migration-help.js +7 -4
  168. package/dist/commands/sources/schema-repair.js +10 -9
  169. package/dist/commands/sources/self-update.js +182 -121
  170. package/dist/commands/sources/source-add.js +169 -178
  171. package/dist/commands/sources/source-clone.js +144 -41
  172. package/dist/commands/sources/source-manage.js +94 -59
  173. package/dist/commands/sources/sources-cli.js +64 -205
  174. package/dist/commands/sources/stash-cli.js +91 -54
  175. package/dist/commands/sources/stash-skeleton.js +1 -1
  176. package/dist/commands/tasks/tasks-cli.js +106 -104
  177. package/dist/commands/tasks/tasks.js +445 -262
  178. package/dist/commands/workflow-cli.js +232 -121
  179. package/dist/core/action-contributors.js +1 -1
  180. package/dist/core/activation-policy.js +49 -0
  181. package/dist/core/adapter/adapters/agent-skills-adapter.js +181 -0
  182. package/dist/core/adapter/adapters/akm-adapter.js +528 -0
  183. package/dist/core/adapter/adapters/akm-lint.js +392 -0
  184. package/dist/core/adapter/adapters/akm-metadata.js +387 -0
  185. package/dist/core/adapter/adapters/akm-task-adapter.js +149 -0
  186. package/dist/core/adapter/adapters/akm-workflow-adapter.js +180 -0
  187. package/dist/core/adapter/adapters/claude-adapter.js +61 -0
  188. package/dist/core/adapter/adapters/dotenv-adapter.js +187 -0
  189. package/dist/core/adapter/adapters/generic-files-adapter.js +119 -0
  190. package/dist/core/adapter/adapters/index.js +80 -0
  191. package/dist/core/adapter/adapters/llm-wiki-adapter.js +419 -0
  192. package/dist/core/adapter/adapters/okf-adapter.js +391 -0
  193. package/dist/core/adapter/adapters/opencode-adapter.js +68 -0
  194. package/dist/core/adapter/adapters/shared.js +286 -0
  195. package/dist/core/adapter/adapters/tool-dir-shared.js +217 -0
  196. package/dist/core/adapter/adapters/website-snapshot-adapter.js +155 -0
  197. package/dist/core/adapter/bundle-adapter.js +4 -0
  198. package/dist/core/adapter/detect-adapter.js +17 -0
  199. package/dist/core/adapter/recognize-match.js +44 -0
  200. package/dist/core/adapter/registry.js +56 -0
  201. package/dist/core/adapter/types.js +4 -0
  202. package/dist/core/asset/akm-markdown.js +30 -0
  203. package/dist/core/asset/asset-placement.js +243 -0
  204. package/dist/core/asset/asset-ref.js +110 -79
  205. package/dist/core/asset/asset-serialize.js +20 -0
  206. package/dist/core/asset/frontmatter.js +28 -12
  207. package/dist/core/asset/markdown.js +40 -51
  208. package/dist/core/asset/resolve-ref.js +274 -0
  209. package/dist/core/asset/stash-meta.js +2 -2
  210. package/dist/core/bundle-id.js +51 -0
  211. package/dist/core/common.js +281 -86
  212. package/dist/core/config/config-io.js +42 -128
  213. package/dist/core/config/config-schema.js +233 -834
  214. package/dist/core/config/config-sources.js +162 -39
  215. package/dist/core/config/config-types.js +16 -11
  216. package/dist/core/config/config-version.js +29 -0
  217. package/dist/core/config/config-walker.js +126 -37
  218. package/dist/core/config/config.js +154 -331
  219. package/dist/core/config/deep-merge.js +41 -0
  220. package/dist/core/config/engine-semantics.js +28 -0
  221. package/dist/core/config/experimental.js +21 -0
  222. package/dist/core/config/schema/embedding.js +38 -0
  223. package/dist/core/config/schema/engines.js +116 -0
  224. package/dist/core/config/schema/experimental.js +47 -0
  225. package/dist/core/config/schema/feedback.js +31 -0
  226. package/dist/core/config/schema/improve-processes.js +389 -0
  227. package/dist/core/config/schema/improve.js +94 -0
  228. package/dist/core/config/schema/index-config.js +176 -0
  229. package/dist/core/config/schema/output.js +18 -0
  230. package/dist/core/config/schema/primitives.js +94 -0
  231. package/dist/core/config/schema/search.js +30 -0
  232. package/dist/core/config/schema/setup.js +18 -0
  233. package/dist/core/config/schema/sources-bundles.js +169 -0
  234. package/dist/core/config/schema/workflow.js +29 -0
  235. package/dist/core/env-secret-ref.js +155 -20
  236. package/dist/core/errors.js +17 -15
  237. package/dist/core/events-types.js +4 -0
  238. package/dist/core/events.js +46 -128
  239. package/dist/core/extra-params.js +62 -0
  240. package/dist/core/file-change.js +17 -0
  241. package/dist/core/file-lock.js +202 -57
  242. package/dist/core/fs-txn.js +392 -0
  243. package/dist/core/git-message.js +59 -0
  244. package/dist/core/improve-result.js +167 -0
  245. package/dist/core/json-schema.js +142 -0
  246. package/dist/core/lesson-lint.js +1 -17
  247. package/dist/core/logs-db.js +1 -1
  248. package/dist/core/maintenance-barrier.js +135 -0
  249. package/dist/core/migration-operation.js +44 -0
  250. package/dist/core/mutation-target.js +78 -0
  251. package/dist/core/paths.js +22 -25
  252. package/dist/core/platform.js +10 -0
  253. package/dist/core/recognition-util.js +128 -0
  254. package/dist/core/redaction.js +392 -0
  255. package/dist/core/standards/resolve-standards-context.js +36 -65
  256. package/dist/core/standards/resolve-stash-standards.js +2 -2
  257. package/dist/core/standards/resolve-type-conventions.js +5 -5
  258. package/dist/core/state/migrations.js +242 -11
  259. package/dist/core/state-db.js +98 -10
  260. package/dist/core/structured.js +1 -1
  261. package/dist/core/subprocess.js +303 -0
  262. package/dist/core/text-truncation.js +9 -5
  263. package/dist/core/time.js +20 -0
  264. package/dist/core/type-presentation.js +130 -0
  265. package/dist/core/warn.js +0 -3
  266. package/dist/core/write-source.js +834 -118
  267. package/dist/indexer/bundle-identity-guard.js +92 -0
  268. package/dist/indexer/db/graph-db.js +1 -25
  269. package/dist/indexer/db/llm-cache.js +1 -1
  270. package/dist/indexer/ensure-index.js +30 -9
  271. package/dist/indexer/graph/graph-boost.js +9 -30
  272. package/dist/indexer/graph/graph-extraction.js +41 -27
  273. package/dist/indexer/graph/graph-types.js +4 -0
  274. package/dist/indexer/index-writer-lock.js +93 -49
  275. package/dist/indexer/index-written-assets.js +100 -53
  276. package/dist/indexer/indexer.js +746 -329
  277. package/dist/indexer/init.js +18 -25
  278. package/dist/indexer/installations.js +142 -0
  279. package/dist/indexer/passes/dir-staleness.js +18 -10
  280. package/dist/indexer/passes/memory-inference.js +25 -15
  281. package/dist/indexer/passes/metadata.js +412 -243
  282. package/dist/indexer/scan/doc-to-entry.js +160 -0
  283. package/dist/indexer/scan/drain-dir.js +134 -0
  284. package/dist/indexer/search/db-search.js +292 -108
  285. package/dist/indexer/search/fts-query.js +64 -0
  286. package/dist/indexer/search/ranking-contributors.js +145 -25
  287. package/dist/indexer/search/ranking-types.js +4 -0
  288. package/dist/indexer/search/ranking.js +28 -71
  289. package/dist/indexer/search/search-attribution.js +67 -0
  290. package/dist/indexer/search/search-fields.js +18 -3
  291. package/dist/indexer/search/search-hit-enrichers.js +30 -40
  292. package/dist/indexer/search/search-source.js +157 -111
  293. package/dist/indexer/search/semantic-status.js +4 -1
  294. package/dist/indexer/usage/usage-events.js +10 -30
  295. package/dist/indexer/walk/file-context.js +3 -45
  296. package/dist/indexer/walk/matchers.js +42 -34
  297. package/dist/indexer/walk/path-resolver.js +11 -5
  298. package/dist/indexer/walk/walker.js +42 -14
  299. package/dist/integrations/agent/builder-shared.js +7 -0
  300. package/dist/integrations/agent/builders.js +5 -56
  301. package/dist/integrations/agent/config.js +3 -143
  302. package/dist/integrations/agent/detect.js +17 -2
  303. package/dist/integrations/agent/engine-resolution.js +231 -0
  304. package/dist/integrations/agent/index.js +1 -2
  305. package/dist/integrations/agent/model-aliases.js +16 -2
  306. package/dist/integrations/agent/profiles.js +36 -62
  307. package/dist/integrations/agent/prompts.js +46 -18
  308. package/dist/integrations/agent/runner-dispatch.js +93 -4
  309. package/dist/integrations/agent/runner.js +76 -208
  310. package/dist/integrations/agent/spawn.js +88 -196
  311. package/dist/integrations/harnesses/aider/agent-builder.js +114 -0
  312. package/dist/integrations/harnesses/aider/index.js +48 -0
  313. package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
  314. package/dist/integrations/harnesses/amazonq/agent-builder.js +147 -0
  315. package/dist/integrations/harnesses/amazonq/index.js +45 -0
  316. package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
  317. package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
  318. package/dist/integrations/harnesses/claude/config-import.js +1 -3
  319. package/dist/integrations/harnesses/claude/index.js +24 -35
  320. package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
  321. package/dist/integrations/harnesses/claude/session-log.js +27 -75
  322. package/dist/integrations/harnesses/codex/agent-builder.js +138 -0
  323. package/dist/integrations/harnesses/codex/index.js +52 -0
  324. package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
  325. package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
  326. package/dist/integrations/harnesses/copilot/index.js +48 -0
  327. package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
  328. package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
  329. package/dist/integrations/harnesses/gemini/index.js +48 -0
  330. package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
  331. package/dist/integrations/harnesses/ids.js +24 -0
  332. package/dist/integrations/harnesses/index.js +54 -34
  333. package/dist/integrations/harnesses/opencode/agent-builder.js +23 -5
  334. package/dist/integrations/harnesses/opencode/config-import.js +1 -3
  335. package/dist/integrations/harnesses/opencode/index.js +14 -32
  336. package/dist/integrations/harnesses/opencode/session-log.js +67 -125
  337. package/dist/integrations/harnesses/opencode-sdk/harness.js +51 -0
  338. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
  339. package/dist/integrations/harnesses/openhands/agent-builder.js +128 -0
  340. package/dist/integrations/harnesses/openhands/index.js +48 -0
  341. package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
  342. package/dist/integrations/harnesses/pi/agent-builder.js +97 -0
  343. package/dist/integrations/harnesses/pi/index.js +45 -0
  344. package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
  345. package/dist/integrations/harnesses/shared.js +17 -0
  346. package/dist/integrations/harnesses/types.js +43 -32
  347. package/dist/integrations/lockfile.js +211 -24
  348. package/dist/integrations/session-logs/index.js +36 -39
  349. package/dist/integrations/session-logs/provider-base.js +113 -0
  350. package/dist/llm/client.js +182 -110
  351. package/dist/llm/embedders/deterministic.js +2 -2
  352. package/dist/llm/embedders/remote.js +21 -9
  353. package/dist/llm/feature-gate.js +17 -57
  354. package/dist/llm/graph-extract.js +12 -13
  355. package/dist/llm/index-passes.js +8 -42
  356. package/dist/llm/memory-infer.js +144 -1
  357. package/dist/llm/metadata-enhance.js +45 -30
  358. package/dist/llm/structured-call.js +16 -8
  359. package/dist/llm/usage-persist.js +30 -5
  360. package/dist/llm/usage-telemetry.js +59 -6
  361. package/dist/output/cli-hints.js +1 -2
  362. package/dist/output/command-registry.js +27 -0
  363. package/dist/output/context.js +22 -7
  364. package/dist/output/format-exempt.js +80 -0
  365. package/dist/output/generic-render.js +251 -0
  366. package/dist/output/html-render.js +11 -16
  367. package/dist/output/render-registry.js +57 -0
  368. package/dist/output/renderers.js +14 -279
  369. package/dist/output/shapes/curate.js +10 -1
  370. package/dist/output/shapes/events.js +12 -7
  371. package/dist/output/shapes/helpers.js +58 -84
  372. package/dist/output/shapes/passthrough.js +11 -39
  373. package/dist/output/shapes/proposal/producer.js +15 -7
  374. package/dist/output/shapes/registry.js +12 -6
  375. package/dist/output/shapes.js +0 -9
  376. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  377. package/dist/output/text/bundle-show.js +7 -0
  378. package/dist/output/text/command-format.js +562 -0
  379. package/dist/output/text/env.js +1 -3
  380. package/dist/output/text/events.js +8 -7
  381. package/dist/output/text/helpers.js +15 -1164
  382. package/dist/output/text/proposal/producer.js +4 -2
  383. package/dist/output/text/proposal-format.js +202 -0
  384. package/dist/output/text/registry-commands.js +1 -2
  385. package/dist/output/text/registry.js +12 -6
  386. package/dist/output/text/show-directives.js +117 -0
  387. package/dist/output/text/show-format.js +103 -0
  388. package/dist/output/text/sync.js +5 -0
  389. package/dist/output/text/workflow-format.js +332 -0
  390. package/dist/output/text/workflow.js +3 -2
  391. package/dist/output/text.js +10 -19
  392. package/dist/registry/factory.js +4 -6
  393. package/dist/registry/origin-resolve.js +16 -27
  394. package/dist/registry/providers/skills-sh.js +3 -3
  395. package/dist/registry/providers/static-index.js +15 -25
  396. package/dist/registry/resolve.js +43 -94
  397. package/dist/registry/semver.js +43 -0
  398. package/dist/runtime.js +81 -12
  399. package/dist/scripts/akm-migrate.js +35529 -0
  400. package/dist/setup/detect.js +5 -7
  401. package/dist/setup/detected-engines.js +136 -0
  402. package/dist/setup/engine-config.js +100 -0
  403. package/dist/setup/registry-stash-loader.js +3 -3
  404. package/dist/setup/semantic-assets.js +12 -9
  405. package/dist/setup/setup.js +444 -208
  406. package/dist/setup/steps/connection-shared.js +120 -0
  407. package/dist/setup/steps/connection.js +108 -305
  408. package/dist/setup/steps/platforms.js +13 -12
  409. package/dist/setup/steps/semantic.js +15 -3
  410. package/dist/setup/steps/sources.js +21 -15
  411. package/dist/setup/steps/stashdir.js +6 -4
  412. package/dist/setup/steps/tasks.js +236 -119
  413. package/dist/setup/steps.js +3 -2
  414. package/dist/sources/freshness.js +39 -0
  415. package/dist/sources/provider-factory.js +11 -17
  416. package/dist/sources/providers/filesystem.js +2 -3
  417. package/dist/sources/providers/git-install.js +278 -34
  418. package/dist/sources/providers/git-provider.js +54 -56
  419. package/dist/sources/providers/git-stash.js +420 -91
  420. package/dist/sources/providers/git.js +2 -2
  421. package/dist/sources/providers/npm.js +16 -19
  422. package/dist/sources/providers/provider-utils.js +47 -22
  423. package/dist/sources/providers/sync-from-ref.js +3 -9
  424. package/dist/sources/providers/website.js +2 -2
  425. package/dist/sources/resolve.js +11 -10
  426. package/dist/sources/snapshot-fetchers/types.js +4 -0
  427. package/dist/sources/{website-ingest.js → snapshot-fetchers/website-ingest.js} +110 -41
  428. package/dist/storage/database.js +60 -4
  429. package/dist/storage/engines/sqlite-migrations.js +156 -5
  430. package/dist/storage/locations.js +1 -2
  431. package/dist/storage/repositories/canaries-repository.js +1 -1
  432. package/dist/storage/repositories/events-repository.js +51 -11
  433. package/dist/storage/repositories/improve-runs-repository.js +6 -32
  434. package/dist/storage/repositories/index-connection.js +79 -0
  435. package/dist/storage/repositories/index-db.js +4 -3
  436. package/dist/storage/repositories/index-entries-repository.js +863 -0
  437. package/dist/{indexer/db/entry-mapper.js → storage/repositories/index-entry-mapper.js} +19 -2
  438. package/dist/storage/repositories/index-entry-types.js +4 -0
  439. package/dist/storage/repositories/index-fts-repository.js +167 -0
  440. package/dist/storage/repositories/index-llm-cache-repository.js +108 -0
  441. package/dist/storage/repositories/index-meta-repository.js +49 -0
  442. package/dist/{indexer/db/schema.js → storage/repositories/index-schema.js} +226 -100
  443. package/dist/storage/repositories/index-sql.js +12 -0
  444. package/dist/storage/repositories/index-utility-repository.js +356 -0
  445. package/dist/storage/repositories/index-vec-repository.js +250 -0
  446. package/dist/storage/repositories/outcome-repository.js +119 -0
  447. package/dist/storage/repositories/proposals-repository.js +317 -75
  448. package/dist/storage/repositories/registry-cache.js +1 -1
  449. package/dist/storage/repositories/salience-repository.js +172 -0
  450. package/dist/storage/repositories/task-history-repository.js +110 -3
  451. package/dist/storage/repositories/workflow-runs-repository.js +240 -19
  452. package/dist/tasks/backends/cron.js +169 -46
  453. package/dist/tasks/backends/exec-utils.js +76 -3
  454. package/dist/tasks/backends/index.js +6 -9
  455. package/dist/tasks/backends/launchd.js +292 -55
  456. package/dist/tasks/backends/schtasks.js +557 -70
  457. package/dist/tasks/backends/types.js +4 -0
  458. package/dist/tasks/command-executable.js +93 -0
  459. package/dist/tasks/embedded.js +56 -38
  460. package/dist/tasks/parser.js +156 -64
  461. package/dist/tasks/resolve-akm-bin.js +144 -51
  462. package/dist/tasks/runner.js +377 -209
  463. package/dist/tasks/schedule.js +108 -19
  464. package/dist/tasks/scheduler-invocation.js +296 -0
  465. package/dist/tasks/schema.js +1 -1
  466. package/dist/tasks/task-id.js +35 -0
  467. package/dist/tasks/validator.js +30 -16
  468. package/dist/text-import-hook.mjs +1 -1
  469. package/dist/workflows/authoring/authoring.js +104 -43
  470. package/dist/workflows/authoring/scope-key.js +1 -1
  471. package/dist/workflows/cli.js +0 -16
  472. package/dist/workflows/concurrency-policy.js +15 -0
  473. package/dist/workflows/exec/brief.js +450 -0
  474. package/dist/workflows/exec/frozen-judge.js +47 -0
  475. package/dist/workflows/exec/native-executor.js +1038 -0
  476. package/dist/workflows/exec/param-secrets.js +115 -0
  477. package/dist/workflows/exec/report.js +1460 -0
  478. package/dist/workflows/exec/run-workflow.js +602 -0
  479. package/dist/workflows/exec/scheduler.js +71 -0
  480. package/dist/workflows/exec/step-work.js +1190 -0
  481. package/dist/workflows/exec/unit-writer.js +23 -0
  482. package/dist/workflows/exec/workflow-engine-gate.js +67 -0
  483. package/dist/workflows/exec/worktree.js +171 -0
  484. package/dist/workflows/ir/compile.js +246 -0
  485. package/dist/workflows/ir/freeze.js +233 -0
  486. package/dist/workflows/ir/params.js +54 -0
  487. package/dist/workflows/ir/plan-hash.js +68 -0
  488. package/dist/workflows/ir/schema.js +540 -0
  489. package/dist/workflows/parser.js +878 -304
  490. package/dist/workflows/program/expressions.js +181 -0
  491. package/dist/workflows/program/schema.js +51 -0
  492. package/dist/workflows/renderer.js +100 -45
  493. package/dist/workflows/resource-limits.js +22 -0
  494. package/dist/workflows/runtime/agent-identity.js +59 -14
  495. package/dist/workflows/runtime/checkin.js +1 -1
  496. package/dist/workflows/runtime/plan-classifier.js +131 -0
  497. package/dist/workflows/runtime/runs.js +376 -119
  498. package/dist/workflows/runtime/unit-checkin.js +45 -0
  499. package/dist/workflows/runtime/unit-phases.js +20 -0
  500. package/dist/workflows/runtime/workflow-asset-loader.js +241 -40
  501. package/dist/workflows/schema.js +1 -11
  502. package/dist/workflows/validate-summary.js +2 -3
  503. package/dist/workflows/validator.js +52 -30
  504. package/docs/README.md +42 -78
  505. package/docs/migration/README.md +8 -0
  506. package/docs/migration/release-notes/0.6.0.md +1 -1
  507. package/docs/migration/release-notes/0.7.0.md +9 -8
  508. package/docs/migration/release-notes/0.9.0.md +158 -14
  509. package/docs/migration/v0.7-to-v0.8.md +46 -47
  510. package/docs/migration/v0.8-to-v0.9.md +844 -0
  511. package/docs/reference/README.md +12 -0
  512. package/docs/reference/data-and-telemetry.md +333 -0
  513. package/package.json +21 -17
  514. package/schemas/akm-asset-envelope.json +93 -0
  515. package/schemas/akm-config.json +4636 -0
  516. package/schemas/akm-task.json +87 -0
  517. package/schemas/akm-workflow.json +373 -0
  518. package/dist/akm-migrate-storage +0 -38
  519. package/dist/assets/help/help-accept.md +0 -12
  520. package/dist/assets/help/help-improve.md +0 -84
  521. package/dist/assets/help/help-proposals.md +0 -17
  522. package/dist/assets/help/help-propose.md +0 -17
  523. package/dist/assets/help/help-reject.md +0 -11
  524. package/dist/assets/profiles/frequent.json +0 -13
  525. package/dist/assets/profiles/recombine-only.json +0 -21
  526. package/dist/assets/profiles/reflect-distill.json +0 -30
  527. package/dist/assets/profiles/synthesize.json +0 -15
  528. package/dist/assets/prompts/procedural-system.md +0 -44
  529. package/dist/assets/prompts/recombine-system.md +0 -40
  530. package/dist/assets/prompts/staleness-detect-system.md +0 -6
  531. package/dist/assets/tasks/core/backup.yml +0 -4
  532. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  533. package/dist/assets/templates/html/default.html +0 -78
  534. package/dist/assets/templates/html/vendor/echarts.min.js +0 -45
  535. package/dist/assets/wiki/index-template.md +0 -12
  536. package/dist/assets/wiki/ingest-workflow-template.md +0 -83
  537. package/dist/assets/wiki/log-template.md +0 -8
  538. package/dist/assets/wiki/schema-template.md +0 -61
  539. package/dist/cli/config-migrate.js +0 -150
  540. package/dist/cli/config-validate.js +0 -39
  541. package/dist/commands/graph/graph-cli.js +0 -124
  542. package/dist/commands/graph/graph.js +0 -487
  543. package/dist/commands/improve/calibration.js +0 -161
  544. package/dist/commands/improve/dedup.js +0 -482
  545. package/dist/commands/improve/extract-watch.js +0 -140
  546. package/dist/commands/improve/hot-probation.js +0 -45
  547. package/dist/commands/improve/improve-auto-accept.js +0 -276
  548. package/dist/commands/improve/improve-profiles.js +0 -168
  549. package/dist/commands/improve/procedural.js +0 -398
  550. package/dist/commands/improve/recombine.js +0 -818
  551. package/dist/commands/improve/schema-similarity-gate.js +0 -168
  552. package/dist/commands/lint/agent-linter.js +0 -44
  553. package/dist/commands/lint/command-linter.js +0 -44
  554. package/dist/commands/lint/default-linter.js +0 -16
  555. package/dist/commands/lint/fact-linter.js +0 -39
  556. package/dist/commands/lint/knowledge-linter.js +0 -16
  557. package/dist/commands/lint/memory-linter.js +0 -61
  558. package/dist/commands/lint/registry.js +0 -41
  559. package/dist/commands/lint/skill-linter.js +0 -45
  560. package/dist/commands/lint/task-linter.js +0 -50
  561. package/dist/commands/lint/workflow-linter.js +0 -81
  562. package/dist/commands/proposal/legacy-import.js +0 -115
  563. package/dist/commands/sources/history.js +0 -196
  564. package/dist/commands/tasks/default-tasks.js +0 -186
  565. package/dist/commands/wiki-cli.js +0 -292
  566. package/dist/core/asset/asset-registry.js +0 -76
  567. package/dist/core/asset/asset-spec.js +0 -259
  568. package/dist/core/config/config-migration.js +0 -602
  569. package/dist/core/deep-merge.js +0 -38
  570. package/dist/core/eval/rank-metrics.js +0 -113
  571. package/dist/core/ripgrep/install.js +0 -163
  572. package/dist/core/ripgrep/resolve.js +0 -81
  573. package/dist/indexer/db/db.js +0 -1413
  574. package/dist/indexer/manifest.js +0 -170
  575. package/dist/indexer/passes/metadata-contributors.js +0 -31
  576. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -94
  577. package/dist/integrations/harnesses/opencode-sdk/index.js +0 -49
  578. package/dist/llm/call-ai.js +0 -62
  579. package/dist/llm/memory-infer-impl.js +0 -138
  580. package/dist/output/shapes/distill.js +0 -14
  581. package/dist/output/shapes/history.js +0 -11
  582. package/dist/output/text/distill.js +0 -6
  583. package/dist/output/text/enable-disable.js +0 -8
  584. package/dist/output/text/history.js +0 -6
  585. package/dist/output/text/wiki.js +0 -16
  586. package/dist/registry/build-index.js +0 -386
  587. package/dist/scripts/migrate-storage.js +0 -19108
  588. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +0 -9411
  589. package/dist/scripts/migrations/v16-to-v17.js +0 -141
  590. package/dist/setup/legacy-config.js +0 -106
  591. package/dist/storage/repositories/consolidation-repository.js +0 -38
  592. package/dist/storage/repositories/recombine-repository.js +0 -213
  593. package/dist/wiki/wiki-templates.js +0 -15
  594. package/dist/wiki/wiki.js +0 -1012
  595. package/dist/workflows/db.js +0 -215
  596. package/docs/data-and-telemetry.md +0 -226
  597. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/registry.js +0 -0
  598. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/youtube.js +0 -0
@@ -3,15 +3,12 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
- import { deriveCanonicalAssetName, deriveCanonicalAssetNameFromStashRoot, isRelevantAssetFile, } from "../../core/asset/asset-spec.js";
6
+ import { parseBundleRef } from "../../core/asset/asset-ref.js";
7
7
  import { parseFrontmatter } from "../../core/asset/frontmatter.js";
8
- import { asNonEmptyString, isAssetType, writeFileAtomic } from "../../core/common.js";
8
+ import { asNonEmptyString } from "../../core/common.js";
9
+ import { loadUserConfig } from "../../core/config/config.js";
9
10
  import { isVerbose, warn } from "../../core/warn.js";
10
- import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "../walk/file-context.js";
11
- import { applyMetadataContributors } from "./metadata-contributors.js";
12
11
  export const SCOPE_KEYS = ["user", "agent", "run", "channel"];
13
- // ── Load / Write ────────────────────────────────────────────────────────────
14
- const STASH_FILENAME = ".stash.json";
15
12
  // ── Quality semantics (v1 spec §4.2) ────────────────────────────────────────
16
13
  /**
17
14
  * Well-known quality values. `generated`, `curated`, and `enriched` are included in
@@ -50,49 +47,12 @@ export function _resetUnknownQualityWarnings() {
50
47
  export function isProposedQuality(quality) {
51
48
  return quality === "proposed";
52
49
  }
53
- export function stashFilePath(dirPath) {
54
- return path.join(dirPath, STASH_FILENAME);
55
- }
56
- export function loadStashFile(dirPath, options) {
57
- const filePath = stashFilePath(dirPath);
58
- if (!fs.existsSync(filePath))
59
- return null;
60
- try {
61
- const raw = JSON.parse(fs.readFileSync(filePath, "utf8"));
62
- if (!raw || !Array.isArray(raw.entries))
63
- return null;
64
- const entries = [];
65
- for (const e of raw.entries) {
66
- const validated = validateStashEntry(e);
67
- if (validated) {
68
- if (options?.requireFilename && !validated.filename)
69
- continue;
70
- entries.push(validated);
71
- }
72
- else {
73
- const name = typeof e === "object" && e !== null && typeof e.name === "string"
74
- ? e.name
75
- : "(unknown)";
76
- warn(`Warning: Skipping invalid entry "${name}" in ${filePath}`);
77
- }
78
- }
79
- return entries.length > 0 ? { entries } : null;
80
- }
81
- catch {
82
- return null;
83
- }
84
- }
85
- export function writeStashFile(dirPath, stash) {
86
- const filePath = stashFilePath(dirPath);
87
- writeFileAtomic(filePath, `${JSON.stringify(stash, null, 2)}\n`);
88
- }
89
50
  /**
90
- * Validate and normalize a raw object into a `StashEntry`.
51
+ * Validate and normalize a raw object into a `IndexDocument`.
91
52
  *
92
- * **Ordering dependency:** Uses `isAssetType()` to check `entry.type`, which
93
- * only recognizes custom types registered via `registerAssetType()`. If this
94
- * function is called before custom types are registered, those entries will be
95
- * rejected as invalid.
53
+ * Open type token: `entry.type` accepts any non-empty string. Type ownership
54
+ * and capability decisions belong to the adapter; this format-neutral
55
+ * projection must not reject a value merely because AKM does not own it.
96
56
  */
97
57
  export function validateStashEntry(entry) {
98
58
  if (typeof entry !== "object" || entry === null)
@@ -100,7 +60,7 @@ export function validateStashEntry(entry) {
100
60
  const e = entry;
101
61
  if (typeof e.name !== "string" || !e.name)
102
62
  return null;
103
- if (typeof e.type !== "string" || !isAssetType(e.type))
63
+ if (typeof e.type !== "string" || !e.type)
104
64
  return null;
105
65
  const result = {
106
66
  name: e.name,
@@ -182,6 +142,11 @@ export function validateStashEntry(entry) {
182
142
  const sources = normalizeNonEmptyStringList(e.sources);
183
143
  if (sources)
184
144
  result.sources = sources;
145
+ // SPEC-6: `category` must survive the projection. Non-string values are
146
+ // dropped, not coerced.
147
+ if (typeof e.category === "string" && e.category.trim().length > 0) {
148
+ result.category = e.category.trim();
149
+ }
185
150
  if (typeof e.beliefState === "string" && e.beliefState.trim().length > 0) {
186
151
  result.beliefState = e.beliefState.trim();
187
152
  }
@@ -197,9 +162,6 @@ export function validateStashEntry(entry) {
197
162
  if (typeof e.generation === "number" && Number.isFinite(e.generation) && e.generation > 0) {
198
163
  result.generation = Math.floor(e.generation);
199
164
  }
200
- const sourceRefs = normalizeNonEmptyStringList(e.sourceRefs);
201
- if (sourceRefs)
202
- result.sourceRefs = sourceRefs;
203
165
  const currentBeliefRefs = normalizeNonEmptyStringList(e.currentBeliefRefs);
204
166
  if (currentBeliefRefs)
205
167
  result.currentBeliefRefs = currentBeliefRefs;
@@ -218,6 +180,11 @@ export function validateStashEntry(entry) {
218
180
  if (typeof e.derivedFrom === "string" && e.derivedFrom.trim().length > 0) {
219
181
  result.derivedFrom = e.derivedFrom.trim();
220
182
  }
183
+ // SPEC-8: `bodyOpening` must survive the projection. The extractor already
184
+ // trimmed and capped it at capture time.
185
+ if (typeof e.bodyOpening === "string" && e.bodyOpening.trim().length > 0) {
186
+ result.bodyOpening = e.bodyOpening;
187
+ }
221
188
  if (typeof e.scope === "object" && e.scope !== null && !Array.isArray(e.scope)) {
222
189
  const scope = normalizeScopeObject(e.scope);
223
190
  if (scope)
@@ -308,6 +275,45 @@ function normalizeIntent(value) {
308
275
  function normalizeStringListOrUndefined(value) {
309
276
  return normalizeNonEmptyStringList(value);
310
277
  }
278
+ /**
279
+ * Normalize a current derived-memory parent ref to its `memories/<name>`
280
+ * conceptId for the `derived_from` column. Returns `undefined` for a non-memory
281
+ * or bare value so the caller can inspect the current `derivedFrom` key.
282
+ */
283
+ function normalizeMemoryBackref(value) {
284
+ if (!value)
285
+ return undefined;
286
+ try {
287
+ const parsed = parseBundleRef(value);
288
+ return parsed.fragment === undefined && parsed.conceptId.startsWith("memories/") ? parsed.conceptId : undefined;
289
+ }
290
+ catch {
291
+ return undefined;
292
+ }
293
+ }
294
+ /**
295
+ * Resolve a derived memory's parent conceptId from its current `source:` backref
296
+ * or, failing that, its bare `derivedFrom: <name>` frontmatter key.
297
+ */
298
+ function derivedFromConceptId(source, derivedFrom) {
299
+ const fromSource = normalizeMemoryBackref(source);
300
+ if (fromSource)
301
+ return fromSource;
302
+ const rawDerivedFrom = derivedFrom?.trim();
303
+ if (!rawDerivedFrom)
304
+ return undefined;
305
+ const qualified = normalizeMemoryBackref(rawDerivedFrom);
306
+ if (qualified)
307
+ return qualified;
308
+ if (rawDerivedFrom.includes(":") || rawDerivedFrom.includes("//"))
309
+ return undefined;
310
+ try {
311
+ return parseBundleRef(`memories/${rawDerivedFrom}`).conceptId;
312
+ }
313
+ catch {
314
+ return undefined;
315
+ }
316
+ }
311
317
  export function applyCuratedFrontmatter(entry, fmData) {
312
318
  const description = asNonEmptyString(fmData.description);
313
319
  if (description) {
@@ -342,6 +348,12 @@ export function applyCuratedFrontmatter(entry, fmData) {
342
348
  const quality = asNonEmptyString(fmData.quality);
343
349
  if (quality)
344
350
  entry.quality = normalizeQuality(quality);
351
+ // SPEC-6 capture step: the `category:` frontmatter key (e.g. `convention`,
352
+ // `meta` on facts) must land on the indexed entry so category-keyed
353
+ // policies can see it. Trimmed; blank/non-string values are ignored.
354
+ const category = asNonEmptyString(fmData.category);
355
+ if (category)
356
+ entry.category = category;
345
357
  const beliefState = asNonEmptyString(fmData.beliefState);
346
358
  if (beliefState)
347
359
  entry.beliefState = beliefState;
@@ -351,17 +363,12 @@ export function applyCuratedFrontmatter(entry, fmData) {
351
363
  const contradictedBy = normalizeStringListOrUndefined(fmData.contradictedBy);
352
364
  if (contradictedBy)
353
365
  entry.contradictedBy = contradictedBy;
354
- // R5 — consolidation provenance. `generation` (merge depth counter) and
355
- // `source_refs` (merge/distill provenance pointers) are written by the
356
- // improve pipeline; captured into the index so the collapse detector can
357
- // count over-generation assets and follow merges without filesystem reads.
366
+ // R5 — consolidation generation depth is captured so the collapse detector
367
+ // can count over-generation assets without filesystem reads.
358
368
  const generation = fmData.generation;
359
369
  if (typeof generation === "number" && Number.isFinite(generation) && generation > 0) {
360
370
  entry.generation = Math.floor(generation);
361
371
  }
362
- const sourceRefs = normalizeStringListOrUndefined(fmData.source_refs);
363
- if (sourceRefs)
364
- entry.sourceRefs = sourceRefs;
365
372
  const currentBeliefRefs = normalizeStringListOrUndefined(fmData.currentBeliefRefs);
366
373
  if (currentBeliefRefs)
367
374
  entry.currentBeliefRefs = currentBeliefRefs;
@@ -384,27 +391,20 @@ export function applyCuratedFrontmatter(entry, fmData) {
384
391
  if (evidenceSources)
385
392
  entry.evidenceSources = evidenceSources;
386
393
  // Phase 5A / Advantage D5: capture parent ref for derived memories.
387
- // Memory-inference writes `source: "memory:<parent>"` and `inferred: true`
388
- // (and a derived child name suffix `.derived`). We mirror that source ref
389
- // into `entry.derivedFrom` so the indexer can populate the dedicated
390
- // `derived_from` column. Non-derived entries leave this field unset.
394
+ // Memory-inference writes `source: "memories/<parent>"` and `inferred: true`
395
+ // (and a derived child name suffix `.derived`). We mirror that source ref into
396
+ // `entry.derivedFrom` so the indexer can populate the dedicated `derived_from`
397
+ // column. Group-C item 2: the column is stored in the 0.9.0 `memories/<name>`
398
+ // conceptId grammar — moving in lockstep with the `getDerivedForParent` lookup
399
+ // key (search-hit-enrichers) so producer + consumer speak one grammar.
400
+ // Non-derived entries leave this field unset.
391
401
  if (entry.type === "memory") {
392
402
  const isDerivedByName = entry.name.toLowerCase().endsWith(".derived");
393
403
  const isDerivedByFm = fmData.inferred === true;
394
404
  if (isDerivedByName || isDerivedByFm) {
395
- const sourceStr = asNonEmptyString(fmData.source);
396
- if (sourceStr?.includes(":")) {
397
- entry.derivedFrom = sourceStr;
398
- }
399
- else {
400
- // Fallback: some legacy renderings store only `derivedFrom: <name>`
401
- // (a bare parent name). Promote it to a `memory:` ref so the lookup
402
- // column stays consistent.
403
- const derivedFromName = asNonEmptyString(fmData.derivedFrom);
404
- if (derivedFromName) {
405
- entry.derivedFrom = derivedFromName.includes(":") ? derivedFromName : `memory:${derivedFromName}`;
406
- }
407
- }
405
+ const parent = derivedFromConceptId(asNonEmptyString(fmData.source), asNonEmptyString(fmData.derivedFrom));
406
+ if (parent)
407
+ entry.derivedFrom = parent;
408
408
  }
409
409
  }
410
410
  const intent = normalizeIntent(fmData.intent);
@@ -484,53 +484,84 @@ export function applyWikiFrontmatter(entry, fmData) {
484
484
  entry.sources = filtered;
485
485
  }
486
486
  }
487
- const WIKI_INFRA_FILES = new Set(["schema.md", "index.md", "log.md"]);
488
487
  /**
489
- * Apply wiki-specific index exclusions while leaving all other stash files
490
- * untouched.
488
+ * Parse one `verified:` family value into the `IndexDocument.provenance` shape.
489
+ * Accepts OKF v0.2's list form and its documented single-mapping shorthand
490
+ * ("consumers MUST treat a bare mapping as a one-element list", SPEC §5.2).
491
+ * Tolerant: an entry without a usable `by` is dropped individually.
492
+ */
493
+ function parseVerifiedFamily(value) {
494
+ const entries = Array.isArray(value) ? value : [value];
495
+ return entries
496
+ .filter((v) => v !== null && typeof v === "object" && !Array.isArray(v))
497
+ .map((v) => {
498
+ const by = asNonEmptyString(v.by);
499
+ if (!by)
500
+ return undefined;
501
+ const at = asNonEmptyString(v.at);
502
+ return at ? { by, at } : { by };
503
+ })
504
+ .filter((v) => v !== undefined);
505
+ }
506
+ /**
507
+ * Extract the OKF v0.2 provenance families that `promoteProposal` stamps onto
508
+ * accepted AKM-native proposals (D2, #730), and apply them to the entry.
509
+ *
510
+ * The on-disk shape is deliberately **hybrid** (owner decision, #730 review):
511
+ * `generated:` and `verified:` are stamped BARE at the top level, exactly as
512
+ * OKF v0.2 spells them, because neither has any pre-existing AKM consumer — so
513
+ * a third-party OKF v0.2 reader treating an AKM stash as an OKF bundle sees
514
+ * spec-conformant trust metadata, which is what `okf-support.md`'s
515
+ * "AKM Markdown is an OKF-compatible superset" positioning promises. Only
516
+ * `sources` stays namespaced under `provenance:`, because a bare top-level
517
+ * `sources:` genuinely collides with {@link applyWikiFrontmatter}'s
518
+ * pre-existing citation-**string** convention (which silently drops
519
+ * non-strings).
491
520
  *
492
- * - In a normal stash, excludes wiki-root `schema.md`, `index.md`, `log.md`.
493
- * - In a wiki-root stash source (`wikiName`), excludes those same root-level
494
- * infrastructure files.
521
+ * The nested `provenance.generatedBy` / `.generatedAt` / `.verified` spellings
522
+ * are still read as a fallback so assets stamped by an earlier build of this
523
+ * branch keep resolving. Tolerant throughout: any malformed sub-field is
524
+ * dropped individually rather than rejecting the whole block.
495
525
  */
496
- export function shouldIndexStashFile(stashRoot, file, options) {
497
- const relPath = path.relative(stashRoot, file);
498
- if (!relPath || relPath.startsWith("..") || path.isAbsolute(relPath))
499
- return true;
500
- const segments = relPath.split(/[\\/]+/).filter(Boolean);
501
- if (segments.length === 0)
502
- return true;
503
- // Skip env .env files that have a sibling .sensitive marker file.
504
- if (segments[0] === "env" && (file.endsWith(".env") || path.basename(file) === ".env")) {
505
- const markerPath = file.replace(/\.env$/, ".sensitive");
506
- if (fs.existsSync(markerPath))
507
- return false;
508
- }
509
- // The legacy `vaults/` directory (frozen copy left by the 0.8 migration) is
510
- // never indexed — the `vault` asset type was removed in 0.9.0.
511
- if (segments[0] === "vaults") {
512
- return false;
513
- }
514
- // Skip secret files that are themselves a `.sensitive` marker, or that have a
515
- // sibling `<name>.sensitive` marker. Secrets are otherwise indexed by name
516
- // only (their bytes are never read — see buildEntryFromFile guards).
517
- if (segments[0] === "secrets") {
518
- if (file.endsWith(".sensitive") || file.endsWith(".lock"))
519
- return false;
520
- if (fs.existsSync(`${file}.sensitive`))
521
- return false;
522
- }
523
- if (options?.treatStashRootAsWikiRoot) {
524
- return !(segments.length === 1 && WIKI_INFRA_FILES.has(segments[0]));
526
+ export function applyProvenanceFrontmatter(entry, fmData) {
527
+ const provenance = fmData.provenance;
528
+ const nested = provenance !== null && typeof provenance === "object" && !Array.isArray(provenance)
529
+ ? provenance
530
+ : {};
531
+ const result = {};
532
+ // Bare `generated: {by, at}` (OKF v0.2 §5.3) wins; nested is the fallback.
533
+ const generatedMapping = fmData.generated !== null && typeof fmData.generated === "object" && !Array.isArray(fmData.generated)
534
+ ? fmData.generated
535
+ : undefined;
536
+ const generatedBy = asNonEmptyString(generatedMapping?.by) ?? asNonEmptyString(nested.generatedBy);
537
+ if (generatedBy)
538
+ result.generatedBy = generatedBy;
539
+ const generatedAt = asNonEmptyString(generatedMapping?.at) ?? asNonEmptyString(nested.generatedAt);
540
+ if (generatedAt)
541
+ result.generatedAt = generatedAt;
542
+ // Bare `verified:` (list or single mapping) wins; nested is the fallback.
543
+ const verified = fmData.verified !== undefined ? parseVerifiedFamily(fmData.verified) : parseVerifiedFamily(nested.verified);
544
+ if (verified.length > 0)
545
+ result.verified = verified;
546
+ // `sources` is namespaced-only — see the collision note above.
547
+ if (Array.isArray(nested.sources)) {
548
+ const sources = nested.sources
549
+ .filter((s) => s !== null && typeof s === "object" && !Array.isArray(s))
550
+ .map((s) => {
551
+ const resource = asNonEmptyString(s.resource);
552
+ return resource ? { resource } : undefined;
553
+ })
554
+ .filter((s) => s !== undefined);
555
+ if (sources.length > 0)
556
+ result.sources = sources;
525
557
  }
526
- const wikisIdx = segments.indexOf("wikis");
527
- if (wikisIdx < 0 || wikisIdx + 1 >= segments.length)
528
- return true;
529
- const wikiRelativeSegments = segments.slice(wikisIdx + 2);
530
- if (wikiRelativeSegments.length === 0)
531
- return true;
532
- return !(wikiRelativeSegments.length === 1 && WIKI_INFRA_FILES.has(wikiRelativeSegments[0]));
558
+ if (Object.keys(result).length > 0)
559
+ entry.provenance = result;
533
560
  }
561
+ // AKM-stash indexing policy (env/vaults/secrets sensitive-marker + wiki-infra
562
+ // exclusions) moved to the `akm` adapter's `recognize` as path/stat-based
563
+ // abstention (owner ruling 2026-07-21 — adapter-owned filtering). See
564
+ // `akmStashAbstains` in `src/core/adapter/adapters/akm-adapter.ts`.
534
565
  /**
535
566
  * Extract `@param` JSDoc tags from a script file's leading comment block.
536
567
  *
@@ -797,7 +828,6 @@ function mergeAliases(existing, generated) {
797
828
  *
798
829
  * This predicate is used by `enhanceDirsWithLlm` to skip the LLM call for
799
830
  * entries that were previously enriched and already carry all three fields.
800
- * Pass `reEnrich = true` in the caller to bypass this check.
801
831
  */
802
832
  export function isEnrichmentComplete(entry) {
803
833
  const hasDescription = typeof entry.description === "string" && entry.description.trim().length > 0;
@@ -805,39 +835,206 @@ export function isEnrichmentComplete(entry) {
805
835
  const hasSearchHints = Array.isArray(entry.searchHints) && entry.searchHints.length > 0;
806
836
  return hasDescription && hasTags && hasSearchHints;
807
837
  }
808
- // ── Metadata Generation ─────────────────────────────────────────────────────
838
+ // ── Body-opening extraction (stash-conventions SPEC-8) ──────────────────────
809
839
  /**
810
- * Shared pipeline (steps 2-6) for building a single StashEntry from a file.
811
- *
812
- * Both `generateMetadata` and `generateMetadataFlat` perform identical work
813
- * once the initial `entry` object has been seeded with type and canonical name.
814
- * This helper encapsulates that shared pipeline so the two callers only differ
815
- * in how they determine the asset type and canonical name (step 1):
816
- *
817
- * - `generateMetadata` — explicit `assetType` arg + `deriveCanonicalAssetName`
818
- * - `generateMetadataFlat` — type from `runMatchers()` + `deriveCanonicalAssetNameFromStashRoot`
840
+ * Maximum length of a captured self-situating body opening. Bounds index-size
841
+ * growth and keeps a single verbose opening from dominating the low-weight
842
+ * `content` FTS column.
843
+ */
844
+ export const BODY_OPENING_MAX_CHARS = 280;
845
+ /**
846
+ * Minimum characters retained when the cap truncates at a word boundary. A
847
+ * boundary cut that would retain less than this falls back to a hard cut, so
848
+ * one pathological long token cannot gut the capture.
849
+ */
850
+ const BODY_OPENING_MIN_RETAINED_CHARS = 250;
851
+ /**
852
+ * True when `index.indexBodyOpening` is enabled in the user config.
819
853
  *
820
- * @param file Absolute path to the file being processed.
821
- * @param assetType Resolved asset type string (already validated by caller).
822
- * @param canonicalName Resolved canonical name (already computed by caller).
823
- * @param dirPath Directory containing the file (used for tag fallback).
824
- * @param pkgMeta Pre-loaded package.json metadata for this directory (may be null/undefined).
825
- * @param stashRoot Stash root used for renderer search hints context.
826
- * @param ctx FileContext for the file (may be pre-built by the caller).
827
- * @param match Pre-resolved MatchResult when available (from `generateMetadataFlat`).
828
- * @returns The populated entry, or `{ skip: true, warning: string }` when the
829
- * renderer throws and the file should be dropped.
854
+ * The gate is the GLOBAL user config, read directly by the metadata pass so
855
+ * every indexing entry point (stash walk, flat walk, write-path indexing)
856
+ * honors the flag without parameter plumbing. Fail-open: an unreadable or
857
+ * invalid config must never break indexing (CLI entry points surface config
858
+ * errors loudly on their own), so any load failure reads as "off".
859
+ */
860
+ function isBodyOpeningIndexingEnabled() {
861
+ try {
862
+ return loadUserConfig().index?.indexBodyOpening === true;
863
+ }
864
+ catch {
865
+ return false;
866
+ }
867
+ }
868
+ /**
869
+ * Locate a leading nested frontmatter block in a body: up to three blank
870
+ * lines, then a `---` line, closed by a later `---` line. Mirrors the
871
+ * base-linter's `parseInnerFrontmatterBlock` recognition — when `akm
872
+ * remember` wraps a session-capture hook's file in its own frontmatter, the
873
+ * hook's `---\nakm_memory_kind: …\n---` block survives at the top of the
874
+ * body. Returns the open/close line indexes, or `null` when no block opens.
875
+ * Location only — callers apply their own interior checks (marker scan in
876
+ * {@link hasSessionMemoryMarker}, shape test in {@link isFrontmatterShaped}).
877
+ */
878
+ function findInnerFrontmatterBlock(lines) {
879
+ let i = 0;
880
+ while (i < lines.length && i < 3 && lines[i].trim() === "")
881
+ i += 1;
882
+ if (lines[i] !== "---")
883
+ return null;
884
+ for (let j = i + 1; j < lines.length; j += 1) {
885
+ if (lines[j] === "---")
886
+ return { open: i, close: j };
887
+ }
888
+ return null;
889
+ }
890
+ /**
891
+ * True when the document carries the session-capture `akm_memory_kind`
892
+ * marker — in the outer frontmatter data OR in a nested inner block at the
893
+ * top of the body (both producer layouts exist; see base-linter's
894
+ * `extractFrontmatterRefs`). Session bodies are raw transcripts, never a
895
+ * self-situating opening.
830
896
  */
831
- async function buildEntryFromFile(file, assetType, canonicalName, dirPath, pkgMeta, stashRoot, ctx, match) {
897
+ function hasSessionMemoryMarker(fmData, body) {
898
+ if (typeof fmData.akm_memory_kind === "string")
899
+ return true;
900
+ const lines = body.split(/\r?\n/);
901
+ const block = findInnerFrontmatterBlock(lines);
902
+ if (!block)
903
+ return false;
904
+ for (let i = block.open + 1; i < block.close; i += 1) {
905
+ if (/^akm_memory_kind:\s*\S/.test(lines[i]))
906
+ return true;
907
+ }
908
+ return false;
909
+ }
910
+ /**
911
+ * True when the interior of a candidate inner block (located by
912
+ * {@link findInnerFrontmatterBlock}) actually reads as YAML frontmatter:
913
+ * every line is blank, indented (a continuation or nested value), or shaped
914
+ * like a top-level `key:` mapping entry. Ordinary prose bracketed by two
915
+ * thematic-break `---` lines fails this test, so a decorative opening
916
+ * callout is treated as the paragraph it is instead of being discarded
917
+ * (review finding on SPEC-8 — the block finder alone accepts ANY content up
918
+ * to an arbitrarily distant closing `---`).
919
+ */
920
+ function isFrontmatterShaped(lines, block) {
921
+ for (let i = block.open + 1; i < block.close; i += 1) {
922
+ const line = lines[i];
923
+ if (line.trim() === "")
924
+ continue;
925
+ if (/^\s/.test(line))
926
+ continue; // indented continuation / nested value
927
+ if (/^[A-Za-z0-9_.-]+:(\s|$)/.test(line))
928
+ continue; // top-level key
929
+ return false;
930
+ }
931
+ return true;
932
+ }
933
+ /**
934
+ * Extract the first prose paragraph of a markdown body (frontmatter already
935
+ * stripped by the caller): skip blank lines, ATX headings, setext `=`
936
+ * underlines (discarding the heading text above them), thematic breaks,
937
+ * fenced code blocks (``` or ~~~, including their contents), and a leading
938
+ * nested frontmatter block — skipped only when its interior is actually
939
+ * frontmatter-shaped, so prose wrapped in decorative `---` lines is still
940
+ * captured; then collect consecutive non-blank lines until the paragraph
941
+ * ends. Deliberate asymmetry: a `---` row after captured prose ENDS the
942
+ * paragraph and keeps it (favoring the callout/thematic-break reading over
943
+ * CommonMark's setext-H2), while a `=+` row can only be a setext underline
944
+ * and so discards the pending lines as heading text. The result is capped at
945
+ * {@link BODY_OPENING_MAX_CHARS} chars — truncated at the last word boundary
946
+ * that still retains a substantial prefix, with a trailing ellipsis. Returns
947
+ * `undefined` when the body has no prose (frontmatter-only files,
948
+ * headings/fences-only bodies).
949
+ */
950
+ export function extractBodyOpening(body) {
951
+ const lines = body.split(/\r?\n/);
952
+ const innerBlock = findInnerFrontmatterBlock(lines);
953
+ const start = innerBlock && isFrontmatterShaped(lines, innerBlock) ? innerBlock.close + 1 : 0;
954
+ const paragraph = [];
955
+ let inFence = false;
956
+ let fenceChar = "";
957
+ let inHtmlComment = false;
958
+ for (let i = start; i < lines.length; i += 1) {
959
+ const trimmed = lines[i].trim();
960
+ const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
961
+ if (inFence) {
962
+ // Fence interiors are never prose (and may be secrets-adjacent command
963
+ // text); skip until the matching closing marker.
964
+ if (fenceMatch && fenceMatch[1].charAt(0) === fenceChar)
965
+ inFence = false;
966
+ continue;
967
+ }
968
+ if (inHtmlComment) {
969
+ // Comment interiors are machinery (the skeleton convention facts open
970
+ // with a <!-- SOFT guidance --> block), never orientation prose.
971
+ if (trimmed.includes("-->"))
972
+ inHtmlComment = false;
973
+ continue;
974
+ }
975
+ if (fenceMatch) {
976
+ if (paragraph.length > 0)
977
+ break; // a fence ends an open paragraph
978
+ inFence = true;
979
+ fenceChar = fenceMatch[1].charAt(0);
980
+ continue;
981
+ }
982
+ if (trimmed.startsWith("<!--")) {
983
+ if (paragraph.length > 0)
984
+ break; // a comment ends an open paragraph
985
+ if (!trimmed.includes("-->"))
986
+ inHtmlComment = true;
987
+ continue;
988
+ }
989
+ if (/^=+$/.test(trimmed)) {
990
+ // A row of `=` is a setext H1 underline: the pending lines are heading
991
+ // text, not prose — discard them and keep searching. (A bare `=` row
992
+ // with nothing pending is skipped like any other non-prose divider.)
993
+ paragraph.length = 0;
994
+ continue;
995
+ }
996
+ const isBlank = trimmed === "";
997
+ const isHeading = /^#{1,6}(\s|$)/.test(trimmed);
998
+ const isThematicBreak = /^(-{3,}|\*{3,}|_{3,})$/.test(trimmed);
999
+ if (isBlank || isHeading || isThematicBreak) {
1000
+ if (paragraph.length > 0)
1001
+ break; // paragraph complete
1002
+ continue; // still searching for the first prose line
1003
+ }
1004
+ paragraph.push(trimmed);
1005
+ }
1006
+ const text = paragraph.join("\n");
1007
+ if (!text)
1008
+ return undefined;
1009
+ if (text.length <= BODY_OPENING_MAX_CHARS)
1010
+ return text;
1011
+ // Cap: prefer a word-boundary cut (never mid-token), but only when it keeps
1012
+ // a substantial prefix; append a one-char ellipsis inside the budget.
1013
+ const slice = text.slice(0, BODY_OPENING_MAX_CHARS - 1);
1014
+ const lastBoundary = Math.max(slice.lastIndexOf(" "), slice.lastIndexOf("\n"), slice.lastIndexOf("\t"));
1015
+ let cut = lastBoundary >= BODY_OPENING_MIN_RETAINED_CHARS ? slice.slice(0, lastBoundary) : slice;
1016
+ // slice() counts UTF-16 code units, so the no-boundary fallback can end on
1017
+ // the high half of a surrogate pair — a lone surrogate that corrupts the
1018
+ // FTS/embedding text. Drop it rather than emit invalid UTF-16.
1019
+ const lastCode = cut.charCodeAt(cut.length - 1);
1020
+ if (lastCode >= 0xd800 && lastCode <= 0xdbff)
1021
+ cut = cut.slice(0, -1);
1022
+ return `${cut.trimEnd()}…`;
1023
+ }
1024
+ // ── Metadata Generation ─────────────────────────────────────────────────────
1025
+ /**
1026
+ * Priorities 1-2 of the metadata pipeline — package.json (P1), `.md`
1027
+ * frontmatter (P2), and script `@param`/comment metadata (P2b) — everything
1028
+ * that runs BEFORE the renderer-contributor step (P3). Extracted (Chunk 5 M-b)
1029
+ * so the `akm` adapter's synchronous `recognize` shares this exact assembly
1030
+ * with the live index-drain path; the two differ ONLY in how they obtain the
1031
+ * P3 renderer metadata (the adapter uses the synchronous
1032
+ * `foldRecognizedMetadata`), guaranteeing P1/P2/P4 parity by construction.
1033
+ * Behavior-preserving: this is a verbatim lift of the former inline P1/P2/P2b
1034
+ * block. Mutates `entry` in place.
1035
+ */
1036
+ export function applyPreContributorFields(entry, file, ctx, pkgMeta) {
832
1037
  const ext = path.extname(file).toLowerCase();
833
- const baseName = path.basename(file, ext);
834
- const entry = {
835
- name: canonicalName,
836
- type: assetType,
837
- quality: "generated",
838
- confidence: 0.55,
839
- source: "filename",
840
- };
841
1038
  // Priority 1: Package.json metadata
842
1039
  if (pkgMeta) {
843
1040
  if (pkgMeta.description && !entry.description) {
@@ -851,7 +1048,7 @@ async function buildEntryFromFile(file, assetType, canonicalName, dirPath, pkgMe
851
1048
  // Priority 2: Frontmatter (for .md files -- overrides package.json description)
852
1049
  // Secrets are excluded even when the file happens to be `.md`: the whole file
853
1050
  // is the secret value and must never be read for frontmatter or any metadata.
854
- if (ext === ".md" && assetType !== "secret") {
1051
+ if (ext === ".md" && entry.type !== "secret") {
855
1052
  const content = ctx.content();
856
1053
  const parsed = parseFrontmatter(content);
857
1054
  applyCuratedFrontmatter(entry, parsed.data);
@@ -861,6 +1058,19 @@ async function buildEntryFromFile(file, assetType, canonicalName, dirPath, pkgMe
861
1058
  entry.parameters = fmParams;
862
1059
  // Pass wiki-pattern frontmatter through onto the entry
863
1060
  applyWikiFrontmatter(entry, parsed.data);
1061
+ // D2 (#730): reread the namespaced `provenance:` block promoteProposal stamps.
1062
+ applyProvenanceFrontmatter(entry, parsed.data);
1063
+ // Stash-organization conventions (SPEC-8): config-gated capture of the
1064
+ // self-situating body opening. Default off — enabling it changes indexed
1065
+ // text (collapse-detector canary baselines shift, and embeddings for
1066
+ // already-embedded entries are NOT regenerated; see docs/reference/configuration.md).
1067
+ // Session-kind memories are raw transcripts and are never captured;
1068
+ // secrets never reach this branch (guard above) and env files are not .md.
1069
+ if (isBodyOpeningIndexingEnabled() && !hasSessionMemoryMarker(parsed.data, parsed.content)) {
1070
+ const bodyOpening = extractBodyOpening(parsed.content);
1071
+ if (bodyOpening)
1072
+ entry.bodyOpening = bodyOpening;
1073
+ }
864
1074
  // Extract parameters from template placeholders ($1, $ARGUMENTS, {{named}})
865
1075
  if (entry.type === "command") {
866
1076
  const cmdParams = extractCommandParameters(parsed.content);
@@ -873,35 +1083,24 @@ async function buildEntryFromFile(file, assetType, canonicalName, dirPath, pkgMe
873
1083
  // Env files (.env) and secret files (whole-file secrets) are deliberately
874
1084
  // excluded — their contents are secrets and must never be parsed for @param
875
1085
  // or any other metadata that could embed a value into the entry.
876
- if (ext !== ".md" && assetType !== "env" && assetType !== "secret") {
1086
+ if (ext !== ".md" && entry.type !== "env" && entry.type !== "secret") {
877
1087
  const content = ctx.content();
878
1088
  const scriptParams = extractScriptParameters(file, content);
879
1089
  if (scriptParams)
880
1090
  entry.parameters = scriptParams;
881
1091
  applyCommentMetadata(entry, extractCommentMetadata(file, content));
882
1092
  }
883
- // Priority 3: Renderer metadata extraction
884
- // When no pre-resolved match is available (generateMetadata path), run
885
- // matchers now so the renderer can extract type-specific metadata.
886
- const resolvedMatch = match ?? (await runMatchers(ctx));
887
- if (resolvedMatch) {
888
- const renderer = await getRenderer(resolvedMatch.renderer);
889
- if (renderer) {
890
- const renderCtx = buildRenderContext(ctx, resolvedMatch, [stashRoot]);
891
- try {
892
- await applyMetadataContributors(entry, {
893
- rendererName: renderer.name,
894
- renderContext: renderCtx,
895
- });
896
- }
897
- catch (error) {
898
- return {
899
- skip: true,
900
- warning: buildMetadataSkipWarning(file, assetType, error),
901
- };
902
- }
903
- }
904
- }
1093
+ }
1094
+ /**
1095
+ * Priority 4 of the metadata pipeline — filename-heuristic fallbacks (P4) that
1096
+ * run AFTER the renderer-contributor step (P3): a filename description when none
1097
+ * was set, path/dir-derived tags, tag normalization, and alias generation.
1098
+ * Extracted (Chunk 5 M-b) alongside {@link applyPreContributorFields} so both
1099
+ * pipeline paths share it. Behavior-preserving verbatim lift. Mutates `entry`.
1100
+ */
1101
+ export function applyPostContributorFields(entry, file, canonicalName, dirPath) {
1102
+ const ext = path.extname(file).toLowerCase();
1103
+ const baseName = path.basename(file, ext);
905
1104
  // Priority 4: Filename heuristics (fallback)
906
1105
  if (!entry.description) {
907
1106
  entry.description = fileNameToDescription(baseName);
@@ -911,84 +1110,31 @@ async function buildEntryFromFile(file, assetType, canonicalName, dirPath, pkgMe
911
1110
  if (!entry.tags || entry.tags.length === 0) {
912
1111
  entry.tags = extractTagsFromPath(file, dirPath);
913
1112
  }
1113
+ // Stash-organization conventions (SPEC-2): directory (scope/domain) tokens
1114
+ // always reach the tags column, even when the author set explicit tags, so
1115
+ // nested assets keep the exact-tag ranking boost for their scope token.
1116
+ // Derived from canonicalName (the ref subpath) rather than the filesystem
1117
+ // path so the stash-walk and flat-walk indexing paths agree (the flat walk
1118
+ // passes `path.dirname(file)` as dirPath, which strips directory segments
1119
+ // from the fallback above). Filename tokens are deliberately NOT merged when
1120
+ // explicit tags exist — they already live in the FTS name column and in
1121
+ // aliases, and merging them would inflate exact-tag matches for every
1122
+ // filename word. `normalizeTerms` below dedupes author-restated tokens.
1123
+ entry.tags = [...(entry.tags ?? []), ...extractDirTagsFromName(canonicalName)];
914
1124
  entry.tags = normalizeTerms(entry.tags ?? []);
915
1125
  entry.aliases = mergeAliases(entry.aliases, buildAliases(canonicalName, entry.tags));
916
1126
  // Search hints are only generated when LLM is configured (via enhanceStashWithLlm)
917
1127
  // Heuristic search hints are too noisy to be useful for search quality
918
1128
  entry.filename = path.basename(file);
919
- return entry;
920
- }
921
- export async function generateMetadata(dirPath, assetType, files, typeRoot = dirPath) {
922
- const entries = [];
923
- const warnings = [];
924
- const pkgMeta = extractPackageMetadata(dirPath);
925
- for (const file of files) {
926
- const ext = path.extname(file).toLowerCase();
927
- const baseName = path.basename(file, ext);
928
- const fileName = path.basename(file);
929
- // Skip non-relevant files
930
- if (!isRelevantAssetFile(assetType, fileName))
931
- continue;
932
- const canonicalName = deriveCanonicalAssetName(assetType, typeRoot, file) ?? baseName;
933
- // Build file context with typeRoot as the stash root so renderer context
934
- // and search hints are scoped to the type directory.
935
- const fileCtx = buildFileContext(typeRoot, file);
936
- // Step 1: type is explicit; delegate steps 2-6 to the shared pipeline.
937
- const result = await buildEntryFromFile(file, assetType, canonicalName, dirPath, pkgMeta, typeRoot, fileCtx, null);
938
- if ("skip" in result) {
939
- warnings.push(result.warning);
940
- continue;
941
- }
942
- entries.push(result);
943
- }
944
- return warnings.length > 0 ? { entries, warnings } : { entries };
945
- }
946
- /**
947
- * Generate metadata for files using the matcher system instead of a fixed asset type.
948
- *
949
- * This is the flat-walk counterpart of `generateMetadata`. It classifies each
950
- * file via `runMatchers()` and uses the matched type for canonical naming.
951
- * Files that no matcher claims are silently skipped.
952
- */
953
- export async function generateMetadataFlat(stashRoot, files) {
954
- const entries = [];
955
- const warnings = [];
956
- const pkgMetaCache = new Map();
957
- for (const file of files) {
958
- if (!shouldIndexStashFile(stashRoot, file))
959
- continue;
960
- // Step 1: determine type and canonical name via the matcher system.
961
- const ctx = buildFileContext(stashRoot, file);
962
- const match = await runMatchers(ctx);
963
- if (!match)
964
- continue;
965
- const assetType = match.type;
966
- if (!isAssetType(assetType))
967
- continue;
968
- // If the file lives under a known type directory, use that as the root
969
- // for canonical naming so names don't include the type prefix.
970
- // e.g. scripts/deploy.sh → "deploy.sh" not "scripts/deploy.sh"
971
- const ext = path.extname(file).toLowerCase();
972
- const baseName = path.basename(file, ext);
973
- const canonicalName = deriveCanonicalAssetNameFromStashRoot(assetType, stashRoot, file) ?? baseName;
974
- // Resolve package.json metadata with a per-directory cache.
975
- const dirPath = path.dirname(file);
976
- if (!pkgMetaCache.has(dirPath)) {
977
- pkgMetaCache.set(dirPath, extractPackageMetadata(dirPath));
978
- }
979
- const pkgMeta = pkgMetaCache.get(dirPath);
980
- // Steps 2-6: delegate to the shared pipeline; pass the pre-resolved match
981
- // so we don't run matchers a second time.
982
- const result = await buildEntryFromFile(file, assetType, canonicalName, dirPath, pkgMeta, stashRoot, ctx, match);
983
- if ("skip" in result) {
984
- warnings.push(result.warning);
985
- continue;
986
- }
987
- entries.push(result);
988
- }
989
- return warnings.length > 0 ? { entries, warnings } : { entries };
990
1129
  }
991
- function buildMetadataSkipWarning(filePath, assetType, error) {
1130
+ // The pre-0.9.0 flat-walk matcher-pass metadata source was DELETED in Chunk 5
1131
+ // F4a M-core-3. Its role (recognize a stash root's files into durable entries)
1132
+ // is now the `akm` adapter's `recognize`, drained by `indexer/scan/drain-dir.ts`
1133
+ // (`drainDirDocuments` for the live indexer, `recognizeStashEntries` for the
1134
+ // `manifest` fallback / `registry` index builder / metadata unit tests). The
1135
+ // flip is the F4 engine swap; the shadow-parity gate proved recognize produced
1136
+ // the identical entries before the old pass was removed.
1137
+ export function buildMetadataSkipWarning(filePath, assetType, error) {
992
1138
  const detail = error instanceof Error ? error.message : String(error);
993
1139
  // Workflow errors are already multi-line `path:line — message` blocks; print
994
1140
  // them as-is so the author sees a flat list without a redundant prefix.
@@ -1127,3 +1273,26 @@ export function extractTagsFromPath(filePath, rootDir) {
1127
1273
  }
1128
1274
  return Array.from(tags);
1129
1275
  }
1276
+ /**
1277
+ * Extract scope/domain tags from the DIRECTORY segments of a canonical asset
1278
+ * name (the ref subpath — e.g. `"projectA/auth-tip"` → `["projecta"]`).
1279
+ *
1280
+ * Unlike {@link extractTagsFromPath} this never tokenizes the filename
1281
+ * segment: it exists so a nested asset's directory (scope/domain) tokens can
1282
+ * be merged into explicit author tags without dragging every filename word
1283
+ * into exact-tag matching (SPEC-2, docs/architecture/specs/stash-conventions-code-spec.md).
1284
+ * Tokenization mirrors `extractTagsFromPath`: each segment splits on `-`/`_`/
1285
+ * `.`, lowercased, single-character tokens dropped. A name with no directory
1286
+ * segments yields no tags.
1287
+ */
1288
+ export function extractDirTagsFromName(name) {
1289
+ const tags = new Set();
1290
+ for (const segment of name.split("/").slice(0, -1)) {
1291
+ for (const token of segment.split(/[-_.]+/)) {
1292
+ const clean = token.toLowerCase().trim();
1293
+ if (clean && clean.length > 1)
1294
+ tags.add(clean);
1295
+ }
1296
+ }
1297
+ return Array.from(tags);
1298
+ }