akm-cli 0.9.0-beta.6 → 0.9.0-rc.0

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 (326) hide show
  1. package/CHANGELOG.md +663 -0
  2. package/README.md +12 -4
  3. package/dist/akm +38 -0
  4. package/dist/akm-migrate-storage +38 -0
  5. package/dist/assets/help/help-improve.md +9 -6
  6. package/dist/assets/hints/cli-hints-full.md +6 -5
  7. package/dist/assets/profiles/default.json +9 -4
  8. package/dist/assets/profiles/frequent.json +1 -1
  9. package/dist/assets/profiles/memory-focus.json +1 -1
  10. package/dist/assets/profiles/proactive-maintenance.json +25 -0
  11. package/dist/assets/profiles/quick.json +1 -1
  12. package/dist/assets/profiles/recombine-only.json +21 -0
  13. package/dist/assets/profiles/reflect-distill.json +30 -0
  14. package/dist/assets/profiles/synthesize.json +15 -0
  15. package/dist/assets/profiles/thorough.json +1 -1
  16. package/dist/assets/prompts/consolidate-system.md +23 -0
  17. package/dist/assets/prompts/contradiction-judge.md +33 -0
  18. package/dist/assets/prompts/distill-knowledge-system.md +22 -0
  19. package/dist/assets/prompts/distill-lesson-system.md +36 -0
  20. package/dist/assets/prompts/extract-session.md +11 -3
  21. package/dist/assets/prompts/graph-extract-system.md +1 -0
  22. package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
  23. package/dist/assets/prompts/memory-infer-system.md +1 -0
  24. package/dist/assets/prompts/memory-infer-user.md +5 -0
  25. package/dist/assets/prompts/metadata-enhance-system.md +1 -0
  26. package/dist/assets/prompts/procedural-system.md +44 -0
  27. package/dist/assets/prompts/recombine-system.md +40 -0
  28. package/dist/assets/prompts/staleness-detect-system.md +6 -0
  29. package/dist/assets/prompts/validate-summary-judge.md +1 -0
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
  38. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
  39. package/dist/assets/templates/html/health.html +281 -111
  40. package/dist/assets/wiki/ingest-workflow-template.md +45 -16
  41. package/dist/assets/wiki/schema-template.md +4 -4
  42. package/dist/cli/clack.js +56 -0
  43. package/dist/cli/config-migrate.js +7 -1
  44. package/dist/cli/confirm.js +1 -1
  45. package/dist/cli/parse-args.js +46 -1
  46. package/dist/cli/shared.js +28 -0
  47. package/dist/cli.js +25 -14
  48. package/dist/commands/agent/agent-dispatch.js +3 -2
  49. package/dist/commands/agent/agent-support.js +0 -7
  50. package/dist/commands/agent/contribute-cli.js +26 -7
  51. package/dist/commands/config-cli.js +26 -13
  52. package/dist/commands/env/child-env.js +47 -0
  53. package/dist/commands/env/env-cli.js +220 -227
  54. package/dist/commands/env/env.js +14 -67
  55. package/dist/commands/env/secret-cli.js +140 -138
  56. package/dist/commands/feedback-cli.js +153 -147
  57. package/dist/commands/graph/graph-cli.js +5 -13
  58. package/dist/commands/graph/graph.js +76 -72
  59. package/dist/commands/health/advisories.js +151 -0
  60. package/dist/commands/health/checks.js +103 -16
  61. package/dist/commands/health/html-report.js +447 -81
  62. package/dist/commands/health/improve-metrics.js +771 -0
  63. package/dist/commands/health/llm-usage.js +65 -0
  64. package/dist/commands/health/md-report.js +103 -0
  65. package/dist/commands/health/metrics.js +278 -0
  66. package/dist/commands/health/stash-exposure.js +46 -0
  67. package/dist/commands/health/surfaces.js +216 -0
  68. package/dist/commands/health/task-runs.js +135 -0
  69. package/dist/commands/health/types.js +26 -0
  70. package/dist/commands/health/windows.js +195 -0
  71. package/dist/commands/health.js +91 -1083
  72. package/dist/commands/improve/anti-collapse.js +170 -0
  73. package/dist/commands/improve/calibration.js +161 -0
  74. package/dist/commands/improve/collapse-detector.js +421 -0
  75. package/dist/commands/improve/consolidate/chunking.js +141 -0
  76. package/dist/commands/improve/consolidate/eligibility.js +64 -0
  77. package/dist/commands/improve/consolidate/merge.js +145 -0
  78. package/dist/commands/improve/consolidate/sanitize.js +231 -0
  79. package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
  80. package/dist/commands/improve/consolidate.js +1313 -1278
  81. package/dist/commands/improve/dedup.js +482 -0
  82. package/dist/commands/improve/distill/content-repair.js +202 -0
  83. package/dist/commands/improve/distill/promote-memory.js +229 -0
  84. package/dist/commands/improve/distill/quality-gate.js +236 -0
  85. package/dist/commands/improve/distill-guards.js +127 -0
  86. package/dist/commands/improve/distill-promotion-policy.js +826 -167
  87. package/dist/commands/improve/distill.js +243 -599
  88. package/dist/commands/improve/eligibility.js +434 -0
  89. package/dist/commands/improve/encoding-salience.js +205 -0
  90. package/dist/commands/improve/extract-cli.js +179 -59
  91. package/dist/commands/improve/extract-prompt.js +55 -4
  92. package/dist/commands/improve/extract-watch.js +140 -0
  93. package/dist/commands/improve/extract.js +409 -43
  94. package/dist/commands/improve/feedback-valence.js +54 -0
  95. package/dist/commands/improve/hot-probation.js +45 -0
  96. package/dist/commands/improve/improve-auto-accept.js +160 -7
  97. package/dist/commands/improve/improve-cli.js +115 -73
  98. package/dist/commands/improve/improve-profiles.js +32 -8
  99. package/dist/commands/improve/improve-result-file.js +15 -25
  100. package/dist/commands/improve/improve-session.js +58 -0
  101. package/dist/commands/improve/improve.js +510 -2537
  102. package/dist/commands/improve/locks.js +154 -0
  103. package/dist/commands/improve/loop-stages.js +1100 -0
  104. package/dist/commands/improve/memory/memory-belief.js +14 -15
  105. package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
  106. package/dist/commands/improve/memory/memory-improve.js +27 -27
  107. package/dist/commands/improve/outcome-loop.js +270 -0
  108. package/dist/commands/improve/preparation.js +2002 -0
  109. package/dist/commands/improve/proactive-maintenance.js +115 -0
  110. package/dist/commands/improve/procedural.js +398 -0
  111. package/dist/commands/improve/recombine.js +818 -0
  112. package/dist/commands/improve/reflect-noise.js +0 -0
  113. package/dist/commands/improve/reflect.js +212 -45
  114. package/dist/commands/improve/salience.js +455 -0
  115. package/dist/commands/improve/schema-similarity-gate.js +168 -0
  116. package/dist/commands/improve/shared.js +51 -0
  117. package/dist/commands/improve/triage.js +93 -0
  118. package/dist/commands/lint/agent-linter.js +19 -24
  119. package/dist/commands/lint/base-linter.js +173 -60
  120. package/dist/commands/lint/command-linter.js +19 -24
  121. package/dist/commands/lint/env-key-rules.js +38 -1
  122. package/dist/commands/lint/fact-linter.js +39 -0
  123. package/dist/commands/lint/index.js +31 -13
  124. package/dist/commands/lint/memory-linter.js +1 -1
  125. package/dist/commands/lint/registry.js +7 -2
  126. package/dist/commands/lint/task-linter.js +3 -3
  127. package/dist/commands/lint/workflow-linter.js +26 -1
  128. package/dist/commands/observability-cli.js +4 -4
  129. package/dist/commands/proposal/drain-policies.js +13 -4
  130. package/dist/commands/proposal/drain.js +45 -51
  131. package/dist/commands/proposal/legacy-import.js +115 -0
  132. package/dist/commands/proposal/proposal-cli.js +24 -34
  133. package/dist/commands/proposal/proposal.js +7 -1
  134. package/dist/commands/proposal/propose.js +8 -3
  135. package/dist/commands/proposal/repository.js +829 -0
  136. package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
  137. package/dist/commands/proposal/validators/proposals.js +93 -882
  138. package/dist/commands/read/curate.js +419 -103
  139. package/dist/commands/read/knowledge.js +10 -3
  140. package/dist/commands/read/remember-cli.js +133 -138
  141. package/dist/commands/read/search-cli.js +15 -8
  142. package/dist/commands/read/search.js +22 -11
  143. package/dist/commands/read/show.js +106 -14
  144. package/dist/commands/registry-cli.js +76 -87
  145. package/dist/commands/remember.js +11 -12
  146. package/dist/commands/sources/add-cli.js +91 -95
  147. package/dist/commands/sources/history.js +1 -1
  148. package/dist/commands/sources/init.js +66 -18
  149. package/dist/commands/sources/installed-stashes.js +11 -3
  150. package/dist/commands/sources/schema-repair.js +44 -46
  151. package/dist/commands/sources/self-update.js +2 -2
  152. package/dist/commands/sources/source-add.js +7 -3
  153. package/dist/commands/sources/sources-cli.js +3 -3
  154. package/dist/commands/sources/stash-cli.js +29 -41
  155. package/dist/commands/sources/stash-skeleton.js +57 -8
  156. package/dist/commands/tasks/default-tasks.js +15 -2
  157. package/dist/commands/tasks/tasks-cli.js +20 -29
  158. package/dist/commands/tasks/tasks.js +39 -11
  159. package/dist/commands/wiki-cli.js +23 -38
  160. package/dist/commands/workflow-cli.js +15 -1
  161. package/dist/core/asset/asset-registry.js +3 -1
  162. package/dist/core/asset/asset-spec.js +21 -4
  163. package/dist/core/asset/frontmatter.js +188 -167
  164. package/dist/core/asset/markdown.js +8 -0
  165. package/dist/core/authoring-rules.js +92 -0
  166. package/dist/core/common.js +4 -23
  167. package/dist/core/concurrent.js +10 -1
  168. package/dist/core/config/config-io.js +10 -1
  169. package/dist/core/config/config-migration.js +18 -40
  170. package/dist/core/config/config-schema.js +389 -58
  171. package/dist/core/config/config-types.js +3 -3
  172. package/dist/core/config/config.js +67 -22
  173. package/dist/core/deep-merge.js +38 -0
  174. package/dist/core/errors.js +1 -0
  175. package/dist/core/eval/rank-metrics.js +113 -0
  176. package/dist/core/events.js +4 -7
  177. package/dist/core/improve-types.js +47 -8
  178. package/dist/core/logs-db.js +14 -75
  179. package/dist/core/parse.js +36 -16
  180. package/dist/core/paths.js +21 -18
  181. package/dist/core/standards/resolve-standards-context.js +87 -0
  182. package/dist/core/standards/resolve-stash-standards.js +99 -0
  183. package/dist/core/standards/resolve-type-conventions.js +66 -0
  184. package/dist/core/state/migrations.js +770 -0
  185. package/dist/core/state-db.js +142 -1091
  186. package/dist/core/structured.js +69 -0
  187. package/dist/core/time.js +53 -0
  188. package/dist/core/warn.js +21 -0
  189. package/dist/core/write-source.js +37 -0
  190. package/dist/indexer/db/db.js +356 -780
  191. package/dist/indexer/db/entry-mapper.js +41 -0
  192. package/dist/indexer/db/graph-db.js +129 -86
  193. package/dist/indexer/db/llm-cache.js +2 -2
  194. package/dist/indexer/db/schema.js +516 -0
  195. package/dist/indexer/ensure-index.js +103 -24
  196. package/dist/indexer/feedback/utility-policy.js +75 -0
  197. package/dist/indexer/graph/graph-boost.js +51 -41
  198. package/dist/indexer/graph/graph-extraction.js +207 -4
  199. package/dist/indexer/index-writer-lock.js +106 -0
  200. package/dist/indexer/index-written-assets.js +105 -0
  201. package/dist/indexer/indexer.js +291 -310
  202. package/dist/indexer/passes/dir-staleness.js +114 -0
  203. package/dist/indexer/passes/memory-inference.js +13 -5
  204. package/dist/indexer/passes/metadata.js +20 -0
  205. package/dist/indexer/read-preflight.js +23 -0
  206. package/dist/indexer/search/db-search.js +89 -13
  207. package/dist/indexer/search/fts-query.js +51 -0
  208. package/dist/indexer/search/ranking-contributors.js +95 -9
  209. package/dist/indexer/search/ranking.js +79 -3
  210. package/dist/indexer/search/search-fields.js +6 -0
  211. package/dist/indexer/search/search-source.js +32 -21
  212. package/dist/indexer/search/semantic-status.js +4 -0
  213. package/dist/indexer/walk/matchers.js +9 -0
  214. package/dist/indexer/walk/walker.js +21 -13
  215. package/dist/integrations/agent/builders.js +39 -13
  216. package/dist/integrations/agent/config.js +20 -59
  217. package/dist/integrations/agent/detect.js +9 -0
  218. package/dist/integrations/agent/index.js +3 -19
  219. package/dist/integrations/agent/model-aliases.js +7 -2
  220. package/dist/integrations/agent/profiles.js +7 -1
  221. package/dist/integrations/agent/prompts.js +75 -9
  222. package/dist/integrations/agent/runner-dispatch.js +59 -0
  223. package/dist/integrations/agent/runner.js +13 -9
  224. package/dist/integrations/agent/spawn.js +69 -67
  225. package/dist/integrations/harnesses/claude/agent-builder.js +1 -1
  226. package/dist/integrations/harnesses/claude/index.js +2 -0
  227. package/dist/integrations/harnesses/claude/session-log.js +11 -1
  228. package/dist/integrations/harnesses/index.js +2 -3
  229. package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
  230. package/dist/integrations/harnesses/opencode/index.js +2 -0
  231. package/dist/integrations/harnesses/opencode/session-log.js +173 -3
  232. package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
  233. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +98 -17
  234. package/dist/integrations/harnesses/types.js +1 -0
  235. package/dist/integrations/session-logs/index.js +16 -0
  236. package/dist/llm/call-ai.js +2 -2
  237. package/dist/llm/client.js +57 -15
  238. package/dist/llm/embedder.js +67 -4
  239. package/dist/llm/embedders/cache.js +3 -1
  240. package/dist/llm/embedders/deterministic.js +66 -0
  241. package/dist/llm/embedders/local.js +73 -3
  242. package/dist/llm/feature-gate.js +16 -15
  243. package/dist/llm/graph-extract.js +67 -44
  244. package/dist/llm/memory-infer-impl.js +138 -0
  245. package/dist/llm/memory-infer.js +1 -127
  246. package/dist/llm/metadata-enhance.js +44 -31
  247. package/dist/llm/structured-call.js +49 -0
  248. package/dist/migrate-storage-node.mjs +8 -0
  249. package/dist/output/context.js +5 -5
  250. package/dist/output/renderers.js +85 -14
  251. package/dist/output/shapes/curate.js +14 -2
  252. package/dist/output/shapes/helpers.js +0 -3
  253. package/dist/output/shapes/passthrough.js +2 -1
  254. package/dist/output/text/helpers.js +29 -1
  255. package/dist/output/text/workflow.js +1 -0
  256. package/dist/registry/providers/skills-sh.js +21 -147
  257. package/dist/registry/providers/static-index.js +15 -157
  258. package/dist/registry/resolve.js +27 -9
  259. package/dist/runtime.js +25 -1
  260. package/dist/scripts/migrate-storage.js +2718 -2354
  261. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +891 -597
  262. package/dist/setup/detect.js +9 -0
  263. package/dist/setup/legacy-config.js +106 -0
  264. package/dist/setup/prompt.js +57 -0
  265. package/dist/setup/providers.js +14 -0
  266. package/dist/setup/registry-stash-loader.js +12 -0
  267. package/dist/setup/semantic-assets.js +124 -0
  268. package/dist/setup/setup.js +52 -1614
  269. package/dist/setup/steps/connection.js +734 -0
  270. package/dist/setup/steps/output.js +31 -0
  271. package/dist/setup/steps/platforms.js +124 -0
  272. package/dist/setup/steps/semantic.js +27 -0
  273. package/dist/setup/steps/sources.js +222 -0
  274. package/dist/setup/steps/stashdir.js +42 -0
  275. package/dist/setup/steps/tasks.js +152 -0
  276. package/dist/sources/include.js +6 -2
  277. package/dist/sources/providers/filesystem.js +0 -1
  278. package/dist/sources/providers/git-install.js +210 -0
  279. package/dist/sources/providers/git-provider.js +234 -0
  280. package/dist/sources/providers/git-stash.js +248 -0
  281. package/dist/sources/providers/git.js +10 -661
  282. package/dist/sources/providers/npm.js +2 -6
  283. package/dist/sources/providers/provider-utils.js +13 -7
  284. package/dist/sources/providers/sync-from-ref.js +9 -1
  285. package/dist/sources/providers/tar-utils.js +16 -8
  286. package/dist/sources/providers/website.js +9 -5
  287. package/dist/sources/website-ingest.js +187 -29
  288. package/dist/sources/wiki-fetchers/registry.js +53 -0
  289. package/dist/sources/wiki-fetchers/youtube.js +239 -0
  290. package/dist/storage/database.js +45 -10
  291. package/dist/storage/managed-db.js +82 -0
  292. package/dist/storage/repositories/canaries-repository.js +107 -0
  293. package/dist/storage/repositories/consolidation-repository.js +38 -0
  294. package/dist/storage/repositories/embeddings-repository.js +72 -0
  295. package/dist/storage/repositories/events-repository.js +187 -0
  296. package/dist/storage/repositories/extract-sessions-repository.js +96 -0
  297. package/dist/storage/repositories/improve-runs-repository.js +146 -0
  298. package/dist/storage/repositories/index-db.js +14 -8
  299. package/dist/storage/repositories/proposals-repository.js +220 -0
  300. package/dist/storage/repositories/recombine-repository.js +213 -0
  301. package/dist/storage/repositories/registry-cache.js +93 -0
  302. package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
  303. package/dist/storage/repositories/task-history-repository.js +93 -0
  304. package/dist/storage/sqlite-pragmas.js +146 -0
  305. package/dist/tasks/backends/cron.js +1 -1
  306. package/dist/tasks/backends/index.js +9 -0
  307. package/dist/tasks/backends/launchd.js +1 -1
  308. package/dist/tasks/backends/schtasks.js +1 -1
  309. package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
  310. package/dist/tasks/runner.js +15 -13
  311. package/dist/text-import-hook.mjs +0 -0
  312. package/dist/wiki/wiki.js +52 -11
  313. package/dist/workflows/cli.js +1 -0
  314. package/dist/workflows/db.js +3 -4
  315. package/dist/workflows/runtime/runs.js +43 -118
  316. package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
  317. package/dist/workflows/validate-summary.js +2 -7
  318. package/docs/README.md +69 -18
  319. package/docs/data-and-telemetry.md +5 -4
  320. package/docs/migration/release-notes/0.7.0.md +1 -1
  321. package/docs/migration/release-notes/0.9.0.md +39 -0
  322. package/package.json +10 -10
  323. package/dist/assets/tasks/core/update-stashes.yml +0 -4
  324. package/dist/commands/db-cli.js +0 -23
  325. package/dist/indexer/db/db-backup.js +0 -376
  326. package/dist/indexer/passes/staleness-detect.js +0 -488
@@ -24,27 +24,30 @@
24
24
  import fs from "node:fs";
25
25
  import os from "node:os";
26
26
  import path from "node:path";
27
- import { assertNever } from "../../core/assert.js";
28
27
  import { parseAssetRef } from "../../core/asset/asset-ref.js";
29
28
  import { assembleAssetFromString, serializeFrontmatter } from "../../core/asset/asset-serialize.js";
30
29
  import { parseFrontmatter } from "../../core/asset/frontmatter.js";
31
30
  import { stripMarkdownFences } from "../../core/asset/markdown.js";
31
+ import { DESCRIPTION_MAX_CHARS, requiresDescription } from "../../core/authoring-rules.js";
32
32
  import { resolveStashDir } from "../../core/common.js";
33
- import { loadConfig } from "../../core/config/config.js";
33
+ import { getImproveProcessConfig, loadConfig } from "../../core/config/config.js";
34
34
  import { ConfigError, UsageError } from "../../core/errors.js";
35
35
  import { appendEvent, readEvents } from "../../core/events.js";
36
36
  import { lintLessonContent } from "../../core/lesson-lint.js";
37
+ import { resolveStandardsContext } from "../../core/standards/resolve-standards-context.js";
38
+ import { warn } from "../../core/warn.js";
37
39
  import { lookup } from "../../indexer/indexer.js";
38
40
  import { runAgent, } from "../../integrations/agent/index.js";
39
41
  import { resolveProcessAgentProfile } from "../../integrations/agent/config.js";
40
42
  import { buildReflectPrompt, extractDraftConfidence, parseAgentProposalPayload, } from "../../integrations/agent/prompts.js";
41
- import { resolveImproveProcessRunnerFromProfile, runnerIsLlm, runnerSupportsFileWrite, } from "../../integrations/agent/runner.js";
43
+ import { resolveDefaultLlmRunner, resolveImproveProcessRunnerFromProfile, runnerIsLlm, runnerSupportsFileWrite, } from "../../integrations/agent/runner.js";
44
+ import { executeRunner } from "../../integrations/agent/runner-dispatch.js";
42
45
  import { runOpencodeSdk } from "../../integrations/harnesses/opencode-sdk/index.js";
43
46
  import { chatCompletion } from "../../llm/client.js";
44
47
  import { isLlmFeatureEnabled } from "../../llm/feature-gate.js";
45
48
  import { baseFailureFields, enoentHintMessage, isEnoentFailure, loadAgentConfigFromDisk, resolveAgentProfile, } from "../agent/agent-support.js";
46
- import { checkReflectSize } from "../proposal/validators/proposal-quality-validators.js";
47
- import { createProposal, isProposalSkipped, listProposals, } from "../proposal/validators/proposals.js";
49
+ import { createProposal, isProposalSkipped, listProposals, } from "../proposal/repository.js";
50
+ import { checkReflectSize, isValidDescription } from "../proposal/validators/proposal-quality-validators.js";
48
51
  import { deriveLessonRef, runLessonQualityJudge } from "./distill.js";
49
52
  import { classifyReflectChange } from "./reflect-noise.js";
50
53
  const MAX_FEEDBACK_LINES = 10;
@@ -383,6 +386,101 @@ function splitFrontmatter(raw) {
383
386
  return { fmText: null, body: raw };
384
387
  return { fmText: m[1], body: m[2] };
385
388
  }
389
+ /**
390
+ * Strip an LLM-appended duplicate frontmatter block from a body string.
391
+ *
392
+ * When the LLM echoes the original source file verbatim after its rewrite,
393
+ * the resulting body contains a second `---...---` YAML block. We detect it
394
+ * by requiring BOTH a balanced fence (opening + closing `---`) AND YAML-like
395
+ * `key: value` content inside, so legitimate Markdown thematic breaks and
396
+ * code-fence examples are never truncated.
397
+ */
398
+ function stripAppendedFrontmatter(body) {
399
+ const fencePattern = /\n---\r?\n([\s\S]*?)\n---\r?\n/;
400
+ const match = body.match(fencePattern);
401
+ if (!match)
402
+ return body;
403
+ // Only strip when the captured block looks like YAML frontmatter.
404
+ if (!/^\w[\w-]*:/m.test(match[1]))
405
+ return body;
406
+ return body.slice(0, body.indexOf(match[0])).replace(/\s+$/, "");
407
+ }
408
+ /**
409
+ * #636 — deterministically derive a valid `description` from an asset's existing
410
+ * metadata when one is missing. Sources, in priority order: the `title:`
411
+ * frontmatter field, the first `# Heading` in the (proposed or source) body, and
412
+ * the first sentence of the opening body paragraph. The candidate is normalized
413
+ * (whitespace collapsed, trailing punctuation/markdown stripped, clamped to the
414
+ * description max) and only returned if it PASSES `isValidDescription` — so this
415
+ * never produces a heading-fragment, truncated, or otherwise gate-failing value.
416
+ * Returns `undefined` when nothing usable can be derived (caller leaves the
417
+ * proposal as-is rather than fabricating prose).
418
+ *
419
+ * This is intentionally deterministic and lives in the reflect proposal-build
420
+ * path — it does NOT touch the validators or the promote-time repair.
421
+ */
422
+ function deriveDescriptionFromAsset(title, proposedBody, sourceBody, targetRef) {
423
+ // Each candidate is tagged with its kind. A title or `# Heading` is a bare
424
+ // fragment ("Paged.js — Named Page") that reads poorly as a description even
425
+ // when it is long enough to pass the length gate, so for those we prefer the
426
+ // padded sentence form. A prose sentence is already a sentence, so it is used
427
+ // as-is (padding it would double-wrap an already-complete sentence).
428
+ const candidates = [];
429
+ // 1. title: frontmatter
430
+ if (typeof title === "string" && title.trim())
431
+ candidates.push({ text: title.trim(), kind: "fragment" });
432
+ // 2. first `# Heading` (proposed body first, then source body)
433
+ for (const body of [proposedBody, sourceBody]) {
434
+ const headingMatch = body.match(/^#{1,6}\s+(.+?)\s*$/m);
435
+ if (headingMatch?.[1])
436
+ candidates.push({ text: headingMatch[1].trim(), kind: "fragment" });
437
+ }
438
+ // 3. first sentence of the opening prose paragraph (skip headings, fences,
439
+ // list markers, blockquotes — those are not prose).
440
+ for (const body of [proposedBody, sourceBody]) {
441
+ const firstSentence = firstProseSentence(body);
442
+ if (firstSentence)
443
+ candidates.push({ text: firstSentence, kind: "prose" });
444
+ }
445
+ for (const { text, kind } of candidates) {
446
+ const normalized = normalizeDescriptionCandidate(text);
447
+ if (!normalized)
448
+ continue;
449
+ // For a title/heading fragment, try the padded sentence form FIRST so the
450
+ // result reads as a sentence rather than a bare fragment — a short but valid
451
+ // title like "Paged.js — Named Page" (21 chars) would otherwise be returned
452
+ // verbatim. Fall back to the bare form only if the padded form fails the
453
+ // gate. A prose candidate is already a sentence, so it is used as-is.
454
+ const variants = kind === "fragment" ? [`Reference notes on ${normalized}.`, normalized] : [normalized];
455
+ for (const v of variants) {
456
+ const clamped = v.length > DESCRIPTION_MAX_CHARS ? v.slice(0, DESCRIPTION_MAX_CHARS).trimEnd() : v;
457
+ if (isValidDescription(clamped, targetRef, { skipRefTailCheck: true }).ok)
458
+ return clamped;
459
+ }
460
+ }
461
+ return undefined;
462
+ }
463
+ /** Extract the first prose sentence from a markdown body, or `""` if none. */
464
+ function firstProseSentence(body) {
465
+ for (const rawLine of body.split(/\r?\n/)) {
466
+ const line = rawLine.trim();
467
+ if (!line)
468
+ continue;
469
+ if (/^(#{1,6}\s|```|~~~|[-*+]\s|\d+\.\s|>|\||<!--)/.test(line))
470
+ continue;
471
+ const sentenceMatch = line.match(/^(.+?[.!?])(\s|$)/);
472
+ return (sentenceMatch?.[1] ?? line).trim();
473
+ }
474
+ return "";
475
+ }
476
+ /** Normalize a description candidate: strip markdown markers, collapse space. */
477
+ function normalizeDescriptionCandidate(raw) {
478
+ return raw
479
+ .replace(/`/g, "")
480
+ .replace(/^[#>*\-\s]+/, "")
481
+ .replace(/\s+/g, " ")
482
+ .trim();
483
+ }
386
484
  /**
387
485
  * Reflect post-processor — enforces the safety rails described at the top of
388
486
  * this file:
@@ -408,7 +506,7 @@ function splitFrontmatter(raw) {
408
506
  * from `payload.frontmatter` so identity fields can be enforced. Size guard
409
507
  * is skipped because there is no source to compare against.
410
508
  */
411
- function sanitizeReflectPayload(payload, sourceContent, targetRef) {
509
+ export function sanitizeReflectPayload(payload, sourceContent, targetRef) {
412
510
  const warnings = [];
413
511
  const { fmText: sourceFmText, body: sourceBody } = sourceContent
414
512
  ? splitFrontmatter(sourceContent)
@@ -449,7 +547,35 @@ function sanitizeReflectPayload(payload, sourceContent, targetRef) {
449
547
  mergedFm[field] = sourceFm[field];
450
548
  }
451
549
  }
452
- const cleanedBody = rawLlmBody.replace(/^\s+/, "");
550
+ const cleanedBody = stripAppendedFrontmatter(rawLlmBody.replace(/^\s+/, ""));
551
+ // #636 — deterministic description fallback (reflect-side belt-and-suspenders).
552
+ // If the type requires a `description` and the merged frontmatter is still
553
+ // MISSING one (source had none AND the model didn't author one), derive a
554
+ // description DETERMINISTICALLY from the existing `title:` frontmatter or the
555
+ // first `# Heading` / opening body sentence — never free-form invention. This
556
+ // runs in the reflect proposal-build path, BEFORE the proposal is created, so
557
+ // the validator/promote path is left untouched (no gate fabricates content).
558
+ //
559
+ // Scope is the issue's target: a source asset that ALREADY carries frontmatter
560
+ // (e.g. scraped docs: `source`/`title`/`scraped`) but has a MISSING/empty
561
+ // `description`. We deliberately do NOT fire when:
562
+ // - the source has no frontmatter block at all (injecting one would be a
563
+ // structural change and would defeat the #580 no-op/cosmetic noise gate
564
+ // for a pure body echo), or
565
+ // - a present-but-otherwise-invalid description exists (too short, a heading
566
+ // fragment) — overwriting authored content is out of scope; the prompt
567
+ // instruction handles improving it instead.
568
+ const refType = targetRef.includes(":") ? (targetRef.split(":")[0] ?? "") : "";
569
+ const mergedDesc = mergedFm.description;
570
+ const descIsMissing = typeof mergedDesc !== "string" || mergedDesc.trim().length === 0;
571
+ const sourceHadFrontmatter = sourceFmText !== null && Object.keys(sourceFm).length > 0;
572
+ if (refType && requiresDescription(refType) && descIsMissing && sourceHadFrontmatter) {
573
+ const derived = deriveDescriptionFromAsset(mergedFm.title, cleanedBody, sourceBody, targetRef);
574
+ if (derived) {
575
+ mergedFm.description = derived;
576
+ warnings.push("Synthesized a deterministic `description` from title/heading (#636) — source and proposal lacked one.");
577
+ }
578
+ }
453
579
  // Size guard — only when source body is meaningfully large. The pure
454
580
  // predicate lives in `core/proposal-quality-validators` so the same check
455
581
  // also runs inside `runProposalValidators` on `proposal accept`.
@@ -585,6 +711,9 @@ export async function akmReflect(options = {}) {
585
711
  metadata: {
586
712
  ...(options.task ? { task: options.task } : {}),
587
713
  ...(options.profile ? { profile: options.profile } : {}),
714
+ // Attribution tagging: stamp the eligibility lane so reflect_invoked can be
715
+ // sliced by lane downstream. See EligibilitySource.
716
+ ...(options.eligibilitySource ? { eligibilitySource: options.eligibilitySource } : {}),
588
717
  },
589
718
  });
590
719
  // Fix #3 (observability 0.8.0): every failure path below MUST emit
@@ -670,7 +799,7 @@ export async function akmReflect(options = {}) {
670
799
  }
671
800
  else {
672
801
  const cfg = options.config ?? loadConfig();
673
- const reflectProcess = cfg.profiles?.improve?.default?.processes?.reflect;
802
+ const reflectProcess = getImproveProcessConfig(cfg, "reflect", options.improveProfile);
674
803
  // Resolve the runner from the improve profile's reflect entry when present.
675
804
  runnerSpec = resolveImproveProcessRunnerFromProfile(reflectProcess, cfg) ?? undefined;
676
805
  if (runnerSpec) {
@@ -702,6 +831,28 @@ export async function akmReflect(options = {}) {
702
831
  throw err;
703
832
  throw err;
704
833
  }
834
+ // P1.3 (meta-review 07, Chain G): unattended `akm improve` must never hand
835
+ // reflect a tool-capable runner. Agent-CLI/SDK runners have filesystem
836
+ // access, so a poisoned asset body flowing through the scheduled reflect
837
+ // prompt could steer an agent with tools; the tool-less LLM HTTP runner
838
+ // corrupts output text at worst (src/llm/call-ai.ts). An llm RunnerSpec
839
+ // resolved from the process block is honored; anything else — an agent/sdk
840
+ // spec, a resolved agent profile, or the default-agent fallback below — is
841
+ // pinned to `defaults.llm`, failing CLOSED when that is unset.
842
+ // `options.agentProfile` stays exempt: it is a direct-injection test seam
843
+ // the cron path (loop-stages) never sets.
844
+ if (options.eventSource === "improve" && !options.agentProfile && (!runnerSpec || !runnerIsLlm(runnerSpec))) {
845
+ const cfg = options.config ?? loadConfig();
846
+ const pinned = resolveDefaultLlmRunner(cfg, resolvedTimeoutMs);
847
+ if (!pinned) {
848
+ throw new ConfigError("Unattended improve pins reflect to the tool-less LLM runner, but the config resolves a tool-capable runner and no defaults.llm profile exists to pin to.", "LLM_NOT_CONFIGURED", 'Set processes.reflect.mode to "llm" on the active improve profile, or configure defaults.llm.');
849
+ }
850
+ warn(runnerSpec
851
+ ? `[akm] reflect: unattended improve resolved a tool-capable "${runnerSpec.kind}" runner — pinned to the tool-less LLM runner (07 Chain-G).`
852
+ : "[akm] reflect: unattended improve would have fallen back to an agent profile — pinned to the tool-less LLM runner (07 Chain-G).");
853
+ runnerSpec = pinned;
854
+ profile = undefined;
855
+ }
705
856
  // Ensure profile is set for agent/sdk runners that don't use runnerSpec
706
857
  if (!runnerSpec && !profile) {
707
858
  const agent = options.agentConfig ?? loadAgentConfigFromDisk();
@@ -723,6 +874,9 @@ export async function akmReflect(options = {}) {
723
874
  // Reflexion-style verbal-RL: inject rejected proposals so the agent avoids
724
875
  // reproducing proposals that have already been reviewed and refused.
725
876
  const rejectedProposals = readRejectedProposals(stash, options.ref);
877
+ // Standards "rulebook" for this target — wiki schema (wiki page) or stash
878
+ // convention/meta facts (non-wiki asset); empty when neither fires.
879
+ const standardsContext = resolveStandardsContext(options.ref, stash);
726
880
  // 5. Spawn the agent — with optional Self-Refine loop (R-1 / #372).
727
881
  //
728
882
  // maxRefineIters controls how many agent invocations are made:
@@ -791,6 +945,7 @@ export async function akmReflect(options = {}) {
791
945
  ...(schemaHints.length > 0 ? { schemaHints } : {}),
792
946
  ...(relatedLessons.length > 0 ? { relatedLessons } : {}),
793
947
  ...(options.task ? { task: options.task } : {}),
948
+ ...(standardsContext.trim() ? { standardsContext } : {}),
794
949
  ...(options.avoidPatterns && options.avoidPatterns.length > 0 ? { avoidPatterns: options.avoidPatterns } : {}),
795
950
  ...(rejectedProposals.length > 0 ? { rejectedProposals } : {}),
796
951
  // R-1: inject prior draft as self-critique target on iterations > 0
@@ -822,44 +977,39 @@ export async function akmReflect(options = {}) {
822
977
  iterResult = await runAgent(resolvedProfile, prompt, runOptions);
823
978
  }
824
979
  else if (runnerSpec) {
825
- // v2: dispatch through unified RunnerSpec
980
+ // v2: dispatch through the unified RunnerSpec seam (X3). The `agent` /
981
+ // `sdk` arms route to the default profile runners; the `llm` arm is
982
+ // reflect-specific (wraps `runReflectViaLlm` — its bespoke iteration
983
+ // shape) so it is supplied as the `llm` handler.
826
984
  const runOptions = {
827
985
  stdio: "captured",
828
986
  parseOutput: "text",
829
987
  ...(Object.keys(agentEnv).length > 0 ? { env: agentEnv } : {}),
830
988
  };
831
- switch (runnerSpec.kind) {
832
- case "llm":
833
- // LLM HTTP path — `draftFilePath` is accepted for type symmetry
834
- // (see `RunReflectViaLlmOptions.draftFilePath` docstring) but is
835
- // intentionally a no-op. The prompt builder above also did not
836
- // include the file-write contract for this kind, so the LLM is
837
- // still asked for JSON via stdout.
838
- iterResult = await runReflectViaLlm({
839
- prompt,
840
- connection: runnerSpec.connection,
841
- timeoutMs: runnerSpec.timeoutMs ?? (typeof resolvedTimeoutMs === "number" ? resolvedTimeoutMs : undefined),
842
- priorDraft,
843
- iteration: iter,
844
- responseSchema: REFLECT_JSON_SCHEMA,
845
- chat: options.chat,
846
- ...(maxTokensForLlm !== undefined ? { maxTokens: maxTokensForLlm } : {}),
847
- });
848
- break;
849
- case "sdk":
850
- iterResult = await runOpencodeSdk(runnerSpec.profile, prompt ?? "", runOptions);
851
- break;
852
- case "agent":
853
- iterResult = await runAgent(runnerSpec.profile, prompt, {
854
- ...runOptions,
855
- ...(runnerSpec.timeoutMs !== undefined ? { timeoutMs: runnerSpec.timeoutMs } : {}),
856
- });
857
- break;
858
- default:
859
- // Exhaustiveness arm (H1): a 4th RunnerSpec kind becomes a compile
860
- // error here instead of leaving `iterResult` unassigned at runtime.
861
- assertNever(runnerSpec);
862
- }
989
+ iterResult = await executeRunner(runnerSpec, prompt ?? "", runOptions, {
990
+ llm: async (spec) =>
991
+ // LLM HTTP path — `draftFilePath` is accepted for type symmetry
992
+ // (see `RunReflectViaLlmOptions.draftFilePath` docstring) but is
993
+ // intentionally a no-op. The prompt builder above also did not
994
+ // include the file-write contract for this kind, so the LLM is
995
+ // still asked for JSON via stdout.
996
+ runReflectViaLlm({
997
+ prompt,
998
+ connection: spec.connection,
999
+ timeoutMs: spec.timeoutMs ?? (typeof resolvedTimeoutMs === "number" ? resolvedTimeoutMs : undefined),
1000
+ priorDraft,
1001
+ iteration: iter,
1002
+ responseSchema: REFLECT_JSON_SCHEMA,
1003
+ chat: options.chat,
1004
+ ...(maxTokensForLlm !== undefined ? { maxTokens: maxTokensForLlm } : {}),
1005
+ }),
1006
+ // The `agent` arm (and only the agent arm — preserving prior behavior)
1007
+ // overlays `spec.timeoutMs` onto the base run options.
1008
+ runAgent: (profile, p, opts) => runAgent(profile, p, {
1009
+ ...opts,
1010
+ ...(runnerSpec.timeoutMs !== undefined ? { timeoutMs: runnerSpec.timeoutMs } : {}),
1011
+ }),
1012
+ });
863
1013
  }
864
1014
  else {
865
1015
  // Production path (v1): dispatch directly to the appropriate runner.
@@ -1044,7 +1194,8 @@ export async function akmReflect(options = {}) {
1044
1194
  // `profiles.improve.default.processes.reflect.qualityGate.enabled` or
1045
1195
  // `profiles.improve.default.processes.distill.qualityGate.enabled` (the
1046
1196
  // `lesson_quality_gate` flag name is the legacy alias still accepted by
1047
- // `isLlmFeatureEnabled`). Fail-open: any judge error passes through.
1197
+ // `isLlmFeatureEnabled`). Fail-CLOSED (07 P0-2): a judge error / no-LLM /
1198
+ // parse failure rejects the proposal rather than passing it through.
1048
1199
  // G-Eval (arXiv:2303.16634) — quality judgment before admission.
1049
1200
  const runtimeConfig = options.config ??
1050
1201
  (() => {
@@ -1148,8 +1299,19 @@ export async function akmReflect(options = {}) {
1148
1299
  // (new-asset proposals have nothing to diff against).
1149
1300
  if (assetContent !== undefined) {
1150
1301
  const changeKind = classifyReflectChange(assetContent, payload.content);
1151
- if (changeKind !== "substantive") {
1152
- const subreason = changeKind === "noop" ? "reflect_skipped_noop" : "reflect_skipped_cosmetic";
1302
+ // 'low-value' is config-gated (#639). DEFAULT OFF — absent = byte-identical
1303
+ // pre-#639 behaviour (low-value treated the same as substantive). Resolved
1304
+ // by the caller from the ACTIVE improve profile's
1305
+ // `processes.reflect.lowValueFilter.enabled` and passed via options, so the
1306
+ // running profile (not a hardcoded `profiles.improve.default`) decides.
1307
+ const lowValueFilterEnabled = options.lowValueFilter === true;
1308
+ const isDeferred = changeKind === "noop" || changeKind === "cosmetic" || (changeKind === "low-value" && lowValueFilterEnabled);
1309
+ if (isDeferred) {
1310
+ const subreason = changeKind === "noop"
1311
+ ? "reflect_skipped_noop"
1312
+ : changeKind === "low-value"
1313
+ ? "reflect_skipped_low_value"
1314
+ : "reflect_skipped_cosmetic";
1153
1315
  emitReflectFailed("no_change", subreason, options.ref, { changeKind });
1154
1316
  return {
1155
1317
  schemaVersion: 1,
@@ -1157,7 +1319,9 @@ export async function akmReflect(options = {}) {
1157
1319
  reason: "no_change",
1158
1320
  error: changeKind === "noop"
1159
1321
  ? `Reflect skipped: proposed content for ${payload.ref} is identical to the current asset (empty diff); no proposal created.`
1160
- : `Reflect skipped: proposed content for ${payload.ref} is a cosmetic-only reformat of the current asset (whitespace/fence/YAML-folding changes); no proposal created.`,
1322
+ : changeKind === "low-value"
1323
+ ? `Reflect skipped: proposed content for ${payload.ref} is a low-value prose micro-rewrite (few changed tokens, no structural changes); no proposal created.`
1324
+ : `Reflect skipped: proposed content for ${payload.ref} is a cosmetic-only reformat of the current asset (whitespace/fence/YAML-folding changes); no proposal created.`,
1161
1325
  ...(options.ref ? { ref: options.ref } : {}),
1162
1326
  exitCode: result.exitCode,
1163
1327
  };
@@ -1228,6 +1392,9 @@ export async function akmReflect(options = {}) {
1228
1392
  // `parseAgentProposalPayload` already clamps to [0, 1] and drops non-
1229
1393
  // finite values; `createProposal` runs its own sanitizer as a safety net.
1230
1394
  ...(typeof payload.confidence === "number" ? { confidence: payload.confidence } : {}),
1395
+ // Attribution tagging: persist the eligibility lane on the proposal so it
1396
+ // survives to accept/reject/revert time even across runs. See EligibilitySource.
1397
+ ...(options.eligibilitySource ? { eligibilitySource: options.eligibilitySource } : {}),
1231
1398
  };
1232
1399
  const proposalResult = createProposal(stash, createInput, options.ctx);
1233
1400
  if (isProposalSkipped(proposalResult)) {