akm-cli 0.9.0-beta.9 → 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 (325) hide show
  1. package/CHANGELOG.md +592 -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 -21
  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 +156 -155
  57. package/dist/commands/graph/graph-cli.js +5 -13
  58. package/dist/commands/graph/graph.js +3 -3
  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 -1091
  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 +1295 -1277
  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 +228 -605
  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 +54 -3
  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 +157 -10
  97. package/dist/commands/improve/improve-cli.js +115 -73
  98. package/dist/commands/improve/improve-profiles.js +28 -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 +485 -2764
  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 +37 -35
  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 +206 -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 +2 -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 -895
  138. package/dist/commands/read/curate.js +410 -111
  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 +19 -39
  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 +382 -62
  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 +18 -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 +132 -1126
  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 +259 -769
  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 +36 -92
  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 +18 -11
  200. package/dist/indexer/index-written-assets.js +105 -0
  201. package/dist/indexer/indexer.js +182 -204
  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 +10 -0
  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 +34 -11
  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 +2661 -2369
  261. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +883 -596
  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/website.js +9 -5
  286. package/dist/sources/website-ingest.js +187 -29
  287. package/dist/sources/wiki-fetchers/registry.js +53 -0
  288. package/dist/sources/wiki-fetchers/youtube.js +239 -0
  289. package/dist/storage/database.js +45 -10
  290. package/dist/storage/managed-db.js +82 -0
  291. package/dist/storage/repositories/canaries-repository.js +107 -0
  292. package/dist/storage/repositories/consolidation-repository.js +38 -0
  293. package/dist/storage/repositories/embeddings-repository.js +72 -0
  294. package/dist/storage/repositories/events-repository.js +187 -0
  295. package/dist/storage/repositories/extract-sessions-repository.js +96 -0
  296. package/dist/storage/repositories/improve-runs-repository.js +146 -0
  297. package/dist/storage/repositories/index-db.js +14 -8
  298. package/dist/storage/repositories/proposals-repository.js +220 -0
  299. package/dist/storage/repositories/recombine-repository.js +213 -0
  300. package/dist/storage/repositories/registry-cache.js +93 -0
  301. package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
  302. package/dist/storage/repositories/task-history-repository.js +93 -0
  303. package/dist/storage/sqlite-pragmas.js +146 -0
  304. package/dist/tasks/backends/cron.js +1 -1
  305. package/dist/tasks/backends/index.js +9 -0
  306. package/dist/tasks/backends/launchd.js +1 -1
  307. package/dist/tasks/backends/schtasks.js +1 -1
  308. package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
  309. package/dist/tasks/runner.js +15 -13
  310. package/dist/text-import-hook.mjs +0 -0
  311. package/dist/wiki/wiki.js +52 -11
  312. package/dist/workflows/cli.js +1 -0
  313. package/dist/workflows/db.js +3 -4
  314. package/dist/workflows/runtime/runs.js +43 -118
  315. package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
  316. package/dist/workflows/validate-summary.js +2 -7
  317. package/docs/README.md +69 -18
  318. package/docs/data-and-telemetry.md +5 -4
  319. package/docs/migration/release-notes/0.7.0.md +1 -1
  320. package/docs/migration/release-notes/0.9.0.md +39 -0
  321. package/package.json +10 -10
  322. package/dist/assets/tasks/core/update-stashes.yml +0 -4
  323. package/dist/commands/db-cli.js +0 -23
  324. package/dist/indexer/db/db-backup.js +0 -376
  325. package/dist/indexer/passes/staleness-detect.js +0 -488
@@ -26,6 +26,7 @@
26
26
  * during validation. We carry it through if the agent supplies it.
27
27
  */
28
28
  import { TYPE_DIRS } from "../../core/asset/asset-spec.js";
29
+ import { authoringRulesForType, DESCRIPTION_MAX_CHARS, DESCRIPTION_MIN_CHARS, requiresDescription, } from "../../core/authoring-rules.js";
29
30
  import { parseEmbeddedJsonResponse, stripCodeFences, stripThinkBlocks } from "../../core/parse.js";
30
31
  /**
31
32
  * Per-asset-type frontmatter / authoring hints surfaced in the prompt so
@@ -41,8 +42,9 @@ const TYPE_HINTS = {
41
42
  memory: "memory assets are short factual notes the user wants persisted across sessions. Frontmatter usually includes `description`.",
42
43
  workflow: "workflow assets are markdown describing a multi-step process. Include `# <Title>` and ordered `## Step N` sections.",
43
44
  script: "script assets are executable text files. Include a shebang and minimal usage comment.",
44
- env: "env assets are `.env` files holding a group of related CONFIGURATION for an app/service (KEY=VALUE pairs, `#` comments) — URLs, flags, and any credentials it needs. Values may or may not be sensitive; all are protected (key names discoverable, values stay on disk). Inject with `akm env run env:<name> -- <cmd>` (the safe path values never reach stdout/your context); do NOT run `akm env export` and read its output, as that prints values. For a single sensitive value used on its own for authentication (token, key, cert) use a `secret` instead. Never echo values back to the user.",
45
+ env: "env assets are `.env` files holding a group of related CONFIGURATION for an app/service (KEY=VALUE pairs, `#` comments) — URLs, flags, and any credentials it needs. Values may or may not be sensitive; all are protected (key names discoverable, values stay on disk). Inject with `akm env run env:<name> -- <cmd>`; prefer `--clean` in agent contexts so the child starts from a minimal inherited environment. AKM itself does not print values, but the child command can print its environment, so do not run `env`, `printenv`, shell tracing, or similar diagnostics when secrets are in scope. For a single sensitive value used on its own for authentication (token, key, cert) use a `secret` instead. Never echo values back to the user.",
45
46
  wiki: "wiki assets are markdown reference pages with `# Title` and structured headings.",
47
+ fact: "fact assets are durable stash-level facts (personal/team/project details, coding conventions, stash-meta). Frontmatter SHOULD include `description` and a `category` (personal|team|project|convention|meta); set `pinned: true` only for the small always-injected core. Keep each fact short, high-signal, and self-contained — it is durable context, not an episodic note.",
46
48
  };
47
49
  function hintForType(type) {
48
50
  return TYPE_HINTS[type] ?? `assets of type "${type}" — produce sensible markdown with optional frontmatter.`;
@@ -109,6 +111,30 @@ export function extractDraftConfidence(stdout) {
109
111
  return undefined;
110
112
  return value;
111
113
  }
114
+ /**
115
+ * Whether the source asset content has a non-empty `description:` key in its
116
+ * YAML frontmatter. Used by {@link buildReflectPrompt} (#636) to decide whether
117
+ * to inject the synthesize-a-description instruction. Uses an inline regex to
118
+ * avoid pulling the full YAML parser into the prompt module (mirrors the
119
+ * existing inline frontmatter handling here).
120
+ */
121
+ function sourceHasNonEmptyDescription(assetContent) {
122
+ if (!assetContent)
123
+ return false;
124
+ const fmMatch = assetContent.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
125
+ if (!fmMatch)
126
+ return false;
127
+ const fmBlock = fmMatch[1] ?? "";
128
+ // Match a top-level `description:` line and capture its inline value.
129
+ const descMatch = fmBlock.match(/^description\s*:\s*(.*)$/m);
130
+ if (!descMatch)
131
+ return false;
132
+ const value = (descMatch[1] ?? "")
133
+ .trim()
134
+ .replace(/^['"]|['"]$/g, "")
135
+ .trim();
136
+ return value.length > 0;
137
+ }
112
138
  /**
113
139
  * Build the prompt for `akm reflect [ref]`. Asks the agent to review an
114
140
  * existing asset (plus any negative feedback / lint findings) and propose
@@ -155,6 +181,34 @@ export function buildReflectPrompt(input) {
155
181
  // ref is set but no feedback — explicitly constrain scope to schema compliance
156
182
  sections.push("No usage feedback recorded. Limit your proposal to schema and structural improvements only: missing required frontmatter fields, unclear `when_to_use`, ambiguous description, or broken formatting. Do not speculate about runtime weaknesses you have not observed.");
157
183
  }
184
+ if (input.standardsContext?.trim()) {
185
+ sections.push("Standards to follow (the rulebook for this target):");
186
+ sections.push(input.standardsContext.trim());
187
+ }
188
+ {
189
+ const resolvedType = input.type ?? (input.ref?.includes(":") ? input.ref.split(":")[0] : "");
190
+ const authoringRules = resolvedType ? authoringRulesForType(resolvedType) : "";
191
+ if (authoringRules) {
192
+ sections.push(authoringRules);
193
+ }
194
+ // #636 — synthesize-a-description instruction. Many source assets (notably
195
+ // scraped docs: `source`/`title`/`scraped`) carry frontmatter but NO
196
+ // `description`. Reflect echoes the source frontmatter, so the proposal
197
+ // inherits the missing description and the promote-time validator
198
+ // (isValidDescription, 20–400 chars) rejects it. The fix is at GENERATION
199
+ // time: when the source lacks a non-empty `description` and the type
200
+ // requires one, tell the model — unmissably — that it MUST author a valid
201
+ // `description`. (The validator/promote path is NOT changed: it must never
202
+ // fabricate content to pass itself.)
203
+ if (resolvedType && requiresDescription(resolvedType) && !sourceHasNonEmptyDescription(input.assetContent)) {
204
+ sections.push([
205
+ "REQUIRED — synthesize a `description` (the source asset has none):",
206
+ `- The source frontmatter does NOT include a non-empty \`description\`, but a ${resolvedType} asset REQUIRES one or the proposal will be rejected at promote time.`,
207
+ `- You MUST author a valid \`description\` in the proposal frontmatter: ${DESCRIPTION_MIN_CHARS}–${DESCRIPTION_MAX_CHARS} characters of plain-prose sentence summarizing what this asset is about.`,
208
+ '- Synthesize it from the asset\'s `title:` frontmatter, its first `# Heading`, or the opening body sentence. Do NOT copy a bare heading fragment (e.g. "Overview", "Named Page", "Key Insight") and do NOT emit a truncated phrase that ends on `:`/`;`/`,` or a hanging connector word.',
209
+ ].join("\n"));
210
+ }
211
+ }
158
212
  if (input.assetContent?.trim()) {
159
213
  // Cap at 12 000 chars to stay well under OS ARG_MAX when the prompt is
160
214
  // passed as a CLI argument to opencode/claude. Large assets (wiki snapshots,
@@ -288,6 +342,16 @@ export function buildProposePrompt(input) {
288
342
  for (const line of input.schemaHints)
289
343
  sections.push(`- ${line}`);
290
344
  }
345
+ if (input.standardsContext?.trim()) {
346
+ sections.push("Standards to follow (the rulebook for this target):");
347
+ sections.push(input.standardsContext.trim());
348
+ }
349
+ {
350
+ const authoringRules = authoringRulesForType(input.type);
351
+ if (authoringRules) {
352
+ sections.push(authoringRules);
353
+ }
354
+ }
291
355
  sections.push("Produce a single proposal that, if accepted, would land as the asset described above.");
292
356
  sections.push(input.draftFilePath ? fileWriteContract(input.draftFilePath) : RESPONSE_CONTRACT_JSON);
293
357
  return sections.join("\n\n");
@@ -304,6 +368,16 @@ export function buildSchemaRepairPrompt(input) {
304
368
  `while preserving all existing content.`);
305
369
  sections.push(`Target ref: ${input.ref}`);
306
370
  sections.push(`Schema requirements for ${input.type} assets: ${hintForType(input.type)}`);
371
+ if (input.standardsContext?.trim()) {
372
+ sections.push("Standards to follow (the rulebook for this target):");
373
+ sections.push(input.standardsContext.trim());
374
+ }
375
+ {
376
+ const authoringRules = authoringRulesForType(input.type);
377
+ if (authoringRules) {
378
+ sections.push(authoringRules);
379
+ }
380
+ }
307
381
  const CONTENT_CAP = 3000;
308
382
  const body = input.assetContent.trimEnd();
309
383
  const truncated = body.length > CONTENT_CAP;
@@ -370,11 +444,3 @@ export function parseAgentProposalPayload(stdout) {
370
444
  }
371
445
  return out;
372
446
  }
373
- /**
374
- * Strip `\`\`\`json … \`\`\`` fences and `<think>…</think>` reasoning blocks
375
- * from agent output. Thin wrapper around `core/parse` helpers, kept exported
376
- * for backward compatibility (re-exported from `integrations/agent/index.ts`).
377
- */
378
- export function stripJsonFences(text) {
379
- return stripCodeFences(stripThinkBlocks(text));
380
- }
@@ -0,0 +1,59 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * X3 — the ONE dispatch seam for the {@link RunnerSpec} tagged union.
6
+ *
7
+ * The improve slice dispatches a `RunnerSpec` (`llm | agent | sdk`) in several
8
+ * places (`reflect.ts`, `proposal/drain.ts`, …). Before this module each site
9
+ * re-rolled the identical 3-arm switch and re-declared its own per-kind test
10
+ * seams (`chat`, `runAgentFn`, `runSdkFn`). `executeRunner` collapses that into
11
+ * one switch + one {@link RunnerSeams} object.
12
+ *
13
+ * Scoping (behavior-preserving):
14
+ * - The `agent` and `sdk` arms are byte-identical across call sites: invoke
15
+ * the profile runner (`runAgent` / `runOpencodeSdk`) with the per-call
16
+ * `RunAgentOptions` the caller passes. Those default runners live here so
17
+ * callers stop importing `runAgent` / `runOpencodeSdk` for dispatch. The
18
+ * `opts` (incl. any `timeoutMs`) is constructed by the caller and passed
19
+ * through unchanged, so each site keeps its exact option set.
20
+ * - The `llm` arm is irreducibly caller-specific (reflect wraps
21
+ * `runReflectViaLlm`, which returns reflect's iteration shape; drain wraps a
22
+ * plain `chatCompletion`). It is therefore a REQUIRED seam — there is no
23
+ * default `llm` handler — so neither caller's bespoke behavior is changed.
24
+ * - The `assertNever` exhaustiveness arm is kept so a 4th `RunnerSpec` kind is
25
+ * a compile error here instead of a silent runtime fall-through.
26
+ *
27
+ * The return type is {@link AgentRunResult} so a later `callStructured` layer
28
+ * (X2) can wrap `executeRunner` without changing this contract.
29
+ */
30
+ import { assertNever } from "../../core/assert.js";
31
+ import { runOpencodeSdk } from "../harnesses/opencode-sdk/index.js";
32
+ import { runAgent } from "./spawn.js";
33
+ /**
34
+ * Dispatch a {@link RunnerSpec} to its runner and return the raw
35
+ * {@link AgentRunResult}. `opts` is the {@link RunAgentOptions} for the profile
36
+ * (`agent` / `sdk`) arms; it is passed through unchanged so each caller keeps
37
+ * its exact option set (incl. any `timeoutMs` the caller chose to apply).
38
+ */
39
+ export async function executeRunner(spec, prompt, opts, seams = {}) {
40
+ switch (spec.kind) {
41
+ case "llm": {
42
+ if (!seams.llm) {
43
+ throw new Error("executeRunner: an `llm` runner requires a `seams.llm` handler (no default LLM dispatch).");
44
+ }
45
+ return seams.llm(spec, prompt);
46
+ }
47
+ case "agent": {
48
+ const run = seams.runAgent ?? runAgent;
49
+ return run(spec.profile, prompt, opts);
50
+ }
51
+ case "sdk": {
52
+ const run = seams.runSdk ?? runOpencodeSdk;
53
+ return run(spec.profile, prompt, opts);
54
+ }
55
+ default:
56
+ // Exhaustiveness arm: a 4th RunnerSpec kind becomes a compile error here.
57
+ return assertNever(spec);
58
+ }
59
+ }
@@ -157,6 +157,19 @@ function resolveProcessRunnerWithLlmFallback(block, config, opts) {
157
157
  }
158
158
  return null;
159
159
  }
160
+ /**
161
+ * Build the tool-less HTTP runner from `defaults.llm`, or `null` when unset.
162
+ * The unattended-improve reflect pin (meta-review 07 Chain-G / P1.3) uses this
163
+ * as the mandatory downgrade target when config would otherwise hand reflect a
164
+ * tool-capable (agent/SDK) runner in a scheduled run. Throws ConfigError when
165
+ * `defaults.llm` names a profile that does not exist.
166
+ */
167
+ export function resolveDefaultLlmRunner(config, timeoutMs) {
168
+ const name = config.defaults?.llm;
169
+ if (!name)
170
+ return null;
171
+ return buildLlmRunnerSpec(name, timeoutMs, config);
172
+ }
160
173
  export function resolveValidationRunner(config) {
161
174
  const validation = config.profiles?.improve?.default?.processes?.validation;
162
175
  const block = validation && validation.enabled !== false ? validation : undefined;
@@ -213,15 +226,6 @@ export function resolveImproveProcessRunnerFromProfile(processConfig, config) {
213
226
  }
214
227
  return null;
215
228
  }
216
- /**
217
- * Convenience accessor for callers that previously read
218
- * `getProcessOptions("index", "staleness_detection", config).thresholdDays`.
219
- * After the 0.8.0 migration, those values live on first-class config keys —
220
- * see `config.index?.stalenessDetection?.thresholdDays` etc.
221
- */
222
- export function getStalenessDetectionThresholdDays(config) {
223
- return config.index?.stalenessDetection?.thresholdDays;
224
- }
225
229
  // Re-export `isProcessEnabled` from feature-gate.ts so callers that previously
226
230
  // imported it from runner.ts continue to work.
227
231
  export { isProcessEnabled } from "../../llm/feature-gate.js";
@@ -17,6 +17,7 @@
17
17
  import fs from "node:fs";
18
18
  import os from "node:os";
19
19
  import path from "node:path";
20
+ import { parseEmbeddedJsonResponse } from "../../core/parse.js";
20
21
  import { spawn as runtimeSpawn } from "../../runtime.js";
21
22
  import { getCommandBuilder } from "./builders.js";
22
23
  import { DEFAULT_AGENT_TIMEOUT_MS } from "./config.js";
@@ -203,6 +204,19 @@ export async function runAgent(profile, prompt, options = {}) {
203
204
  const finalArgv = [...builtArgv, ...(options.dispatch ? (options.args ?? []) : [])];
204
205
  const env = { ...buildChildEnv(profile, options), ...(builtEnv ?? {}) };
205
206
  const start = Date.now();
207
+ // Cooperative cancel: refuse to spawn at all when the caller's signal is
208
+ // already aborted (e.g. the run's budget was exhausted before this unit).
209
+ if (options.signal?.aborted) {
210
+ return {
211
+ ok: false,
212
+ exitCode: null,
213
+ stdout: "",
214
+ stderr: "",
215
+ durationMs: 0,
216
+ reason: "aborted",
217
+ error: `agent CLI "${profile.name}" not started: caller signal already aborted`,
218
+ };
219
+ }
206
220
  let proc;
207
221
  try {
208
222
  const spawnFn = resolveSpawnFn(options);
@@ -211,7 +225,9 @@ export async function runAgent(profile, prompt, options = {}) {
211
225
  stdout: stdioMode === "captured" ? "pipe" : "inherit",
212
226
  stderr: stdioMode === "captured" ? "pipe" : "inherit",
213
227
  env,
214
- ...(options.cwd ? { cwd: options.cwd } : {}),
228
+ // options.cwd wins; dispatch.cwd is the request-level fallback (it was
229
+ // declared on AgentDispatchRequest but consumed by nothing — P0.5 fix).
230
+ ...((options.cwd ?? options.dispatch?.cwd) ? { cwd: options.cwd ?? options.dispatch?.cwd } : {}),
215
231
  // Spawn in its own process group so killGroup(-pid, signal) reaches all
216
232
  // descendants (e.g. the .opencode binary that opencode's node wrapper forks).
217
233
  // Only applied in captured mode — interactive mode inherits the parent
@@ -258,6 +274,31 @@ export async function runAgent(profile, prompt, options = {}) {
258
274
  }, 5000);
259
275
  }, timeoutMs);
260
276
  }
277
+ // Cooperative cancel: same SIGTERM→SIGKILL discipline as the timeout, but
278
+ // flagged separately so the result carries `reason: "aborted"`.
279
+ let aborted = false;
280
+ const abortSignal = options.signal;
281
+ const onAbort = () => {
282
+ if (!proc || proc.exitCode !== null)
283
+ return;
284
+ aborted = true;
285
+ killGroup(proc, "SIGTERM");
286
+ const sigkillTimer = setTimeoutImpl(() => {
287
+ if (!proc || proc.exitCode !== null)
288
+ return;
289
+ killGroup(proc, "SIGKILL");
290
+ }, 5000);
291
+ if (typeof sigkillTimer !== "number")
292
+ sigkillTimer.unref?.();
293
+ };
294
+ if (abortSignal) {
295
+ // A signal that aborted between the pre-spawn check and here is handled
296
+ // by calling the listener directly.
297
+ if (abortSignal.aborted)
298
+ onAbort();
299
+ else
300
+ abortSignal.addEventListener("abort", onAbort, { once: true });
301
+ }
261
302
  // Stream-drain timeout: the overall wall-clock budget plus a 2 s grace
262
303
  // period. When a process is killed via SIGTERM/SIGKILL (from our timeout
263
304
  // handler or from outside) some runtimes keep the pipe write-end open in
@@ -304,6 +345,7 @@ export async function runAgent(profile, prompt, options = {}) {
304
345
  catch (err) {
305
346
  if (timer !== undefined)
306
347
  clearTimeoutImpl(timer);
348
+ abortSignal?.removeEventListener("abort", onAbort);
307
349
  // BUG-H2: drain stream readers before the early return so they don't
308
350
  // surface as unhandled rejections after the function resolves.
309
351
  // The streams already carry a built-in drain timeout so this allSettled
@@ -321,8 +363,20 @@ export async function runAgent(profile, prompt, options = {}) {
321
363
  };
322
364
  }
323
365
  clearTimeoutImpl(timer);
366
+ abortSignal?.removeEventListener("abort", onAbort);
324
367
  const [stdout, stderr] = await Promise.all([stdoutPromise, stderrPromise]);
325
368
  const durationMs = Date.now() - start;
369
+ if (aborted) {
370
+ return {
371
+ ok: false,
372
+ exitCode,
373
+ stdout,
374
+ stderr,
375
+ durationMs,
376
+ reason: "aborted",
377
+ error: `agent CLI "${profile.name}" aborted by caller signal`,
378
+ };
379
+ }
326
380
  if (timedOut) {
327
381
  return {
328
382
  ok: false,
@@ -346,72 +400,20 @@ export async function runAgent(profile, prompt, options = {}) {
346
400
  };
347
401
  }
348
402
  if (parseOutput === "json" && stdioMode === "captured") {
349
- // Strip <think> blocks and code fences, then try direct parse with
350
- // embedded-JSON fallback for local LLMs that emit prose around the payload.
351
- const cleaned = stdout
352
- .trim()
353
- .replace(/<think>[\s\S]*?<\/think>/gi, "")
354
- .trim()
355
- .replace(/^```(?:json)?\s*\n?/, "")
356
- .replace(/\n?```\s*$/, "")
357
- .trim();
358
- let parsed;
359
- try {
360
- parsed = JSON.parse(cleaned);
361
- }
362
- catch {
363
- // Fallback: extract the first balanced {…} from prose output.
364
- let found;
365
- for (let s = 0; s < cleaned.length; s++) {
366
- if (cleaned[s] !== "{")
367
- continue;
368
- let depth = 0, inStr = false, esc = false;
369
- for (let i = s; i < cleaned.length; i++) {
370
- const c = cleaned[i];
371
- if (inStr) {
372
- if (esc) {
373
- esc = false;
374
- }
375
- else if (c === "\\") {
376
- esc = true;
377
- }
378
- else if (c === '"') {
379
- inStr = false;
380
- }
381
- continue;
382
- }
383
- if (c === '"') {
384
- inStr = true;
385
- continue;
386
- }
387
- if (c === "{")
388
- depth++;
389
- if (c === "}") {
390
- depth--;
391
- if (depth === 0) {
392
- try {
393
- found = JSON.parse(cleaned.slice(s, i + 1));
394
- }
395
- catch { }
396
- break;
397
- }
398
- }
399
- }
400
- if (found !== undefined)
401
- break;
402
- }
403
- if (found === undefined) {
404
- return {
405
- ok: false,
406
- exitCode,
407
- stdout,
408
- stderr,
409
- durationMs,
410
- reason: "parse_error",
411
- error: "no JSON object found in agent output",
412
- };
413
- }
414
- parsed = found;
403
+ // Strip <think> blocks and code fences, then parse with embedded-JSON
404
+ // fallback for local LLMs that emit prose around the payload. Handles
405
+ // both top-level `{…}` and `[…]` structures.
406
+ const parsed = parseEmbeddedJsonResponse(stdout);
407
+ if (parsed === undefined) {
408
+ return {
409
+ ok: false,
410
+ exitCode,
411
+ stdout,
412
+ stderr,
413
+ durationMs,
414
+ reason: "parse_error",
415
+ error: "no JSON structure found in agent output",
416
+ };
415
417
  }
416
418
  return { ok: true, exitCode, stdout, stderr, durationMs, parsed };
417
419
  }
@@ -33,7 +33,7 @@ export const claudeBuilder = {
33
33
  args.push("--system-prompt", req.systemPrompt);
34
34
  }
35
35
  if (req.model) {
36
- const resolved = resolveModel(req.model, "claude", profile.modelAliases);
36
+ const resolved = resolveModel(req.model, "claude", profile.modelAliases, profile.globalModelAliases);
37
37
  args.push("--model", resolved);
38
38
  }
39
39
  if (req.tools) {
@@ -25,6 +25,7 @@
25
25
  * that say `'claude-code'` keep working unchanged.
26
26
  */
27
27
  import { BaseHarness } from "../types.js";
28
+ import { claudeBuilder } from "./agent-builder.js";
28
29
  export { claudeBuilder } from "./agent-builder.js";
29
30
  export { claudeCodeImporter } from "./config-import.js";
30
31
  export { ClaudeCodeProvider } from "./session-log.js";
@@ -53,6 +54,7 @@ export class ClaudeHarness extends BaseHarness {
53
54
  // Home-relative config dir scanned by `akm setup` (#567). Claude Code has a
54
55
  // session-log provider, so offering it as a stash source is functional.
55
56
  setupDetectionDir = ".claude";
57
+ agentBuilder = claudeBuilder;
56
58
  capabilities = caps({
57
59
  sessionLogs: true,
58
60
  agentDispatch: true,
@@ -107,6 +107,16 @@ export class ClaudeCodeProvider {
107
107
  isAvailable() {
108
108
  return fs.existsSync(claudeProjectsDir());
109
109
  }
110
+ /**
111
+ * Directory holding Claude Code's per-project session JSONL files
112
+ * (`~/.claude/projects`, honoring `AKM_CLAUDE_PROJECTS_DIR`). Returns `[]`
113
+ * when the directory does not exist on this machine. See {@link
114
+ * SessionLogHarness.watchRoots}.
115
+ */
116
+ watchRoots() {
117
+ const dir = claudeProjectsDir();
118
+ return fs.existsSync(dir) ? [dir] : [];
119
+ }
110
120
  *readEvents(input) {
111
121
  try {
112
122
  for (const jsonlPath of this.#walkJsonl(claudeProjectsDir())) {
@@ -60,9 +60,8 @@ const HARNESS_BY_ANY_ID = (() => {
60
60
  })();
61
61
  /**
62
62
  * Canonical, ordered list of valid harness / platform ids. The Zod
63
- * `AgentPlatformSchema` enum, the `AgentProfileConfigV2` platform union,
64
- * `parseAgentProfilesMapV2`'s membership check, and setup's `DetectedHarness`
65
- * union all derive from this so they cannot drift.
63
+ * `AgentPlatformSchema` enum, the `AgentProfileConfig` platform union, and
64
+ * setup's `DetectedHarness` union all derive from this so they cannot drift.
66
65
  */
67
66
  export const VALID_HARNESS_IDS = Object.freeze(HARNESS_REGISTRY.map((h) => h.id));
68
67
  /** Harnesses that expose readable native session logs. */
@@ -33,7 +33,7 @@ export const opencodeBuilder = {
33
33
  args.push("--system-prompt", req.systemPrompt);
34
34
  }
35
35
  if (req.model) {
36
- const resolved = resolveModel(req.model, "opencode", profile.modelAliases);
36
+ const resolved = resolveModel(req.model, "opencode", profile.modelAliases, profile.globalModelAliases);
37
37
  args.push("--model", resolved);
38
38
  }
39
39
  args.push("--");
@@ -18,6 +18,7 @@
18
18
  * Claude Code's 'claude' vs 'claude-code'), so no alias bridge is needed.
19
19
  */
20
20
  import { BaseHarness } from "../types.js";
21
+ import { opencodeBuilder } from "./agent-builder.js";
21
22
  export { opencodeBuilder } from "./agent-builder.js";
22
23
  export { openCodeImporter } from "./config-import.js";
23
24
  export { OpenCodeProvider } from "./session-log.js";
@@ -48,6 +49,7 @@ export class OpencodeHarness extends BaseHarness {
48
49
  // `v1ProfilePlatform()` resolves most-specific-id-first, so "opencode-sdk-*"
49
50
  // is claimed by OpencodeSdkHarness before this prefix can over-match it.
50
51
  v1ProfilePrefixes = ["opencode"];
52
+ agentBuilder = opencodeBuilder;
51
53
  capabilities = caps({
52
54
  sessionLogs: true,
53
55
  agentDispatch: true,